added service with base function to get course and event attendees

This commit is contained in:
2025-11-16 00:48:30 -05:00
parent 9a65784f8b
commit ca152f7955
5 changed files with 163 additions and 0 deletions

View File

@@ -45,6 +45,7 @@ const loaHandler = require('./routes/loa')
const { status, memberStatus } = require('./routes/statuses') const { status, memberStatus } = require('./routes/statuses')
const authRouter = require('./routes/auth') const authRouter = require('./routes/auth')
const { roles, memberRoles } = require('./routes/roles'); const { roles, memberRoles } = require('./routes/roles');
const { courseRouter, eventRouter } = require('./routes/course');
const morgan = require('morgan'); const morgan = require('morgan');
app.use('/application', applicationsRouter); app.use('/application', applicationsRouter);
@@ -56,6 +57,8 @@ app.use('/status', status)
app.use('/memberStatus', memberStatus) app.use('/memberStatus', memberStatus)
app.use('/roles', roles) app.use('/roles', roles)
app.use('/memberRoles', memberRoles) app.use('/memberRoles', memberRoles)
app.use('/course', courseRouter)
app.use('/courseEvent', eventRouter)
app.use('/', authRouter) app.use('/', authRouter)
app.get('/ping', (req, res) => { app.get('/ping', (req, res) => {

43
api/src/routes/course.ts Normal file
View File

@@ -0,0 +1,43 @@
import { CourseAttendee } from "@app/shared/types/course";
import { getAllCourses, getCourseAttendees } from "../services/CourseSerivce";
import { Request, Response, Router } from "express";
const courseRouter = Router();
const eventRouter = Router();
courseRouter.get('/', async (req, res) => {
try {
const courses = await getAllCourses();
res.status(200).json(courses);
} catch (err) {
console.error('failed to fetch courses', err);
res.status(500).json('failed to fetch courses\n' + err);
}
})
eventRouter.get('/attendees/:id', async (req: Request, res: Response) => {
try {
const attendees: CourseAttendee[] = await getCourseAttendees(Number(req.params.id));
res.status(200).json(attendees);
} catch (err) {
console.error('failed to fetch attendees', err);
res.status(500).json("failed to fetch attendees\n" + err);
}
})
// //insert a new latest rank for a user
// ur.post('/', async (req, res) => {
// try {
// const change = req.body?.change;
// await insertMemberRank(change.member_id, change.rank_id, change.date);
// res.sendStatus(201);
// } catch (err) {
// console.error('Insert failed:', err);
// res.status(500).json({ error: 'Failed to update ranks' });
// }
// });
module.exports.courseRouter = courseRouter;
module.exports.eventRouter = eventRouter;

View File

@@ -0,0 +1,51 @@
import pool from "../db"
import { Course, CourseAttendee, RawAttendeeRow } from "@app/shared/types/course"
export async function getAllCourses(): Promise<Course[]> {
const sql = "SELECT * FROM courses WHERE deleted = false;"
const res = await pool.query(sql);
return res;
}
function buildAttendee(row: RawAttendeeRow): CourseAttendee {
return {
passed: !!row.passed,
attendee_id: row.attendee_id,
course_event_id: row.course_event_id,
created_at: row.created_at,
updated_at: row.updated_at,
remarks: row.remarks,
attendee_role_id: row.attendee_role_id,
role: row.role_id
? {
id: row.role_id,
name: row.role_name,
description: row.role_description,
deleted: !!row.role_deleted,
created_at: row.role_created_at,
updated_at: row.role_updated_at,
}
: null
};
}
export async function getCourseAttendees(id: number): Promise<CourseAttendee[]> {
const sql = `SELECT
ca.*,
ar.id AS role_id,
ar.name AS role_name,
ar.description AS role_description,
ar.deleted AS role_deleted,
ar.created_at AS role_created_at,
ar.updated_at AS role_updated_at
FROM course_attendees ca
LEFT JOIN course_attendee_roles ar ON ar.id = ca.attendee_role_id
WHERE ca.course_event_id = ?;`;
const res:RawAttendeeRow[] = await pool.query(sql, [id]);
return res.map((row) => buildAttendee(row))
}

17
shared/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true
},
"include": ["./**/*.ts"]
}

49
shared/types/course.ts Normal file
View File

@@ -0,0 +1,49 @@
export interface Course {
id: number;
name: string;
short_name: string;
category: string;
description?: string | null;
image_url?: string | null;
created_at: string;
updated_at: string;
deleted?: number | boolean;
prereq_id?: number | null;
}
export interface CourseAttendee {
passed: boolean; // tinyint(1)
attendee_id: number; // PK
course_event_id: number; // PK
attendee_role_id: number | null;
role: CourseAttendeeRole | null;
created_at: string; // datetime → ISO string
updated_at: string; // datetime → ISO string
remarks: string | null;
}
export interface CourseAttendeeRole {
id: number; // PK, auto-increment
name: string | null; // varchar(50), unique, nullable
description: string | null; // text
created_at: string | null; // datetime (nullable)
updated_at: string | null; // datetime (nullable)
deleted: boolean; // tinyint(4)
}
export interface RawAttendeeRow {
passed: number;
attendee_id: number;
course_event_id: number;
attendee_role_id: number | null;
created_at: string;
updated_at: string;
remarks: string | null;
role_id: number | null;
role_name: string | null;
role_description: string | null;
role_deleted: number | null;
role_created_at: string | null;
role_updated_at: string | null;
}