From ca152f7955b5e5db374d362861a073a8df85ef3c Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Sun, 16 Nov 2025 00:48:30 -0500 Subject: [PATCH] added service with base function to get course and event attendees --- api/src/index.js | 3 ++ api/src/routes/course.ts | 43 ++++++++++++++++++++++++++ api/src/services/CourseSerivce.ts | 51 +++++++++++++++++++++++++++++++ shared/tsconfig.json | 17 +++++++++++ shared/types/course.ts | 49 +++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 api/src/routes/course.ts create mode 100644 api/src/services/CourseSerivce.ts create mode 100644 shared/tsconfig.json create mode 100644 shared/types/course.ts diff --git a/api/src/index.js b/api/src/index.js index cb7629c..59c1381 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -45,6 +45,7 @@ const loaHandler = require('./routes/loa') const { status, memberStatus } = require('./routes/statuses') const authRouter = require('./routes/auth') const { roles, memberRoles } = require('./routes/roles'); +const { courseRouter, eventRouter } = require('./routes/course'); const morgan = require('morgan'); app.use('/application', applicationsRouter); @@ -56,6 +57,8 @@ app.use('/status', status) app.use('/memberStatus', memberStatus) app.use('/roles', roles) app.use('/memberRoles', memberRoles) +app.use('/course', courseRouter) +app.use('/courseEvent', eventRouter) app.use('/', authRouter) app.get('/ping', (req, res) => { diff --git a/api/src/routes/course.ts b/api/src/routes/course.ts new file mode 100644 index 0000000..527abf9 --- /dev/null +++ b/api/src/routes/course.ts @@ -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; \ No newline at end of file diff --git a/api/src/services/CourseSerivce.ts b/api/src/services/CourseSerivce.ts new file mode 100644 index 0000000..c24d9b9 --- /dev/null +++ b/api/src/services/CourseSerivce.ts @@ -0,0 +1,51 @@ +import pool from "../db" +import { Course, CourseAttendee, RawAttendeeRow } from "@app/shared/types/course" + +export async function getAllCourses(): Promise { + 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 { + 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)) +} \ No newline at end of file diff --git a/shared/tsconfig.json b/shared/tsconfig.json new file mode 100644 index 0000000..3f94d22 --- /dev/null +++ b/shared/tsconfig.json @@ -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"] +} diff --git a/shared/types/course.ts b/shared/types/course.ts new file mode 100644 index 0000000..13f77f2 --- /dev/null +++ b/shared/types/course.ts @@ -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; +} \ No newline at end of file