Skip to content

Commit

Permalink
feature/lisasiliu/view-student-profile-v2 (#103)
Browse files Browse the repository at this point in the history
* created file to put ui code in

* finished hardcoded notification table

* updated backend to match main

* student profile basic shell text

* finished rest of ui

* feat: starting form changes

* finished up student page

* updating with main

* feat: set up useFieldArray, refactor

* feat: working student form

* Removed conflicting color

* Implemented view profile button

* End-to-end retrieve student info

* Visualize Student Data

* Deletion route

* Printing Student Profile

* Remove debug code

* Delete Student Dialog

* Student Deletion

* Student Profile Picture Render

* Get Student Route Verification

* Squashed commit of the following:

commit 058c01b
Author: Victor Siu <[email protected]>
Date:   Mon Jun 17 19:25:24 2024 -0700

    Feature/vs2961/attendance dashboard (#106)

    * Add backend query code

    * Add frontend components

    * Add mobile

    * Add attendance dashboard code

    * Add code for attendanceTable

    * Fix linting

    * Make varying sessions work

    * Fix linting

    * Fix types

    * fix linting

    * Prevent buttons from being clicked multiple times

commit 525fbe2
Author: adhi0331 <[email protected]>
Date:   Sun Jun 16 15:29:45 2024 -0500

    fixed minor bug

commit a16cf00
Author: Aaron Chan <[email protected]>
Date:   Sun Jun 16 13:15:56 2024 -0700

    Account Archival Frontend & Backend (#107)

    * feat: add create program notes backend

    * fix: THead unique key prop warning

    * feat: add edited by field to notes

    * fix: fix styling issues

    * feat: add edit and delete functionality to progress notes and change userId to uid for backend

    * fix: fix calendar styling

    * feat: add download progress notes functionality using react-pdf

    * feat: add mobile responsiveness

    * feat: create program context provider shared across Home, Programs, and Notes page

    * fix: fix styling to be consistent across pages

    * feat: add progress note filtering and add loading spinner to pages

    * feat: add account type authorization checks on frontend and backend

    * refactor: refactor notes logic and fix styling

    * feat: add logic to check for no students and refactor filter

    * feat: add shadows using overflow clip

    * fix: fix styling for search filter

    * fix: fix small style inconsistencies

    * fix: update poppins font url to use https

    * fix: fix modal close button styling to be more consistent

    * fix: fix escape key overriding dialog close

    * Squashed commit of the following:

    commit 0b133b8
    Author: Michael Sullivan <[email protected]>
    Date:   Tue May 14 09:09:16 2024 -0700

        Feature/mraysu/program form v2 (#100)

        * Update Backend Program Schema

        * V2 UI

        * Disabled Editing Program Type

        * Frontend-backend integration

        * Lint fixes

        ---------

        Co-authored-by: mraysu <[email protected]>
        Co-authored-by: Adhithya Ananthan <[email protected]>

    commit e17b509
    Author: parth4apple <[email protected]>
    Date:   Tue May 14 09:01:15 2024 -0700

        Student and Enrollment Schema modifications (#101)

        * feat: initial schema

        * feat: edit routes

        * feat: test and fix routes

    * feat: add frontend and backend for account archival

    * feat: ensure ui updates and refactor code

    * Squashed merge with main

    * fix: update type of VerifyUser to be same as User

    * fix: fix user type error

commit fe61637
Author: Andrew Pu <[email protected]>
Date:   Sun Jun 16 13:00:01 2024 -0700

    Feature/andrewzpu/program profile pages (#105)

    * added backend route for getting single program

    * added separate pages for each program

    * Added program edit popup and "no programs" message

    * Fixed add button display

    * Reorganized ProgramFormButton to take take any component

    * Removed start and end date from card

    * Connected Program Card to Program Profile Pages

    * Set up basic framework of Program Profile Page

    * Added feature where clicking anywhere else will close the edit popup button

    * Updated popup and redirect functionality of program profiles

    * Finished full screen view of program profile

    * Fixed hourly pay references

    * Added backend route to get program enrollments

    * Connected enrollments route to program profile page

    * Updated student count messages on cards and program profile

    * Added Student Names to Enrollment Table

    * Fixed mobile view

    * Fixed minor details

    * Minor Table Change

    * finish merge

    * fixed try catch error

    ---------

    Co-authored-by: Adhithya Ananthan <[email protected]>
    Co-authored-by: adhi0331 <[email protected]>

commit 6e67ad3
Author: Michael Sullivan <[email protected]>
Date:   Sat Jun 15 08:16:22 2024 -0700

    Feature/mraysu/program archive (#96)

    * Archive Program Route

    * Change program status to archived for students

    * Added Archived field to program schema

    * Implemented Archive Button

    * Prevent students from 'joining' archived programs

    * Lint fix

    * Integrated new enrollment schema

    * Archived Programs Page

    * Fix merge issues

    * Updated Archive Page View

    * Mobile UI Adjustments

    ---------

    Co-authored-by: mraysu <[email protected]>

commit b0b800a
Author: Aammya Sapra <[email protected]>
Date:   Fri Jun 14 21:40:43 2024 -0700

    Feature/aammya8/new account approval (#104)

    * Add user approval and denial functionality, as well as email user about account approval updates

    * backup

    * backup

    * fixed delete route

    * change approve/deny/delete to use email

    * approve/deny controllers do not get entered? but delete does

    * Fix Notifications UI (immediately remove corresponding card when approve/deny button clicked)

    * Modify routes for testing purposes

    * Debug statements --> user does not get found in denyUser

    * Email successfully sent for deny (accidentally deleted user before trying to send email earlier lol)

    * Fix frontend (populate account type)

    * Remove extra comments

    * added auth protection and cleaned up code

    * added env for emails

    * fix user role bug

    * fixed some bugs and deleted some log statements

    * ran lint fix

    ---------

    Co-authored-by: adhi0331 <[email protected]>

* fix: fix enrollment frontend and backend inconsistency

* Squashed merge branch 'feature/lisasiliu/view-student-profile-v2' into feature/parth4apple/student-form-updates

* feat: add image upload to student profile

* feat: add document upload to student profile and fix image upload

* feat: fix document upload so that it only triggers when user saves

* fix: fix merge conflict bug

* Complete Route Auth Verification

* Prettier

* /all token verification

* Fix Formatting

* formatting

* Auth for frontend attendance

* Fix Dependency Issue

* formatting error

* More Auth Headers

* Minor fix

* Auth Headers

---------

Co-authored-by: parth4apple <[email protected]>
Co-authored-by: mraysu <[email protected]>
Co-authored-by: mraysu <[email protected]>
Co-authored-by: Aaron Chan <[email protected]>
  • Loading branch information
5 people authored Dec 17, 2024
1 parent 058c01b commit 4791e05
Show file tree
Hide file tree
Showing 76 changed files with 3,323 additions and 821 deletions.
74 changes: 74 additions & 0 deletions backend/src/controllers/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Functions that process image route requests.
*/

import { NextFunction, Request, Response } from "express";
import mongoose from "mongoose";

import { InternalError } from "../errors";
import { ServiceError } from "../errors/service";
import { ValidationError } from "../errors/validation";
import { Image } from "../models/image";
import StudentModel from "../models/student";
import UserModel from "../models/user";
import { handleImageParsing } from "../util/image";

import { OwnerRequestBody } from "./types/types";
import { EditPhotoRequestBody } from "./types/userTypes";

export const editPhoto = (req: EditPhotoRequestBody, res: Response, nxt: NextFunction) => {
try {
//Validation logic inside handleImageParsing
handleImageParsing(req, res, nxt);
} catch (e) {
console.log(e);
nxt(e);
}
};

export const getPhoto = async (
req: Request<Record<string, never>, Record<string, never>, OwnerRequestBody>,
res: Response,
nxt: NextFunction,
) => {
try {
const { ownerId, ownerType, imageId } = req.body;

if (!mongoose.Types.ObjectId.isValid(imageId)) {
return res
.status(ValidationError.INVALID_MONGO_ID.status)
.send({ error: ValidationError.INVALID_MONGO_ID.message });
}

let owner = null;

if (ownerType === "user") {
owner = await UserModel.findById(ownerId);
} else if (ownerType === "student") {
owner = await StudentModel.findById(ownerId);
}

if (!owner) {
throw ValidationError.USER_NOT_FOUND;
}

const image = await Image.findById(imageId);
if (!image) {
throw ValidationError.IMAGE_NOT_FOUND;
}

if (image.ownerId !== ownerId) {
throw ValidationError.IMAGE_USER_MISMATCH;
}

return res.status(200).set("Content-type", image.mimetype).send(image.buffer);
} catch (e) {
console.log(e);
if (e instanceof ServiceError) {
nxt(e);
}
return res
.status(InternalError.ERROR_GETTING_IMAGE.status)
.send(InternalError.ERROR_GETTING_IMAGE.displayMessage(true));
}
};
119 changes: 97 additions & 22 deletions backend/src/controllers/student.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { validationResult } from "express-validator";
import mongoose, { HydratedDocument } from "mongoose";

import EnrollmentModel from "../models/enrollment";
import { Image } from "../models/image";
import ProgressNoteModel from "../models/progressNote";
import StudentModel from "../models/student";
import { Enrollment } from "../types/enrollment";
import { createEnrollment, editEnrollment } from "../util/enrollment";
Expand All @@ -22,16 +24,25 @@ export const createStudent: RequestHandler = async (req, res, next) => {

validationErrorParser(errors);

const newStudentId = new mongoose.Types.ObjectId();

const { enrollments, ...studentData } = req.body as StudentRequest;
const newStudent = await StudentModel.create(studentData);

// create enrollments for the student
await Promise.all(
const createdEnrollments = await Promise.all(
enrollments.map(async (program: Enrollment) => {
await createEnrollment({ ...program, studentId: newStudent._id });
return await EnrollmentModel.create({ ...program, studentId: newStudentId });
}),
);

res.status(201).json(newStudent);
const newStudent = await StudentModel.create({
...studentData,
enrollments: createdEnrollments.map((enrollment) => enrollment._id),
});

const populatedStudent = await StudentModel.findById(newStudent._id).populate("enrollments");

res.status(201).json(populatedStudent);
} catch (error) {
next(error);
}
Expand All @@ -49,42 +60,106 @@ export const editStudent: RequestHandler = async (req, res, next) => {
if (studentId !== studentData._id.toString()) {
return res.status(400).json({ message: "Invalid student ID" });
}
const updatedStudent = await StudentModel.findByIdAndUpdate(studentId, studentData, {
new: true,
});
if (!updatedStudent) {
return res.status(404).json({ message: "Student not found" });

if (!enrollments) {
const updatedStudent = await StudentModel.findByIdAndUpdate(
studentId,
{ ...studentData },
{
new: true,
},
);
if (!updatedStudent) {
return res.status(404).json({ message: "Student not found" });
}

return res.status(200).json(updatedStudent);
}

// update enrollments for the student
await Promise.all(
const updatedEnrollments = await Promise.all(
enrollments.map(async (enrollment: Enrollment) => {
const enrollmentExists = await EnrollmentModel.findById(enrollment._id);
const enrollmentBody = { ...enrollment, studentId: new mongoose.Types.ObjectId(studentId) };
if (!enrollmentExists) await createEnrollment(enrollmentBody);
else await editEnrollment(enrollmentBody);
if (!enrollmentExists) {
return await createEnrollment(enrollmentBody);
} else {
return await editEnrollment(enrollmentBody);
}
}),
);

res.status(200).json({ ...updatedStudent, enrollments });
const updatedStudent = await StudentModel.findByIdAndUpdate(
studentId,
{ ...studentData, enrollments: updatedEnrollments.map((enrollment) => enrollment?._id) },
{
new: true,
},
);
if (!updatedStudent) {
return res.status(404).json({ message: "Student not found" });
}

const populatedStudent = await StudentModel.findById(updatedStudent._id).populate(
"enrollments",
);

console.log({ populatedStudent });

res.status(200).json(populatedStudent);
} catch (error) {
next(error);
}
};

export const getAllStudents: RequestHandler = async (_, res, next) => {
try {
const students = await StudentModel.find();
const students = await StudentModel.find().populate("enrollments");

// gather all enrollments for each student and put them in student.programs
const hydratedStudents = await Promise.all(
students.map(async (student) => {
const enrollments = await EnrollmentModel.find({ studentId: student._id });
return { ...student.toObject(), programs: enrollments };
}),
);
res.status(200).json(students);
} catch (error) {
next(error);
}
};

export const getStudent: RequestHandler = async (req, res, next) => {
try {
const errors = validationResult(req);

validationErrorParser(errors);

const studentId = req.params.id;
const studentData = await StudentModel.findById(req.params.id);

if (!studentData) {
return res.status(404).json({ message: "Student not found" });
}

const enrollments = await EnrollmentModel.find({ studentId });

res.status(200).json({ ...studentData.toObject(), enrollments });
} catch (error) {
next(error);
}
};

export const deleteStudent: RequestHandler = async (req, res, next) => {
try {
const errors = validationResult(req);
validationErrorParser(errors);

const studentId = req.params.id;
const deletedStudent = await StudentModel.findById(studentId);
if (!deletedStudent) {
return res.status(404).json({ message: "Student not found" });
}

await EnrollmentModel.deleteMany({ studentId });
await ProgressNoteModel.deleteMany({ studentId });
await Image.deleteMany({ userId: studentId });
await StudentModel.deleteOne({ _id: studentId });

res.status(200).json(hydratedStudents);
res.status(200).json(deletedStudent);
} catch (error) {
next(error);
}
Expand Down
18 changes: 18 additions & 0 deletions backend/src/controllers/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,21 @@ export type UserId = {
};

export type UserIdRequestBody = Request & UserId;

// Add type so that if uploadType is "create", new field imgeId is required
type NewUploadType = {
uploadType: "new";
imageId: string;
};

type EditUploadType = {
uploadType: "edit";
imageId: never;
};

export type OwnerInfo = {
ownerId: string;
ownerType: string;
} & (NewUploadType | EditUploadType);

export type OwnerRequestBody = Request & OwnerInfo;
13 changes: 10 additions & 3 deletions backend/src/controllers/types/userTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request } from "express";

import { UserId } from "./types";
import { OwnerInfo, UserId } from "./types";

export type CreateUserRequestBody = {
name: string;
Expand Down Expand Up @@ -32,7 +32,10 @@ export type UpdateAccountTypeRequestBody = UserId & {
export type SaveImageRequest = {
body: {
previousImageId: string;
userId: string;
ownerId: string;
ownerType: string;
uploadType: string;
imageId: string;
};
file: {
buffer: Buffer;
Expand All @@ -42,6 +45,10 @@ export type SaveImageRequest = {
};
};

export type EditPhotoRequestBody = Request<Record<string, never>, Record<string, never>, UserId> & {
export type EditPhotoRequestBody = Request<
Record<string, never>,
Record<string, never>,
OwnerInfo
> & {
rawBody?: Buffer;
};
57 changes: 0 additions & 57 deletions backend/src/controllers/user.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { NextFunction, Request, Response } from "express";
import { validationResult } from "express-validator";
import admin from "firebase-admin";
import mongoose from "mongoose";

import { InternalError } from "../errors";
import { ServiceError } from "../errors/service";
import { ValidationError } from "../errors/validation";
import { Image } from "../models/image";
import UserModel from "../models/user";
import { sendApprovalEmail, sendDenialEmail } from "../util/email";
import { firebaseAdminAuth } from "../util/firebase";
import { handleImageParsing } from "../util/image";
import { deleteUserFromFirebase, deleteUserFromMongoDB } from "../util/user";
import validationErrorParser from "../util/validationErrorParser";

Expand All @@ -20,7 +15,6 @@ import {
EditEmailRequestBody,
EditLastChangedPasswordRequestBody,
EditNameRequestBody,
EditPhotoRequestBody,
LoginUserRequestBody,
UpdateAccountTypeRequestBody,
} from "./types/userTypes";
Expand Down Expand Up @@ -174,57 +168,6 @@ export const loginUser = async (
}
};

export const editPhoto = (req: EditPhotoRequestBody, res: Response, nxt: NextFunction) => {
try {
//Validation logic inside handleImageParsing
handleImageParsing(req, res, nxt);
} catch (e) {
console.log(e);
nxt(e);
}
};

export const getPhoto = async (
req: Request<Record<string, never>, Record<string, never>, UserIdRequestBody>,
res: Response,
nxt: NextFunction,
) => {
try {
const { uid } = req.body;
const { id: imageId } = req.params;

if (!mongoose.Types.ObjectId.isValid(imageId)) {
return res
.status(ValidationError.INVALID_MONGO_ID.status)
.send({ error: ValidationError.INVALID_MONGO_ID.message });
}

const user = await UserModel.findById(uid);
if (!user) {
throw ValidationError.USER_NOT_FOUND;
}

const image = await Image.findById(imageId);
if (!image) {
throw ValidationError.IMAGE_NOT_FOUND;
}

if (image.userId !== uid) {
throw ValidationError.IMAGE_USER_MISMATCH;
}

return res.status(200).set("Content-type", image.mimetype).send(image.buffer);
} catch (e) {
console.log(e);
if (e instanceof ServiceError) {
nxt(e);
}
return res
.status(InternalError.ERROR_GETTING_IMAGE.status)
.send(InternalError.ERROR_GETTING_IMAGE.displayMessage(true));
}
};

export const editName = async (
req: Request<Record<string, never>, Record<string, never>, EditNameRequestBody>,
res: Response,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/errors/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const IMAGE_NOT_FOUND = "Image was not found. Please make sure id passed in rout
const INVALID_MONGO_ID = "Mongo ID was invalid. Please ensure that the id is correct";
const IMAGE_USER_MISMATCH = "Image does not belong to the user";
const PROGRESS_NOTE_NOT_FOUND = "Progress note not found in database";
const IMAGE_UPDATED_FAILED = "Image was not updated successfully";

export class ValidationError extends CustomError {
static USER_CREATION_UNSUCCESSFUL = new ValidationError(1, 400, USER_CREATION_UNSUCCESSFUL);
Expand All @@ -24,4 +25,5 @@ export class ValidationError extends CustomError {
static INVALID_MONGO_ID = new ValidationError(9, 400, INVALID_MONGO_ID);
static IMAGE_USER_MISMATCH = new ValidationError(10, 401, IMAGE_USER_MISMATCH);
static PROGRESS_NOTE_NOT_FOUND = new ValidationError(11, 404, PROGRESS_NOTE_NOT_FOUND);
static IMAGE_UPDATED_FAILED = new ValidationError(12, 400, IMAGE_UPDATED_FAILED);
}
Loading

0 comments on commit 4791e05

Please sign in to comment.