Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions backend/src/controllers/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/**
* Function handlers for calendar route requests
*/
import { RequestHandler } from "express";
import createHttpError from "http-errors";

import EnrollmentModel from "../models/enrollment";
import SessionModel from "../models/session";

import { Calendar, CalendarSlot } from "./types/calendarTypes";

/**
* Calendar Body: {
*
* studentId: string;
* programId: string;
* calendar: {
* date: Date;
* hours: number;
* session: string;
* }[]
*
* }
*/

/**
* Request handler for getting calendar for student in program
* @param req
* @param res
* @param next
*/
export const getCalendar: RequestHandler = async (req, res, next) => {
try {
const studentId = req.params.studentId;
const programId = req.params.programId;

const enrollment = EnrollmentModel.find({ studentId, programId });
if (!enrollment) {
throw createHttpError(404, "Enrollment not found");
}

// get all sessions with studentId and programId
const sessions = await SessionModel.find({ programId });

const calendar: Calendar = { studentId, programId, calendar: [] };
for (const session of sessions) {
for (const student of session.students) {
if (student.studentId.toString() === studentId) {
let hours = 0;
if (session.marked) {
hours = student.hoursAttended;
}
const date = session.date;
const sessionId = session._id.toString();
calendar.calendar.push({ date, hours, session: sessionId });
}
}
}
console.log(calendar);

return res.status(200).send(calendar);
} catch (error) {
next(error);
}
};

/**
* Handler for editing a day in a calendar
* @param req
* @param res
* @param next
*/
export const editCalendar: RequestHandler = async (req, res, next) => {
try {
const studentId = req.params.studentId;
const programId = req.params.programId;

const enrollment = await EnrollmentModel.findOne({ studentId, programId });
if (!enrollment) {
throw createHttpError(404, "Enrollment not found");
}

const { hours, session } = req.body as CalendarSlot;

const sessionObject = await SessionModel.findById(session);

if (!sessionObject) {
throw createHttpError(404, "Session not found");
}

if (sessionObject.programId.toString() !== programId) {
throw createHttpError(404, "Incorrect program for session");
}

const student = sessionObject.students.find((s) => s.studentId.toString() === studentId);

if (!student) {
throw createHttpError(404, "Student not in session");
}

const prevHoursAttended = student.hoursAttended;
let hoursLeft = enrollment.hoursLeft + prevHoursAttended;

student.hoursAttended = hours;
hoursLeft -= student.hoursAttended;
enrollment.hoursLeft = hoursLeft > 0 ? hoursLeft : 0;

await sessionObject.save();
await enrollment.save();

res.status(200).send("Updated");
} catch (error) {
next(error);
}
};
6 changes: 6 additions & 0 deletions backend/src/controllers/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export const updateProgram: RequestHandler = async (req, res, next) => {
{ $set: { status: "Waitlisted", dateUpdated: Date.now() } },
);

// update days of week when changed
await EnrollmentModel.updateMany(
{ programId: { $eq: programId }, status: { $eq: "Joined" } },
{ $set: { schedule: programData.daysOfWeek } },
);

res.status(200).json(editedProgram);
} catch (error) {
next(error);
Expand Down
12 changes: 12 additions & 0 deletions backend/src/controllers/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@
};

// Call when creating a session from absence
export const createAbsenceSession: RequestHandler = async (req, res, next) => {

Check warning on line 223 in backend/src/controllers/session.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise-returning function provided to variable where a void return was expected
const errors = validationResult(req);

try {
Expand All @@ -247,7 +247,7 @@
};

// Call when attendance is marked
export const updateSession: RequestHandler = async (req, res, next) => {

Check warning on line 250 in backend/src/controllers/session.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise-returning function provided to variable where a void return was expected
const errors = validationResult(req);
try {
validationErrorParser(errors);
Expand All @@ -266,6 +266,18 @@
return res.status(404).json({ message: "No object in database with provided ID" });
}

programData.students.forEach(async (student: StudentInfo) => {

Check warning on line 269 in backend/src/controllers/session.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise returned in function argument where a void return was expected
const enrollment = await EnrollmentModel.findOne({
studentId: student.studentId,
programId: programData.programId,
});
if (enrollment) {
const hours = enrollment.hoursLeft - student.hoursAttended;
enrollment.hoursLeft = hours > 0 ? hours : 0;
await enrollment.save();
}
});

const absentStudents = programData.students.filter((student: StudentInfo) => !student.attended);

const absenceSessions = absentStudents.map((absentStudent) => ({
Expand All @@ -282,7 +294,7 @@
};

// Call when frontpage to load recent sessions is called
export const getRecentSessions: RequestHandler = async (_, res, next) => {

Check warning on line 297 in backend/src/controllers/session.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise-returning function provided to variable where a void return was expected
try {
await createMissingRegularSessions();
await createMissingVaryingSessions();
Expand All @@ -297,7 +309,7 @@
};

// Call when looking to populate absence cards
export const getAbsenceSessions: RequestHandler = async (_, res, next) => {

Check warning on line 312 in backend/src/controllers/session.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise-returning function provided to variable where a void return was expected
try {
const sessions = await AbsenceSessionModel.find();

Expand Down
7 changes: 6 additions & 1 deletion backend/src/controllers/student.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/**
* Functions that process task route requests.
* Functions that process student route requests.
*/

import { RequestHandler } from "express";
Expand All @@ -9,6 +9,7 @@

import EnrollmentModel from "../models/enrollment";
import { Image } from "../models/image";
import ProgramModel from "../models/program";
import ProgressNoteModel from "../models/progressNote";
import StudentModel from "../models/student";
import { Enrollment } from "../types/enrollment";
Expand Down Expand Up @@ -81,6 +82,10 @@
enrollments.map(async (enrollment: Enrollment) => {
const enrollmentExists = await EnrollmentModel.findById(enrollment._id);
const enrollmentBody = { ...enrollment, studentId: new mongoose.Types.ObjectId(studentId) };
const program = await ProgramModel.findById({ _id: enrollment.programId });
if (program?.type === "regular") {
enrollmentBody.schedule = program.daysOfWeek;
}
if (!enrollmentExists) {
return await createEnrollment(enrollmentBody);
} else {
Expand Down Expand Up @@ -117,7 +122,7 @@
const students = await StudentModel.find().populate("enrollments");

// Even though this is a get request, we have verifyAuthToken middleware that sets the accountType in the request body
const { accountType } = req.body;

Check warning on line 125 in backend/src/controllers/student.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

// Ensure that documents that are marked admin are not returned to non-admin users
if (accountType !== "admin") {
Expand All @@ -138,7 +143,7 @@
try {
const errors = validationResult(req);

const { accountType } = req.body;

Check warning on line 146 in backend/src/controllers/student.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

validationErrorParser(errors);

Expand Down
11 changes: 11 additions & 0 deletions backend/src/controllers/types/calendarTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type CalendarSlot = {
date: Date;
hours: number;
session: string;
};

export type Calendar = {
studentId: string;
programId: string;
calendar: CalendarSlot[];
};
2 changes: 2 additions & 0 deletions backend/src/routes/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from "express";

import calendarRouter from "./calendar";
import imageRouter from "./image";
import programRoutes from "./program";
import progressNoteRoutes from "./progressNote";
Expand All @@ -15,6 +16,7 @@ router.use("/student", studentRoutes);
router.use("/program", programRoutes);
router.use("/session", sessionRoutes);
router.use("/progressNote", progressNoteRoutes);
router.use("/calendar", calendarRouter);
router.use("/image", imageRouter);

export default router;
20 changes: 20 additions & 0 deletions backend/src/routes/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Calendar route requests
*/
import express from "express";

import * as CalendarController from "../controllers/calendar";
import { verifyAuthToken } from "../validators/auth";
import * as CalendarValidator from "../validators/calendar";

const router = express.Router();

router.get("/:studentId/:programId", [verifyAuthToken], CalendarController.getCalendar);
router.patch(
"/:studentId/:programId",
[verifyAuthToken],
CalendarValidator.editCalendar,
CalendarController.editCalendar,
);

export default router;
2 changes: 1 addition & 1 deletion backend/src/routes/student.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Task route requests.
* Student route requests.
*/

import express from "express";
Expand Down
33 changes: 33 additions & 0 deletions backend/src/validators/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { body } from "express-validator";

// const makeDateValidator = () =>
// body("date")
// .exists()
// .withMessage("date needed")
// .bail()
// .isDate()
// .bail();

const makeHoursValidator = () =>
body("hours")
.exists()
.withMessage("hours needed")
.bail()
.isNumeric()
.withMessage("needs to be number")
.bail();

const makeSessionValidator = () =>
body("session")
.exists()
.withMessage("sessionId needed")
.bail()
.isString()
.withMessage("needs to be string")
.bail();

export const editCalendar = [
// makeDateValidator(),
makeHoursValidator(),
makeSessionValidator(),
];
46 changes: 46 additions & 0 deletions frontend/src/api/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { GET, PATCH, createAuthHeader, handleAPIError } from "../api/requests";

import type { APIResult } from "../api/requests";

export type CalendarResponse = {
studentId: string;
programId: string;
calendar: {
date: Date;
hours: number;
session: string;
}[];
};

export async function getCalendar(
studentId: string,
programId: string,
firebaseToken: string,
): Promise<APIResult<CalendarResponse>> {
try {
const headers = createAuthHeader(firebaseToken);
const response = await GET(`/calendar/${studentId}/${programId}`, headers);
const json = (await response.json()) as CalendarResponse;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function editCalendar(
studentId: string,
programId: string,
firebaseToken: string,
hours: number,
session: string,
): Promise<APIResult<string>> {
try {
const headers = createAuthHeader(firebaseToken);
const body = { hours, session };
const res = await PATCH(`/calendar/${studentId}/${programId}`, body, headers);
const json = (await res.json()) as string;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}
4 changes: 1 addition & 3 deletions frontend/src/api/programs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { GET, PATCH, POST, handleAPIError } from "../api/requests";
import { GET, PATCH, POST, createAuthHeader, handleAPIError } from "../api/requests";
import { CreateProgramRequest } from "../components/ProgramForm/types";

import { createAuthHeader } from "./progressNotes";

import type { APIResult } from "../api/requests";

export type Program = CreateProgramRequest & { _id: string; dateUpdated: string };
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/api/progressNotes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { APIResult, DELETE, GET, POST, PUT, handleAPIError } from "@/api/requests";
import {
APIResult,
DELETE,
GET,
POST,
PUT,
createAuthHeader,
handleAPIError,
} from "@/api/requests";
import { ProgressNote } from "@/components/ProgressNotes/types";

export const createAuthHeader = (firebaseToken: string) => ({
Authorization: `Bearer ${firebaseToken}`,
});

export async function createProgressNote(
studentId: string,
dateLastUpdated: Date,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,7 @@ export function handleAPIError(error: unknown): APIError {
}
return { success: false, error: `Unknown error; ${String(error)}` };
}

export const createAuthHeader = (firebaseToken: string) => ({
Authorization: `Bearer ${firebaseToken}`,
});
4 changes: 1 addition & 3 deletions frontend/src/api/sessions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { GET, PATCH, POST, handleAPIError } from "../api/requests";

import { createAuthHeader } from "./progressNotes";
import { GET, PATCH, POST, createAuthHeader, handleAPIError } from "../api/requests";

import type { APIResult } from "../api/requests";

Expand Down
4 changes: 1 addition & 3 deletions frontend/src/api/students.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { DELETE, GET, POST, PUT, handleAPIError } from "../api/requests";
import { DELETE, GET, POST, PUT, createAuthHeader, handleAPIError } from "../api/requests";
import { StudentData as CreateStudentRequest } from "../components/StudentForm/types";

import { createAuthHeader } from "./progressNotes";

import type { APIResult } from "../api/requests";

export type Student = CreateStudentRequest & {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
export async function getNotApprovedUsers(firebaseToken: string): Promise<APIResult<User[]>> {
try {
const headers = createAuthHeader(firebaseToken);
console.log(headers);
const response = await GET("/user/not-approved", headers);
const json = (await response.json()) as User[];
return { success: true, data: json };
Expand All @@ -47,7 +46,7 @@
// return { success: true };
return { success: true, data: undefined }; // return APIResult<void> with empty data
} else {
const error = await response.json();

Check warning on line 49 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to approve user");
}
} catch (error) {
Expand All @@ -65,7 +64,7 @@
// return { success: true };
return { success: true, data: undefined }; // return APIResult<void> with empty data
} else {
const error = await response.json();

Check warning on line 67 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to deny user");
}
} catch (error) {
Expand All @@ -85,7 +84,7 @@
// return { success: true };
return { success: true, data: undefined };
} else {
const error = await response.json();

Check warning on line 87 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to delete user");
}
} catch (error) {
Expand Down
Loading
Loading