added service with base function to get course and event attendees
This commit is contained in:
@@ -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
43
api/src/routes/course.ts
Normal 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;
|
||||||
51
api/src/services/CourseSerivce.ts
Normal file
51
api/src/services/CourseSerivce.ts
Normal 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
17
shared/tsconfig.json
Normal 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
49
shared/types/course.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user