Skip to content

Commit

Permalink
Admin class management (backend) (#44)
Browse files Browse the repository at this point in the history
* add update and delete endpoints to class module

* add a delete endpoint to the class schedules module

* photo pipelining, switch to using mysql2/promise for better transactions

* fix casing

* utilize transactions, generateDB models

* add logging

* rename DB models acccordingly

* double check mime type on backend

* bug fixes in class module

* use dynamic values clause in schedule insertion

* add shift_id column to shifts table

* fix insertions for volunteer and availability modules

* implement add, delete and update endpoints in shift module

* add checked_in field to addShift

* cleanup schedule model

* fix updateSchedulesByClassId

---------

Co-authored-by: theosiemensrhodes <[email protected]>
  • Loading branch information
jansm04 and theosiemensrhodes authored Jan 25, 2025
1 parent 1768b6c commit c0495b6
Show file tree
Hide file tree
Showing 16 changed files with 398 additions and 106 deletions.
9 changes: 4 additions & 5 deletions backend/src/common/generated.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RowDataPacket } from "mysql2";

import { RowDataPacket } from "mysql2";

/*
* This file was generated by a tool.
* Rerun sql-ts to regenerate this file.
Expand Down Expand Up @@ -63,17 +63,16 @@ export interface ScheduleDB extends RowDataPacket {
}
export interface ShiftCoverageRequestDB extends RowDataPacket {
'covered_by'?: string | null;
'fk_schedule_id': number;
'fk_volunteer_id': string;
'fk_shift_id': number;
'request_id'?: number;
'shift_date': string;
}
export interface ShiftDB extends RowDataPacket {
'checked_in'?: any | null;
'duration': number;
'fk_schedule_id': number;
'fk_volunteer_id': string;
'shift_date': string;
'shift_id': number;
}
export interface UserDB extends RowDataPacket {
'created_at'?: Date | null;
Expand Down
64 changes: 62 additions & 2 deletions backend/src/controllers/classController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function getAllClasses(req: Request, res: Response) {
}

async function addClass(req: Request, res: Response) {
const { fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category } = req.body;
const { fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category, subcategory } = req.body;

if (!fk_instructor_id || !class_name || !start_date || !end_date) {
return res.status(400).json({
Expand All @@ -33,6 +33,7 @@ async function addClass(req: Request, res: Response) {
start_date: start_date,
end_date: end_date,
category: category,
subcategory: subcategory
} as ClassDB;

const result = await classesModel.addClass(newClass);
Expand All @@ -46,6 +47,7 @@ async function addClass(req: Request, res: Response) {
start_date,
end_date,
category,
subcategory
};

return res.status(201).json({
Expand All @@ -59,6 +61,63 @@ async function addClass(req: Request, res: Response) {
}
}

async function updateClass(req: Request, res: Response) {
const class_id = Number(req.params.class_id);

const { fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category, subcategory } = req.body;

if (!class_id) {
return res.status(400).json({
error: 'Missing required field: class_id is required.'
});
}

if (!fk_instructor_id || !class_name || !start_date || !end_date) {
return res.status(400).json({
error: 'Missing required fields: fk_instructor_id, class_name, start_date, and end_date are required.'
});
}

try {
const updatedClass = {
fk_instructor_id,
class_name,
instructions: instructions,
zoom_link: zoom_link,
start_date: start_date,
end_date: end_date,
category: category,
subcategory: subcategory
} as ClassDB;

const result = await classesModel.updateClass(class_id, updatedClass);
return res.status(200).json(result);
} catch (error: any) {
return res.status(error.status ?? 500).json({
error: error.message,
});
}
}

async function deleteClass(req: Request, res: Response) {
const class_id = Number(req.params.class_id);

if (!class_id) {
return res.status(400).json({
error: 'Missing required field: class_id is required.'
});
}

try {
const result = await classesModel.deleteClass(class_id);
return res.status(200).json(result);
} catch (error: any) {
return res.status(error.status).json({
error: error.message,
});
}
}

async function getClassesByDay(req: Request, res: Response) {
const day = req.params.day;
const classModel = new ClassesModel();
Expand Down Expand Up @@ -137,6 +196,7 @@ async function uploadClassImage(req: Request, res: Response) {

export {
addClass, getAllClasses, getClassById,
getClassesByDay, uploadClassImage
getClassesByDay, uploadClassImage,
updateClass, deleteClass
};

42 changes: 37 additions & 5 deletions backend/src/controllers/scheduleController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function getSchedules(req: Request, res: Response) {
}

async function getSchedulesByClassId(req: Request, res: Response) {
const { class_id } = req.params;
const class_id = Number(req.params.class_id);

if (!class_id) {
return res.status(400).json({
Expand Down Expand Up @@ -55,7 +55,7 @@ function isValidSchedules(data: any): data is ScheduleDB[] {
}

async function setSchedulesByClassId(req: Request, res: Response) {
const { class_id } = req.params;
const class_id = Number(req.params.class_id);
const schedules: ScheduleDB[] = req.body;

if (!class_id) {
Expand All @@ -80,9 +80,8 @@ async function setSchedulesByClassId(req: Request, res: Response) {
}
}


async function updateSchedulesByClassId(req: Request, res: Response) {
const { class_id } = req.params;
const class_id = Number(req.params.class_id);
const schedules: ScheduleDB[] = req.body;

if (!class_id) {
Expand All @@ -107,9 +106,42 @@ async function updateSchedulesByClassId(req: Request, res: Response) {
}
}

async function deleteSchedules(req: Request, res: Response) {
const class_id = Number(req.params.class_id);
const { schedule_ids } = req.body;

if (!class_id) {
return res.status(400).json({
error: "Missing required parameter: 'class_id'"
});
}

if (!schedule_ids) {
return res.status(400).json({
error: "Missing required field: 'schedule_ids'"
});
}

if (!Array.isArray(schedule_ids)) {
return res.status(400).json({
error: "Invalid field: 'schedule_ids' should be an array of values"
});
}

try {
const result = await scheduleModel.deleteSchedulesByScheduleId(class_id, schedule_ids);
res.status(200).json(result);
} catch (error) {
return res.status(500).json({
error: `Internal server error: ${JSON.stringify(error)}`
});
}
}

export {
getSchedules,
getSchedulesByClassId,
setSchedulesByClassId,
updateSchedulesByClassId
updateSchedulesByClassId,
deleteSchedules
};
77 changes: 76 additions & 1 deletion backend/src/controllers/shiftController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,82 @@ async function requestToCoverShift(req: Request, res: Response) {
}
}

async function addShift(req: Request, res: Response) {
const shift: ShiftDB = req.body;

if (!shift.shift_date) {
return res.status(400).json({
error: "Missing required field: 'shift_date'.",
});
} else if (!shift.fk_schedule_id) {
return res.status(400).json({
error: "Missing required field: 'fk_schedule_id'.",
});
} else if (!shift.duration) {
return res.status(400).json({
error: "Missing required field: 'duration'.",
});
}

try {
const request = await shiftModel.addShift(shift);
const addedShift = {
shift_id: request.insertId,
fk_volunteer_id: shift.fk_volunteer_id ?? null,
fk_schedule_id: shift.fk_schedule_id,
shift_date: shift.shift_date,
duration: shift.duration,
checked_in: shift.checked_in
};

res.status(200).json(addedShift);
} catch (error: any) {
return res.status(error.status ?? 500).json({
error: error.message
});
}
}

async function updateShift(req: Request, res: Response) {
const shift_id = Number(req.params.shift_id);
const shift: ShiftDB = req.body;

if (!shift_id) {
return res.status(400).json({
error: "Missing required parameter: 'shift_id'.",
});
}

try {
const request = await shiftModel.updateShift(shift_id, shift);
res.status(200).json(request);
} catch (error: any) {
return res.status(error.status ?? 500).json({
error: error.message
});
}
}

async function deleteShift(req: Request, res: Response) {
const shift_id = Number(req.params.shift_id);

if (!shift_id) {
return res.status(400).json({
error: "Missing required parameter: 'shift_id'.",
});
}

try {
const request = await shiftModel.deleteShift(shift_id);
res.status(200).json(request);
} catch (error: any) {
return res.status(error.status ?? 500).json({
error: error.message
});
}
}

export {
getShiftInfo, getShiftsByDate, getShiftsByVolunteerId, getShiftsByVolunteerIdAndMonth,
requestToCoverShift
requestToCoverShift, addShift, updateShift, deleteShift
};
5 changes: 4 additions & 1 deletion backend/src/models/availabilityModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export default class AvailabilityModel {
async setAvailabilityByVolunteerId(volunteer_id: string, availabilities: AvailabilityDB[], transaction?: PoolConnection): Promise<ResultSetHeader> {
const connection = transaction ?? connectionPool;

const query = `INSERT INTO availability (fk_volunteer_id, day, start_time, end_time) VALUES ?`;
const valuesCaluse = availabilities
.map(() => '(?)')
.join(", ");
const query = `INSERT INTO availability (fk_volunteer_id, day, start_time, end_time) VALUES ${valuesCaluse}`;
const values = availabilities.map((availability) => [
volunteer_id,
availability.day,
Expand Down
10 changes: 5 additions & 5 deletions backend/src/models/classModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ export default class ClassesModel {

async addClass(newClass: ClassDB): Promise<ResultSetHeader> {
const query = `INSERT INTO class
(fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category)
VALUES (?, ?, ?, ?, ?, ?, ?)`;
(fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category, subcategory)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;

const { fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category } = newClass;
const values = [fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category];
const { fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category, subcategory } = newClass;
const values = [fk_instructor_id, class_name, instructions, zoom_link, start_date, end_date, category, subcategory];

const [results, _] = await connectionPool.query<ResultSetHeader>(query, values);

Expand All @@ -117,7 +117,7 @@ export default class ClassesModel {
const setClause = Object.keys(classData)
.map((key) => `${key} = ?`)
.join(", ");
const query = `UPDATE class SET ${setClause} WHERE volunteer_id = ?`;
const query = `UPDATE class SET ${setClause} WHERE class_id = ?`;
const values = [...Object.values(classData), class_id];

const [results, _] = await connection.query<ResultSetHeader>(query, values);
Expand Down
17 changes: 10 additions & 7 deletions backend/src/models/scheduleModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class ScheduleModel {
return results;
}

async getSchedulesByClassId(classId: string): Promise<ScheduleDB[]> {
async getSchedulesByClassId(classId: number): Promise<ScheduleDB[]> {
const query = "SELECT * FROM schedule WHERE fk_class_id = ?";
const values = [classId];

Expand All @@ -21,10 +21,13 @@ export default class ScheduleModel {
return results;
}

async setSchedulesByClassId(classId: string, scheduleItems: ScheduleDB[], transaction?: PoolConnection): Promise<any> {
async setSchedulesByClassId(classId: number, scheduleItems: ScheduleDB[], transaction?: PoolConnection): Promise<any> {
const connection = transaction ?? connectionPool;

const query = `INSERT INTO schedule (fk_class_id, day, start_time, end_time) VALUES ?`;

const valuesCaluse = scheduleItems
.map(() => '(?)')
.join(", ");
const query = `INSERT INTO schedule (fk_class_id, day, start_time, end_time) VALUES ${valuesCaluse}`;
const values = scheduleItems.map((schedule) => [
classId,
schedule.day,
Expand All @@ -36,8 +39,8 @@ export default class ScheduleModel {

return results;
}

async deleteSchedulesByScheduleId(classId: string, scheduleIds: number[], transaction?: PoolConnection): Promise<any> {
async deleteSchedulesByScheduleId(classId: number, scheduleIds: number[], transaction?: PoolConnection): Promise<any> {
const connection = transaction ?? connectionPool;

const query = `DELETE FROM schedule WHERE fk_class_id = ? AND schedule_id IN (?)`;
Expand All @@ -48,7 +51,7 @@ export default class ScheduleModel {
return results;
}

async updateSchedulesByClassId(classId: string, newSchedules: ScheduleDB[]): Promise<void> {
async updateSchedulesByClassId(classId: number, newSchedules: ScheduleDB[]): Promise<void> {
const transaction = await connectionPool.getConnection();

try {
Expand Down
Loading

0 comments on commit c0495b6

Please sign in to comment.