Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/lisasiliu/view-student-profile-v2 #103

Merged
merged 43 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8b44702
created file to put ui code in
lisasiliu Apr 14, 2024
6c698bc
finished hardcoded notification table
lisasiliu Apr 16, 2024
6600664
updated backend to match main
lisasiliu Apr 18, 2024
998b604
student profile basic shell text
lisasiliu Apr 30, 2024
f24b73d
finished rest of ui
lisasiliu May 14, 2024
362e84a
feat: starting form changes
parth4apple May 20, 2024
ff94815
finished up student page
lisasiliu May 21, 2024
43cffb4
updating with main
lisasiliu May 21, 2024
f3f6009
feat: set up useFieldArray, refactor
parth4apple Jun 2, 2024
5bff702
feat: working student form
parth4apple Jun 4, 2024
270d779
chore: resolve merge conflicts
parth4apple Jun 4, 2024
1fc558f
Merge with main/resolve conflicts
mraysu Oct 20, 2024
9de1cc4
Removed conflicting color
mraysu Oct 20, 2024
668072a
Implemented view profile button
mraysu Oct 20, 2024
a5a3912
End-to-end retrieve student info
mraysu Oct 24, 2024
78f0a9e
Visualize Student Data
mraysu Oct 30, 2024
8d47a80
Deletion route
mraysu Nov 7, 2024
e489e7b
Printing Student Profile
mraysu Nov 7, 2024
64b4644
Remove debug code
mraysu Nov 7, 2024
c20e3d9
Delete Student Dialog
mraysu Nov 14, 2024
29d6dd7
Student Deletion
mraysu Nov 21, 2024
b6b97f4
Student Profile Picture Render
mraysu Nov 21, 2024
35a0d04
Get Student Route Verification
mraysu Nov 21, 2024
41404b2
Squashed commit of the following:
aaronchan32 Nov 28, 2024
d917f2c
fix: fix enrollment frontend and backend inconsistency
aaronchan32 Nov 28, 2024
63e5e0e
Squashed merge branch 'feature/lisasiliu/view-student-profile-v2' int…
aaronchan32 Nov 28, 2024
a9c6ead
feat: add image upload to student profile
aaronchan32 Nov 29, 2024
c5c7d61
feat: add document upload to student profile and fix image upload
aaronchan32 Dec 1, 2024
f711b1e
feat: fix document upload so that it only triggers when user saves
aaronchan32 Dec 1, 2024
00ac200
Merge branch 'main' of https://github.com/TritonSE/PIA-Program-Manage…
aaronchan32 Dec 1, 2024
4a98159
fix: fix merge conflict bug
aaronchan32 Dec 1, 2024
b859ec7
Complete Route Auth Verification
mraysu Dec 2, 2024
595a9ca
Prettier
mraysu Dec 2, 2024
cda5967
Merge branch 'feature/parth4apple/student-form-updates' into feature/…
mraysu Dec 6, 2024
69c435b
/all token verification
mraysu Dec 6, 2024
4426847
Fix Formatting
mraysu Dec 6, 2024
0f4a5c1
formatting
mraysu Dec 6, 2024
9a019f6
Auth for frontend attendance
mraysu Dec 6, 2024
6eb68bd
Fix Dependency Issue
mraysu Dec 6, 2024
7b944e3
formatting error
mraysu Dec 6, 2024
4d071bd
More Auth Headers
mraysu Dec 9, 2024
c69138f
Minor fix
mraysu Dec 9, 2024
958fb99
Auth Headers
mraysu Dec 9, 2024
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
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 @@
EditEmailRequestBody,
EditLastChangedPasswordRequestBody,
EditNameRequestBody,
EditPhotoRequestBody,
LoginUserRequestBody,
UpdateAccountTypeRequestBody,
} from "./types/userTypes";
Expand Down Expand Up @@ -100,9 +94,9 @@

export const approveUser = async (req: Request, res: Response, nxt: NextFunction) => {
try {
const { email } = req.body;

Check warning on line 97 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

const user = await UserModel.findOne({ email });

Check warning on line 99 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value
if (!user) {
return res.status(404).send("User not found");
}
Expand All @@ -125,12 +119,12 @@
console.log("Inside denyUser controller");

try {
const { email } = req.body;

Check warning on line 122 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

console.log("Email from request body:", email);

// const user = await UserModel.findOne({ email });
const user = await UserModel.findOne({ email });

Check warning on line 127 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

if (!user) {
return res.status(404).send("User not found");
Expand Down Expand Up @@ -174,57 +168,6 @@
}
};

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
Loading