Skip to content

Commit 9f0cab7

Browse files
authored
Merge pull request #94 from CoolerMinecraft/preview
Feedback
2 parents c8bb8d3 + 35598e0 commit 9f0cab7

20 files changed

Lines changed: 1206 additions & 39 deletions

File tree

server/db/admin.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { sql } from 'kysely';
55
import { redisConnection } from './connection.js';
66
import { decrypt } from '../utils/encryption.js';
77
import { getAdminIds, isAdmin } from '../middleware/admin.js';
8-
import { getActiveUsersForSession } from "../websockets/sessionUsersWebsocket.js"; // Update import
8+
import { getActiveUsersForSession } from "../websockets/sessionUsersWebsocket.js";
99
import { getUserRoles } from "./roles.js";
1010

1111
type RawUser = {
@@ -395,10 +395,11 @@ export async function getAllUsers(page = 1, limit = 50, search = '', filterAdmin
395395
}
396396
}
397397

398-
export async function getAdminSessions() {
398+
export async function getAdminSessions(page = 1, limit = 100, search = '') {
399399
try {
400-
// Get all sessions with user info
401-
const sessions = await mainDb
400+
const offset = (page - 1) * limit;
401+
402+
let query = mainDb
402403
.selectFrom('sessions as s')
403404
.leftJoin('users as u', 's.created_by', 'u.id')
404405
.select([
@@ -413,10 +414,28 @@ export async function getAdminSessions() {
413414
'u.discriminator',
414415
'u.avatar'
415416
])
416-
.orderBy('s.created_at', 'desc')
417-
.execute();
417+
.orderBy('s.created_at', 'desc');
418+
419+
if (search && search.trim()) {
420+
const searchTerm = `%${search.trim()}%`;
421+
query = query.where((eb) =>
422+
eb.or([
423+
eb('s.session_id', 'ilike', searchTerm),
424+
eb('s.airport_icao', 'ilike', searchTerm),
425+
eb('u.username', 'ilike', searchTerm),
426+
eb('s.created_by', 'ilike', searchTerm)
427+
])
428+
);
429+
}
430+
431+
const countQuery = query.clearSelect().clearOrderBy().select(({ fn }) => fn.countAll().as('count'));
432+
const countResult = await countQuery.executeTakeFirst();
433+
const total = Number(countResult?.count) || 0;
434+
const pages = Math.ceil(total / limit);
435+
436+
const sessions = await query.limit(limit).offset(offset).execute();
418437

419-
const sessionsWithFlights = await Promise.all(
438+
const sessionsWithDetails = await Promise.all(
420439
sessions.map(async (session) => {
421440
let flight_count = 0;
422441
try {
@@ -439,14 +458,21 @@ export async function getAdminSessions() {
439458
})
440459
);
441460

442-
return sessionsWithFlights;
461+
return {
462+
sessions: sessionsWithDetails,
463+
pagination: {
464+
page,
465+
limit,
466+
total,
467+
pages
468+
}
469+
};
443470
} catch (error) {
444471
console.error('Error fetching admin sessions:', error);
445472
throw error;
446473
}
447474
}
448475

449-
450476
export async function syncUserSessionCounts() {
451477
try {
452478
const sessionCounts = await mainDb

server/db/feedback.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { mainDb } from "./connection.js";
2+
import { sql } from "kysely";
3+
4+
export async function getAllFeedback() {
5+
try {
6+
const feedback = await mainDb
7+
.selectFrom('feedback')
8+
.leftJoin('users', 'feedback.user_id', 'users.id')
9+
.select([
10+
'feedback.id',
11+
'feedback.user_id',
12+
'feedback.username',
13+
'feedback.rating',
14+
'feedback.comment',
15+
'feedback.created_at',
16+
'feedback.updated_at',
17+
'users.avatar',
18+
])
19+
.orderBy('feedback.created_at', 'desc')
20+
.execute();
21+
return feedback;
22+
} catch (error) {
23+
console.error('Error fetching feedback:', error);
24+
throw error;
25+
}
26+
}
27+
28+
export async function addFeedback({
29+
userId,
30+
username,
31+
rating,
32+
comment,
33+
}: {
34+
userId: string;
35+
username: string;
36+
rating: number;
37+
comment?: string;
38+
}) {
39+
try {
40+
const [feedback] = await mainDb
41+
.insertInto('feedback')
42+
.values({
43+
id: sql`DEFAULT`,
44+
user_id: userId,
45+
username,
46+
rating,
47+
comment,
48+
})
49+
.returningAll()
50+
.execute();
51+
return feedback;
52+
} catch (error) {
53+
console.error('Error adding feedback:', error);
54+
throw error;
55+
}
56+
}
57+
58+
export async function deleteFeedback(id: number) {
59+
try {
60+
const [feedback] = await mainDb
61+
.deleteFrom('feedback')
62+
.where('id', '=', id)
63+
.returningAll()
64+
.execute();
65+
return feedback;
66+
} catch (error) {
67+
console.error('Error deleting feedback:', error);
68+
throw error;
69+
}
70+
}
71+
72+
export async function getFeedbackStats() {
73+
try {
74+
const stats = await mainDb
75+
.selectFrom('feedback')
76+
.select([
77+
sql<number>`COUNT(*)`.as('total_feedback'),
78+
sql<number>`AVG(rating)`.as('average_rating'),
79+
sql<number>`COUNT(CASE WHEN rating = 5 THEN 1 END)`.as('five_star'),
80+
sql<number>`COUNT(CASE WHEN rating = 4 THEN 1 END)`.as('four_star'),
81+
sql<number>`COUNT(CASE WHEN rating = 3 THEN 1 END)`.as('three_star'),
82+
sql<number>`COUNT(CASE WHEN rating = 2 THEN 1 END)`.as('two_star'),
83+
sql<number>`COUNT(CASE WHEN rating = 1 THEN 1 END)`.as('one_star'),
84+
])
85+
.executeTakeFirst();
86+
return stats;
87+
} catch (error) {
88+
console.error('Error fetching feedback stats:', error);
89+
throw error;
90+
}
91+
}

server/db/schemas.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ export async function createMainTables() {
167167
.addColumn('ip_address', 'varchar(255)')
168168
.addColumn('timestamp', 'timestamp', (col) => col.defaultTo('now()'))
169169
.execute();
170+
171+
// feedback
172+
await mainDb.schema
173+
.createTable('feedback')
174+
.ifNotExists()
175+
.addColumn('id', 'serial', (col) => col.primaryKey())
176+
.addColumn('user_id', 'varchar(255)', (col) => col.notNull())
177+
.addColumn('username', 'varchar(255)', (col) => col.notNull())
178+
.addColumn('rating', 'integer', (col) => col.notNull())
179+
.addColumn('comment', 'text')
180+
.addColumn('created_at', 'timestamp', (col) => col.defaultTo('now()'))
181+
.addColumn('updated_at', 'timestamp', (col) => col.defaultTo('now()'))
182+
.execute();
170183
}
171184

172185
// Helper to create a dynamic flights table for a session

server/db/types/connection/MainDatabase.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ChatReportsTable } from "./main/ChatReportsTable";
1414
import { UpdateModalsTable } from "./main/UpdateModalsTable";
1515
import { FlightLogsTable } from "./main/FlightLogsTable";
1616
import { GlobalHolidaySettingsTable } from "./main/GlobalHolidaySettingsTable";
17+
import { FeedbackTable } from "./main/FeedbackTable";
1718

1819
export interface MainDatabase {
1920
app_settings: AppSettingsTable;
@@ -32,4 +33,5 @@ export interface MainDatabase {
3233
update_modals: UpdateModalsTable;
3334
flight_logs: FlightLogsTable;
3435
global_holiday_settings: GlobalHolidaySettingsTable;
36+
feedback: FeedbackTable;
3537
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface FeedbackTable {
2+
id: number;
3+
user_id: string;
4+
username: string;
5+
rating: number;
6+
comment?: string;
7+
created_at?: Date;
8+
updated_at?: Date;
9+
}

server/routes/admin/feedback.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import express from 'express';
2+
import { createAuditLogger } from '../../middleware/auditLogger.js';
3+
import { logAdminAction } from '../../db/audit.js';
4+
import {
5+
getAllFeedback,
6+
deleteFeedback,
7+
getFeedbackStats
8+
} from '../../db/feedback.js';
9+
import { getClientIp } from '../../utils/getIpAddress.js';
10+
11+
const router = express.Router();
12+
13+
// GET: /api/admin/feedback - Get all feedback
14+
router.get('/', createAuditLogger('ADMIN_FEEDBACK_ACCESSED'), async (req, res) => {
15+
try {
16+
const feedback = await getAllFeedback();
17+
res.json(feedback);
18+
} catch (error) {
19+
console.error('Error fetching feedback:', error);
20+
res.status(500).json({ error: 'Failed to fetch feedback' });
21+
}
22+
});
23+
24+
// GET: /api/admin/feedback/stats - Get feedback statistics
25+
router.get('/stats', async (req, res) => {
26+
try {
27+
const stats = await getFeedbackStats();
28+
res.json(stats);
29+
} catch (error) {
30+
console.error('Error fetching feedback stats:', error);
31+
res.status(500).json({ error: 'Failed to fetch feedback stats' });
32+
}
33+
});
34+
35+
// DELETE: /api/admin/feedback/:id - Delete feedback
36+
router.delete('/:id', async (req, res) => {
37+
try {
38+
const { id } = req.params;
39+
const numericId = Number(id);
40+
41+
const feedback = await deleteFeedback(numericId);
42+
43+
if (req.user?.userId) {
44+
const ip = getClientIp(req);
45+
await logAdminAction({
46+
adminId: req.user.userId,
47+
adminUsername: req.user.username || 'Unknown',
48+
actionType: 'FEEDBACK_DELETED',
49+
ipAddress: Array.isArray(ip) ? ip.join(', ') : ip,
50+
details: { message: `Deleted feedback with ID: ${numericId}` },
51+
});
52+
}
53+
54+
res.json(feedback);
55+
} catch (error) {
56+
console.error('Error deleting feedback:', error);
57+
res.status(500).json({ error: 'Failed to delete feedback' });
58+
}
59+
});
60+
61+
export default router;

server/routes/admin/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import rolesRouter from './roles.js';
2020
import chatReportsRouter from './chat-reports.js';
2121
import updateModalsRouter from './updateModals.js';
2222
import flightLogsRouter from './flight-logs.js';
23+
import feedbackRouter from './feedback.js';
2324

2425
const router = express.Router();
2526

@@ -36,6 +37,7 @@ router.use('/roles', rolesRouter);
3637
router.use('/chat-reports', chatReportsRouter);
3738
router.use('/update-modals', updateModalsRouter);
3839
router.use('/flight-logs', flightLogsRouter);
40+
router.use('/feedback', feedbackRouter);
3941

4042
// GET: /api/admin/statistics - Get dashboard statistics
4143
router.get('/statistics', requirePermission('admin'), async (req, res) => {

server/routes/admin/sessions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ router.use(requirePermission('sessions'));
1111
// GET: /api/admin/sessions - Get all sessions with details
1212
router.get('/', createAuditLogger('ADMIN_SESSIONS_ACCESSED'), async (req, res) => {
1313
try {
14-
const sessions = await getAdminSessions();
15-
res.json(sessions);
14+
const page = parseInt(req.query.page as string) || 1;
15+
const limit = Math.min(parseInt(req.query.limit as string) || 100, 100);
16+
const search = (req.query.search as string) || '';
17+
const result = await getAdminSessions(page, limit, search);
18+
res.json(result);
1619
} catch (error) {
1720
console.error('Error fetching sessions:', error);
1821
res.status(500).json({ error: 'Failed to fetch sessions' });

server/routes/feedback.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import express from 'express';
2+
import { addFeedback } from '../db/feedback.js';
3+
import requireAuth from '../middleware/auth.js';
4+
5+
const router = express.Router();
6+
7+
// POST: /api/feedback - Submit feedback
8+
router.post('/', requireAuth, async (req, res) => {
9+
try {
10+
const { rating, comment } = req.body;
11+
12+
if (!rating || rating < 1 || rating > 5) {
13+
return res.status(400).json({ error: 'Rating must be between 1 and 5' });
14+
}
15+
16+
const feedback = await addFeedback({
17+
userId: req.user!.userId || '',
18+
username: req.user!.username || '',
19+
rating: Number(rating),
20+
comment: comment?.trim() || undefined,
21+
});
22+
23+
res.json(feedback);
24+
} catch (error) {
25+
console.error('Error submitting feedback:', error);
26+
res.status(500).json({ error: 'Failed to submit feedback' });
27+
}
28+
});
29+
30+
export default router;

server/routes/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import pilotRouter from './pilot.js';
1212
import adminRouter from './admin/index.js';
1313
import updateModalRouter from './updateModal.js';
1414
import versionRouter from './version.js';
15+
import feedbackRouter from './feedback.js';
1516

1617
const router = express.Router();
1718

@@ -27,5 +28,6 @@ router.use('/pilot', pilotRouter);
2728
router.use('/admin', adminRouter);
2829
router.use('/update-modal', updateModalRouter);
2930
router.use('/version', versionRouter);
31+
router.use('/feedback', feedbackRouter);
3032

3133
export default router;

0 commit comments

Comments
 (0)