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/mraysu/add students form #32

Merged
merged 10 commits into from
Feb 2, 2024
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
2 changes: 0 additions & 2 deletions backend/.env.example

This file was deleted.

26 changes: 26 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-validator": "^7.0.1",
"firebase-admin": "^12.0.0",
"mongodb": "^6.3.0",
"mongoose": "^8.0.3"
Expand Down
6 changes: 6 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { json } from "body-parser";
import express from "express";
import mongoose from "mongoose";

import studentRoutes from "../src/routes/student";

import { mongoURI, port } from "./config";
import { errorHandler } from "./errors/handler";

/**
* Express server application class
Expand All @@ -25,6 +28,9 @@ void mongoose

server.app.use(json());

server.app.use("/student", studentRoutes);
server.app.use(errorHandler);

// make server listen on some port
server.app.listen(port, () => {
console.log(`> Listening on port ${port}`);
Expand Down
53 changes: 53 additions & 0 deletions backend/src/controllers/student.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/**
* Functions that process task route requests.
*/

import { RequestHandler } from "express";
import { validationResult } from "express-validator";

import { ValidationError } from "../errors/validation";
import StudentModel from "../models/student";

export type contact = {
lastName: string;
firstName: string;
email: string;
phoneNumber: string;
};

export type typedModel = {
student: contact;
emergency: contact;
serviceCoordinator: contact;
location: string;
medication: string;
birthday: string;
intakeDate: string;
tourDate: string;
prog1: string;
prog2: string;
dietary: string;
otherString: string;
};

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

try {
if (!errors.isEmpty()) {
let errorString = "";

for (const error of errors.array()) {
errorString += error.msg + " ";
}
throw new ValidationError(errorString);
}

const newStudent = await StudentModel.create(req.body as typedModel);

res.status(201).json(newStudent);
} catch (error) {
next(error);
}
};
7 changes: 7 additions & 0 deletions backend/src/errors/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CustomError } from "../errors";

export class ValidationError extends CustomError {
constructor(message: string) {
super(0, 400, "VALIDATION ERROR: " + message);
}
}
51 changes: 51 additions & 0 deletions backend/src/models/student.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Student schema
*/
import { InferSchemaType, Schema, model } from "mongoose";

const studentSchema = new Schema({
student: {
lastName: { type: String, required: true },
firstName: { type: String, required: true },
email: { type: String, required: true },
phoneNumber: { type: String, required: true },
},

emergency: {
lastName: { type: String, required: true },
firstName: { type: String, required: true },
email: { type: String, required: true },
phoneNumber: { type: String, required: true },
},

serviceCoordinator: {
lastName: { type: String, required: true },
firstName: { type: String, required: true },
email: { type: String, required: true },
phoneNumber: { type: String, required: true },
},

//Address of student
location: { type: String, required: true },

//String list of medications
medication: { type: String, required: true },

birthday: { type: Date, required: true },
intakeDate: { type: Date, required: true },
tourDate: { type: Date, required: true },

//For now, chose to express these as strings. Will probably be replaced with
//program subdocs in the future once they have been defined
prog1: { type: String, required: true },
prog2: { type: String, default: "" },

//Will contain list of all dietary restrictions
dietary: { type: [String] },

otherString: { type: String, default: "" },
});

type Student = InferSchemaType<typeof studentSchema>;

export default model<Student>("Student", studentSchema);
14 changes: 14 additions & 0 deletions backend/src/routes/student.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Task route requests.
*/

import express from "express";

import * as StudentController from "../controllers/student";
import * as StudentValidator from "../validators/student";

const router = express.Router();

router.post("/", StudentValidator.createStudent, StudentController.createStudent);

export default router;
154 changes: 154 additions & 0 deletions backend/src/validators/student.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Student document validator
*/

import { body } from "express-validator";

//designed these to use the globstar operator from express-validator to more cleanly
//validate contacts
const makeLastNamesValidator = () =>
body("**.lastName")
.exists()
.withMessage("Last name required")
.bail()
.isString()
.withMessage("Last name must be a string")
.bail()
.notEmpty()
.withMessage("Last name required");

const makeFirstNamesValidator = () =>
body("**.firstName")
.exists()
.withMessage("First name required")
.bail()
.isString()
.withMessage("First name must be a string")
.bail()
.notEmpty()
.withMessage("First name required");

const makeEmailsValidator = () =>
body("**.email")
.exists()
.withMessage("Email required")
.bail()
.isEmail()
.withMessage("Field must be a valid email")
.bail()
.notEmpty()
.withMessage("Email required");

//Currently only accepts phone numbers as either 10 or 11 digit numbers
const makePhoneNumbersValidator = () =>
body("**.phoneNumber")
.trim()
.exists()
.withMessage("Phone number required")
.bail()
.isNumeric()
.withMessage("Field must have a valid number")
.isLength({ min: 10, max: 11 })
.withMessage("Phone number has an incorrect length")
.bail()
.notEmpty()
.withMessage("Phone number required");

//validate location
const makeLocationValidator = () =>
body("location")
.exists()
.withMessage("Location field fequired")
.bail()
.isString()
.withMessage("Location must be a string")
.bail()
.notEmpty()
.withMessage("Location field required");

//medication
const makeMedicationValidator = () =>
body("medication")
.exists()
.withMessage("Medication field required")
.bail()
.isString()
.withMessage("Medication must be a string")
.bail()
.notEmpty()
.withMessage("Medication field required");

//birthday
const makeBirthdayValidator = () =>
body("birthday")
.exists()
.withMessage("Birthday field required")
.bail()
.isISO8601()
.withMessage("Birthday string must be a valid date-time string");

//intake date
const makeIntakeDateValidator = () =>
body("intakeDate")
.exists()
.withMessage("Intake Date field required")
.bail()
.isISO8601()
.withMessage("Intake Date string must be a valid date-time string");

//tour date
const makeTourDateValidator = () =>
body("tourDate")
.exists()
.withMessage("Tour Date field required")
.bail()
.isISO8601()
.withMessage("Tour Date string must be a valid date-time string");

//prog1 --placeholder, will later validate for a program objectid
const makeProg1Validator = () =>
body("prog1")
.exists()
.withMessage("Program 1 field required")
.bail()
.isString()
.withMessage("Program 1 must be a string");

//prog2
const makeProg2Validator = () =>
body("prog2").optional().isString().withMessage("Program 2 must be a string");

//dietary
//validates entire array
const makeDietaryArrayValidator = () =>
body("dietary")
.optional()
.exists()
.isArray()
.withMessage("Dietary restrictions must be an array");
//validates individual items
const makeDietaryItemsValidator = () =>
body("dietary.*").exists().isString().withMessage("Dietary restriction element must be a string");

const makeDietaryOtherValidator = () =>
body("otherString")
.optional()
.isString()
.withMessage("Other dietary restriction must be a string");

export const createStudent = [
makeLastNamesValidator(),
makeFirstNamesValidator(),
makeEmailsValidator(),
makePhoneNumbersValidator(),
makeLocationValidator(),
makeMedicationValidator(),
makeBirthdayValidator(),
makeIntakeDateValidator(),
makeTourDateValidator(),
makeProg1Validator(),
makeProg2Validator(),
makeDietaryArrayValidator(),
makeDietaryItemsValidator(),
makeDietaryOtherValidator(),
];
Empty file removed frontend/.env.example
Empty file.
10 changes: 5 additions & 5 deletions frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"incremental": true,
"plugins": [
{
"name": "next"
}
"name": "next",
},
],
"paths": {
"@/*": ["./src/*"]
}
"@/*": ["./src/*"],
},
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules"],
}
Loading