Skip to content

Commit f4339f4

Browse files
authored
Merge pull request #152 from cephie-studios/canary
chore: db migration
2 parents c059178 + 9e2255e commit f4339f4

32 files changed

Lines changed: 560 additions & 841 deletions

.env.example

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ VITE_SERVER_URL=http://localhost:9901 # Make sure this matches the PORT value be
22
FRONTEND_URL=http://localhost:5173 # Make sure this matches the vite.config.ts
33

44
POSTGRES_DB_URL=postgresql://pfcontrol:pfcontrol@localhost:5432/pfcontrol
5-
POSTGRES_DB_URL_FLIGHTS=postgresql://pfcontrol:pfcontrol@localhost:5432/pfcontrol_flights
6-
POSTGRES_DB_URL_CHATS=postgresql://pfcontrol:pfcontrol@localhost:5432/pfcontrol_chats
7-
POSTGRES_DB_PW=pfcontrol
5+
86
DB_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE # Must be 128 Characters Long - use this in terminal: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
97
REDIS_URL=redis://localhost:6379
108

server/db/admin.ts

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { mainDb, flightsDb } from './connection.js';
1+
import { mainDb } from './connection.js';
22
import { cleanupOldStatistics } from './statistics.js';
3-
import { getAllSessions } from './sessions.js';
43
import { sql } from 'kysely';
54
import { redisConnection } from './connection.js';
65
import { decrypt } from '../utils/encryption.js';
@@ -11,15 +10,15 @@ import { getUserRoles } from './roles.js';
1110
type RawUser = {
1211
id: string;
1312
username: string;
14-
discriminator: string;
13+
discriminator?: string;
1514
avatar: string | null;
1615
last_login: Date | null;
1716
ip_address: string | null;
1817
is_vpn: boolean;
1918
total_sessions_created: number;
2019
total_minutes: number;
2120
created_at: Date | undefined;
22-
settings: string | null;
21+
settings: unknown;
2322
roblox_username: string | null;
2423
role_id: number | null;
2524
role_name: string | null;
@@ -46,23 +45,11 @@ async function calculateDirectStatistics() {
4645
.select(({ fn }) => fn.countAll().as('count'))
4746
.executeTakeFirst();
4847

49-
const sessions = await getAllSessions();
50-
let totalFlights = 0;
51-
52-
for (const session of sessions) {
53-
try {
54-
const tableName = `flights_${session.session_id}`;
55-
const flightResult = await flightsDb
56-
.selectFrom(tableName)
57-
.select(({ fn }) => fn.countAll().as('count'))
58-
.executeTakeFirst();
59-
totalFlights += Number(flightResult?.count) || 0;
60-
} catch {
61-
console.warn(
62-
`Could not count flights for session ${session.session_id}`
63-
);
64-
}
65-
}
48+
const flightCountResult = await mainDb
49+
.selectFrom('flights')
50+
.select(({ fn }) => fn.countAll().as('count'))
51+
.executeTakeFirst();
52+
const totalFlights = Number(flightCountResult?.count) || 0;
6653

6754
return {
6855
total_logins: 0,
@@ -379,7 +366,11 @@ export async function getAllUsers(
379366
let decryptedSettings = null;
380367
try {
381368
if (user.settings) {
382-
decryptedSettings = decrypt(JSON.parse(user.settings));
369+
const settingsObj =
370+
typeof user.settings === 'string'
371+
? JSON.parse(user.settings)
372+
: user.settings;
373+
decryptedSettings = decrypt(settingsObj);
383374
}
384375
} catch {
385376
console.warn(`Failed to decrypt settings for user ${user.id}`);
@@ -548,19 +539,25 @@ export async function getAdminSessions(page = 1, limit = 100, search = '') {
548539

549540
const sessions = await query.limit(limit).offset(offset).execute();
550541

542+
const sessionIds = sessions.map((s) => s.session_id);
543+
544+
const flightCounts =
545+
sessionIds.length > 0
546+
? await mainDb
547+
.selectFrom('flights')
548+
.select(['session_id', mainDb.fn.countAll().as('count')])
549+
.where('session_id', 'in', sessionIds)
550+
.groupBy('session_id')
551+
.execute()
552+
: [];
553+
554+
const flightCountMap = new Map(
555+
flightCounts.map((r) => [r.session_id, Number(r.count)])
556+
);
557+
551558
const sessionsWithDetails = await Promise.all(
552559
sessions.map(async (session) => {
553-
let flight_count = 0;
554-
try {
555-
const tableName = `flights_${session.session_id}`;
556-
const flightResult = await flightsDb
557-
.selectFrom(tableName)
558-
.select(({ fn }) => fn.countAll().as('count'))
559-
.executeTakeFirst();
560-
flight_count = Number(flightResult?.count) || 0;
561-
} catch {
562-
// Table may not exist, keep flight_count as 0
563-
}
560+
const flight_count = flightCountMap.get(session.session_id) || 0;
564561
const activeSessionUsers = await getActiveUsersForSession(
565562
session.session_id
566563
);

server/db/apiLogs.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ export async function getApiLogs(
8484
'request_body',
8585
'response_body',
8686
'error_message',
87-
'timestamp',
87+
'created_at',
8888
])
89-
.orderBy('timestamp', 'desc');
89+
.orderBy('created_at', 'desc');
9090

9191
if (filters.userId) {
9292
query = query.where((q) =>
@@ -110,11 +110,11 @@ export async function getApiLogs(
110110
}
111111

112112
if (filters.dateFrom) {
113-
query = query.where('timestamp', '>=', new Date(filters.dateFrom));
113+
query = query.where('created_at', '>=', new Date(filters.dateFrom));
114114
}
115115

116116
if (filters.dateTo) {
117-
query = query.where('timestamp', '<=', new Date(filters.dateTo));
117+
query = query.where('created_at', '<=', new Date(filters.dateTo));
118118
}
119119

120120
if (filters.search) {
@@ -154,14 +154,14 @@ export async function getApiLogs(
154154
}
155155
if (filters.dateFrom) {
156156
countQuery = countQuery.where(
157-
'timestamp',
157+
'created_at',
158158
'>=',
159159
new Date(filters.dateFrom)
160160
);
161161
}
162162
if (filters.dateTo) {
163163
countQuery = countQuery.where(
164-
'timestamp',
164+
'created_at',
165165
'<=',
166166
new Date(filters.dateTo)
167167
);
@@ -242,7 +242,7 @@ export async function getApiLogStats(days: number = 7): Promise<ApiLogStats> {
242242
'errorCount'
243243
),
244244
])
245-
.where('timestamp', '>=', cutoffDate)
245+
.where('created_at', '>=', cutoffDate)
246246
.executeTakeFirst();
247247

248248
const totalRequests = Number(totalStatsResult?.totalRequests || 0);
@@ -261,7 +261,7 @@ export async function getApiLogStats(days: number = 7): Promise<ApiLogStats> {
261261
sql<number>`count(*)`.as('count'),
262262
sql<number>`avg(response_time)`.as('avgResponseTime'),
263263
])
264-
.where('timestamp', '>=', cutoffDate)
264+
.where('created_at', '>=', cutoffDate)
265265
.groupBy('path')
266266
.orderBy('count', 'desc')
267267
.limit(10)
@@ -271,7 +271,7 @@ export async function getApiLogStats(days: number = 7): Promise<ApiLogStats> {
271271
const statusCodeDistribution = await mainDb
272272
.selectFrom('api_logs')
273273
.select(['status_code', sql<number>`count(*)`.as('count')])
274-
.where('timestamp', '>=', cutoffDate)
274+
.where('created_at', '>=', cutoffDate)
275275
.groupBy('status_code')
276276
.orderBy('count', 'desc')
277277
.execute();
@@ -280,15 +280,15 @@ export async function getApiLogStats(days: number = 7): Promise<ApiLogStats> {
280280
const dailyStats = await mainDb
281281
.selectFrom('api_logs')
282282
.select([
283-
sql<string>`date(timestamp)`.as('date'),
283+
sql<string>`date(created_at)`.as('date'),
284284
sql<number>`count(*)`.as('requestCount'),
285285
sql<number>`count(case when status_code >= 400 then 1 end)`.as(
286286
'errorCount'
287287
),
288288
sql<number>`avg(response_time)`.as('avgResponseTime'),
289289
])
290-
.where('timestamp', '>=', cutoffDate)
291-
.groupBy(sql`date(timestamp)`)
290+
.where('created_at', '>=', cutoffDate)
291+
.groupBy(sql`date(created_at)`)
292292
.orderBy('date', 'desc')
293293
.execute();
294294

@@ -348,7 +348,7 @@ export async function getApiLogStatsLast24Hours(): Promise<
348348
const hourlyStats = await mainDb
349349
.selectFrom('api_logs')
350350
.select([
351-
sql<string>`date_trunc('hour', timestamp)`.as('hour'),
351+
sql<string>`date_trunc('hour', created_at)`.as('hour'),
352352
sql<number>`count(case when status_code >= 200 and status_code < 300 then 1 end)`.as(
353353
'successful'
354354
),
@@ -362,8 +362,8 @@ export async function getApiLogStatsLast24Hours(): Promise<
362362
'other'
363363
),
364364
])
365-
.where('timestamp', '>=', cutoffDate)
366-
.groupBy(sql`date_trunc('hour', timestamp)`)
365+
.where('created_at', '>=', cutoffDate)
366+
.groupBy(sql`date_trunc('hour', created_at)`)
367367
.orderBy('hour', 'asc')
368368
.execute();
369369

server/db/audit.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function logAdminAction(actionData: AdminActionData) {
4848
user_agent:
4949
userAgent !== null && userAgent !== undefined ? userAgent : undefined,
5050
})
51-
.returning(['id', 'timestamp'])
51+
.returning(['id', 'created_at'])
5252
.executeTakeFirst();
5353

5454
return result?.id;
@@ -86,7 +86,7 @@ export async function getAuditLogs(
8686
'details',
8787
'ip_address',
8888
'user_agent',
89-
'timestamp',
89+
'created_at',
9090
]);
9191

9292
if (filters.adminId) {
@@ -112,15 +112,15 @@ export async function getAuditLogs(
112112
}
113113

114114
if (filters.dateFrom) {
115-
query = query.where('timestamp', '>=', new Date(filters.dateFrom));
115+
query = query.where('created_at', '>=', new Date(filters.dateFrom));
116116
}
117117

118118
if (filters.dateTo) {
119-
query = query.where('timestamp', '<=', new Date(filters.dateTo));
119+
query = query.where('created_at', '<=', new Date(filters.dateTo));
120120
}
121121

122122
const logsResult = await query
123-
.orderBy('timestamp', 'desc')
123+
.orderBy('created_at', 'desc')
124124
.limit(limit)
125125
.offset(offset)
126126
.execute();
@@ -174,14 +174,14 @@ export async function getAuditLogs(
174174
}
175175
if (filters.dateFrom) {
176176
countQuery = countQuery.where(
177-
'timestamp',
177+
'created_at',
178178
'>=',
179179
new Date(filters.dateFrom)
180180
);
181181
}
182182
if (filters.dateTo) {
183183
countQuery = countQuery.where(
184-
'timestamp',
184+
'created_at',
185185
'<=',
186186
new Date(filters.dateTo)
187187
);
@@ -219,7 +219,7 @@ export async function getAuditLogById(logId: number | string) {
219219
'details',
220220
'ip_address',
221221
'user_agent',
222-
'timestamp',
222+
'created_at',
223223
])
224224
.where('id', '=', typeof logId === 'string' ? Number(logId) : logId)
225225
.executeTakeFirst();
@@ -261,7 +261,7 @@ export async function cleanupOldAuditLogs(daysToKeep = 14) {
261261
const result = await mainDb
262262
.deleteFrom('audit_log')
263263
.where(
264-
'timestamp',
264+
'created_at',
265265
'<',
266266
new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000)
267267
)

0 commit comments

Comments
 (0)