Skip to content

Commit 0d916b3

Browse files
authored
Merge pull request #84 from CoolerMinecraft/release
Release
2 parents 73f944a + c631763 commit 0d916b3

21 files changed

Lines changed: 2516 additions & 1597 deletions

server/db/flightLogs.ts

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { mainDb } from "./connection.js";
2-
import { encrypt, decrypt } from "../utils/encryption.js"; // Assuming decrypt function exists
2+
import { encrypt, decrypt } from "../utils/encryption.js";
33
import { sql } from "kysely";
44

55
export interface FlightLogData {
@@ -44,7 +44,6 @@ export async function logFlightAction(logData: FlightLogData) {
4444
.execute();
4545
} catch (error) {
4646
console.error('Error logging flight action:', error);
47-
// Non-blocking: don't throw, just log
4847
}
4948
}
5049

@@ -67,7 +66,6 @@ let cleanupInterval: NodeJS.Timeout | null = null;
6766
export function startFlightLogsCleanup() {
6867
const CLEANUP_INTERVAL = 24 * 60 * 60 * 1000; // Daily
6968

70-
// Initial cleanup after 1 minute
7169
setTimeout(async () => {
7270
try {
7371
await cleanupOldFlightLogs(30);
@@ -76,7 +74,6 @@ export function startFlightLogsCleanup() {
7674
}
7775
}, 60 * 1000);
7876

79-
// Recurring cleanup
8077
cleanupInterval = setInterval(async () => {
8178
try {
8279
await cleanupOldFlightLogs(30);
@@ -94,12 +91,12 @@ export function stopFlightLogsCleanup() {
9491
}
9592

9693
export interface FlightLogFilters {
94+
general?: string;
9795
user?: string;
9896
action?: 'add' | 'update' | 'delete';
9997
session?: string;
10098
flightId?: string;
101-
dateFrom?: string;
102-
dateTo?: string;
99+
date?: string;
103100
text?: string;
104101
}
105102

@@ -116,6 +113,20 @@ export async function getFlightLogs(
116113
.limit(limit)
117114
.offset((page - 1) * limit);
118115

116+
if (filters.general) {
117+
const searchPattern = `%${filters.general}%`;
118+
query = query.where((eb) =>
119+
eb.or([
120+
eb('username', 'ilike', searchPattern),
121+
eb('session_id', 'ilike', searchPattern),
122+
eb('flight_id', 'ilike', searchPattern),
123+
eb('user_id', 'ilike', searchPattern),
124+
eb(sql`old_data::text`, 'ilike', searchPattern),
125+
eb(sql`new_data::text`, 'ilike', searchPattern),
126+
])
127+
);
128+
}
129+
119130
if (filters.user) {
120131
query = query.where('username', 'ilike', `%${filters.user}%`);
121132
}
@@ -128,11 +139,14 @@ export async function getFlightLogs(
128139
if (filters.flightId) {
129140
query = query.where('flight_id', '=', filters.flightId);
130141
}
131-
if (filters.dateFrom) {
132-
query = query.where('timestamp', '>=', new Date(filters.dateFrom));
133-
}
134-
if (filters.dateTo) {
135-
query = query.where('timestamp', '<=', new Date(filters.dateTo));
142+
if (filters.date) {
143+
const startOfDay = new Date(filters.date);
144+
startOfDay.setHours(0, 0, 0, 0);
145+
const endOfDay = new Date(filters.date);
146+
endOfDay.setHours(23, 59, 59, 999);
147+
148+
query = query.where('timestamp', '>=', startOfDay);
149+
query = query.where('timestamp', '<=', endOfDay);
136150
}
137151
if (filters.text) {
138152
const searchPattern = `%${filters.text}%`;
@@ -144,11 +158,56 @@ export async function getFlightLogs(
144158
);
145159
}
146160

147-
const logs = await query.execute();
148-
const total = await mainDb
161+
let countQuery = mainDb
149162
.selectFrom('flight_logs')
150-
.select((eb) => eb.fn.count('id').as('count'))
151-
.executeTakeFirst();
163+
.select((eb) => eb.fn.count('id').as('count'));
164+
165+
if (filters.general) {
166+
const searchPattern = `%${filters.general}%`;
167+
countQuery = countQuery.where((eb) =>
168+
eb.or([
169+
eb('username', 'ilike', searchPattern),
170+
eb('session_id', 'ilike', searchPattern),
171+
eb('flight_id', 'ilike', searchPattern),
172+
eb('user_id', 'ilike', searchPattern),
173+
eb(sql`old_data::text`, 'ilike', searchPattern),
174+
eb(sql`new_data::text`, 'ilike', searchPattern),
175+
])
176+
);
177+
}
178+
if (filters.user) {
179+
countQuery = countQuery.where('username', 'ilike', `%${filters.user}%`);
180+
}
181+
if (filters.action) {
182+
countQuery = countQuery.where('action', '=', filters.action);
183+
}
184+
if (filters.session) {
185+
countQuery = countQuery.where('session_id', '=', filters.session);
186+
}
187+
if (filters.flightId) {
188+
countQuery = countQuery.where('flight_id', '=', filters.flightId);
189+
}
190+
if (filters.date) {
191+
const startOfDay = new Date(filters.date);
192+
startOfDay.setHours(0, 0, 0, 0);
193+
const endOfDay = new Date(filters.date);
194+
endOfDay.setHours(23, 59, 59, 999);
195+
196+
countQuery = countQuery.where('timestamp', '>=', startOfDay);
197+
countQuery = countQuery.where('timestamp', '<=', endOfDay);
198+
}
199+
if (filters.text) {
200+
const searchPattern = `%${filters.text}%`;
201+
countQuery = countQuery.where((eb) =>
202+
eb.or([
203+
eb(sql`old_data::text`, 'ilike', searchPattern),
204+
eb(sql`new_data::text`, 'ilike', searchPattern),
205+
])
206+
);
207+
}
208+
209+
const logs = await query.execute();
210+
const total = await countQuery.executeTakeFirst();
152211

153212
return {
154213
logs: logs.map(log => ({

server/db/users.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export async function getUserById(userId: string) {
3636
let cached = null;
3737
try {
3838
cached = await redisConnection.get(cacheKey);
39+
if (cached) {
40+
return JSON.parse(cached);
41+
}
3942
} catch (error) {
4043
if (error instanceof Error) {
4144
console.warn('[Redis] Failed to read cache for user:', error.message);
@@ -134,6 +137,9 @@ export async function getUserByUsername(username: string) {
134137
let cached = null;
135138
try {
136139
cached = await redisConnection.get(cacheKey);
140+
if (cached) {
141+
return JSON.parse(cached);
142+
}
137143
} catch (error) {
138144
if (error instanceof Error) {
139145
console.warn(`[Redis] Failed to read cache for username ${username}:`, error.message);
@@ -169,7 +175,7 @@ export async function getUserByUsername(username: string) {
169175
};
170176

171177
try {
172-
await redisConnection.set(cacheKey, JSON.stringify(result), "EX", 60 * 30); // cache for 30 minutes
178+
await redisConnection.set(cacheKey, JSON.stringify(result), "EX", 60 * 30);
173179
} catch (error) {
174180
if (error instanceof Error) {
175181
console.warn(`[Redis] Failed to set cache for username ${username}:`, error.message);

server/routes/admin/flight-logs.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import express, { Request, Response } from 'express';
2-
import { createAuditLogger, logIPAccess } from '../../middleware/auditLogger.js';
2+
import { createAuditLogger } from '../../middleware/auditLogger.js';
33
import { requirePermission } from '../../middleware/rolePermissions.js';
44
import { getFlightLogs, getFlightLogById } from '../../db/flightLogs.js';
5+
import { logAdminAction } from '../../db/audit.js';
6+
import { getClientIp } from '../../utils/getIpAddress.js';
57

68
const router = express.Router();
79

@@ -12,26 +14,26 @@ router.get('/', createAuditLogger('ADMIN_FLIGHT_LOGS_ACCESSED'), async (req, res
1214
try {
1315
const pageParam = req.query.page;
1416
const limitParam = req.query.limit;
17+
const generalParam = req.query.general;
1518
const userParam = req.query.user;
1619
const actionParam = req.query.action;
1720
const sessionParam = req.query.session;
1821
const flightIdParam = req.query.flightId;
19-
const dateFromParam = req.query.dateFrom;
20-
const dateToParam = req.query.dateTo;
22+
const dateParam = req.query.date;
2123
const textParam = req.query.text;
2224

2325
const page = typeof pageParam === 'string' ? parseInt(pageParam) : 1;
2426
const limit = typeof limitParam === 'string' ? parseInt(limitParam) : 50;
27+
const general = typeof generalParam === 'string' ? generalParam : undefined;
2528
const user = typeof userParam === 'string' ? userParam : undefined;
2629
const validActions = ["add", "update", "delete"] as const;
2730
const action = typeof actionParam === 'string' && validActions.includes(actionParam as typeof validActions[number]) ? actionParam as typeof validActions[number] : undefined;
2831
const session = typeof sessionParam === 'string' ? sessionParam : undefined;
2932
const flightId = typeof flightIdParam === 'string' ? flightIdParam : undefined;
30-
const dateFrom = typeof dateFromParam === 'string' ? dateFromParam : undefined;
31-
const dateTo = typeof dateToParam === 'string' ? dateToParam : undefined;
33+
const date = typeof dateParam === 'string' ? dateParam : undefined;
3234
const text = typeof textParam === 'string' ? textParam : undefined;
3335

34-
const data = await getFlightLogs(page, limit, { user, action, session, flightId, dateFrom, dateTo, text });
36+
const data = await getFlightLogs(page, limit, { general, user, action, session, flightId, date, text });
3537
res.json(data);
3638
} catch (error) {
3739
console.error('Error fetching flight logs:', error);
@@ -55,14 +57,43 @@ router.get('/:id', createAuditLogger('ADMIN_FLIGHT_LOG_VIEWED'), async (req, res
5557
});
5658

5759
// POST: /api/admin/flight-logs/reveal-ip/:id
58-
router.post('/reveal-ip/:id', createAuditLogger('FLIGHT_LOG_IP_REVEALED'), logIPAccess, async (req: Request, res: Response) => {
60+
router.post('/reveal-ip/:id', createAuditLogger('FLIGHT_LOG_IP_REVEALED'), async (req: Request, res: Response) => {
5961
try {
6062
const logId = req.params.id;
6163
const log = await getFlightLogById(logId);
6264
if (!log) {
6365
return res.status(404).json({ error: 'Flight log not found' });
6466
}
65-
res.json({ ip_address: log.ip_address });
67+
68+
// Log the IP access manually since logIPAccess middleware isn't working properly
69+
if (req.user?.userId) {
70+
try {
71+
const clientIp = getClientIp(req);
72+
await logAdminAction({
73+
adminId: req.user.userId,
74+
adminUsername: req.user.username || 'Unknown Admin',
75+
actionType: 'FLIGHT_LOG_IP_REVEALED',
76+
targetUserId: log.user_id,
77+
targetUsername: log.username,
78+
details: {
79+
method: req.method,
80+
url: req.originalUrl,
81+
revealedIP: log.ip_address,
82+
flightLogId: log.id,
83+
timestamp: new Date().toISOString()
84+
},
85+
ipAddress: Array.isArray(clientIp) ? clientIp[0] : clientIp,
86+
userAgent: req.get('User-Agent') || null
87+
});
88+
} catch (auditError) {
89+
console.error('Failed to log flight log IP access:', auditError);
90+
}
91+
}
92+
93+
res.json({
94+
logId: log.id,
95+
ip_address: log.ip_address
96+
});
6697
} catch (error) {
6798
console.error('Error revealing flight log IP:', error);
6899
res.status(500).json({ error: 'Failed to reveal IP' });

server/routes/pilot.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import express from 'express';
22
import { getUserByUsername } from '../db/users.js';
33
import { mainDb } from '../db/connection.js';
4+
import { isAdmin } from '../middleware/admin.js';
45

56
const router = express.Router();
67

@@ -36,6 +37,7 @@ router.get('/:username', async (req, res) => {
3637

3738
const shouldIncludeStats = privacySettings.displayPilotStatsOnProfile;
3839
const shouldIncludeLinkedAccounts = privacySettings.displayLinkedAccountsOnProfile;
40+
const shouldIncludeBackground = privacySettings.displayBackgroundOnProfile;
3941

4042
const profile = {
4143
user: {
@@ -49,11 +51,13 @@ router.get('/:username', async (req, res) => {
4951
vatsim_rating_short: shouldIncludeLinkedAccounts ? userResult.vatsim_rating_short : null,
5052
vatsim_rating_long: shouldIncludeLinkedAccounts ? userResult.vatsim_rating_long : null,
5153
member_since: userResult.created_at,
54+
is_admin: isAdmin(userResult.id),
5255
roles: rolesResult,
5356
role_name: rolesResult[0]?.name || null,
5457
role_description: rolesResult[0]?.description || null,
5558
bio: userResult.settings?.bio ?? '',
5659
statistics: shouldIncludeStats ? (userResult.statistics || {}) : {},
60+
background_image: shouldIncludeBackground ? userResult.settings?.backgroundImage : null,
5761
},
5862
privacySettings,
5963
};

0 commit comments

Comments
 (0)