Skip to content

Commit

Permalink
Feature/steven+yoto vsr personal info (#24)
Browse files Browse the repository at this point in the history
* lint fixes

* Added route to POST and VSR controller

* Added validation functions for form responses

* Added validation functions for form responses

* Removed numOfBoys, numOfGirls from schema, cleaned up backend, renamed martial to marital

* Added tests for validate spouse name

* Added tests for validate ethnicity

* Basic text field, multiple choice, dropdown, and submit

* Fix linting issues and minor edit to gender field validation

* app now uses post route

* Attempt at linking front end with back end, onbooarding code for front end api requests imported and adjusted

* Reworked TextField component to do ref-forwarding, successfully pushed a document to the mongo database

* Fixed couple bugs with date verification

* Removed forwardRef

* Added title and description, required fields, and overall more styling

* updated date to use current date, added more fields for the vsr page

* Prototype of ethnicity selector

* Made options unselectable

* Working version of ethnicity form

* Prototype of appearing and disappearing children form

* Added more styling

* Added backend functionality for ages, changed Child #n name to Child #n age

* Fixed small bug with ethnicity, added numBoys and spouse to createVSR, and continued styling

* fixed issue with ethnicity selection

* Changed textbox styling to flex 1

* Fixed issue with null values in age array

* Page number component

* added girls form

* changes with validation

* Yet even more lint fixes

* Fix CSS styles & clean up code

* Update favicon, title, & description, remove unused boilerplate

* Remove commented-out code & make ethnicity an array

* Fix bugs with entering ages of children

* Only require spouse's name for Married marital status

* Fix age validation

* Fix bugs with VSR age & num children

* Address Daniel's review comments

---------

Co-authored-by: Yoto Kim <[email protected]>
Co-authored-by: Benjamin Johnson <[email protected]>
  • Loading branch information
3 people authored Feb 25, 2024
1 parent c60b6b4 commit 3750b82
Show file tree
Hide file tree
Showing 30 changed files with 1,731 additions and 278 deletions.
3 changes: 3 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "dotenv/config";
import cors from "cors";
import express, { NextFunction, Request, Response } from "express";
import { isHttpError } from "http-errors";
import vsrRoutes from "../src/routes/vsr";
import { userRouter } from "src/routes/users";
import env from "src/util/validateEnv";

Expand Down Expand Up @@ -52,4 +53,6 @@ app.use((error: unknown, req: Request, res: Response, next: NextFunction) => {
res.status(statusCode).json({ error: errorMessage });
});

app.use("/api/vsr", vsrRoutes);

export default app;
51 changes: 51 additions & 0 deletions backend/src/controllers/vsr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { RequestHandler } from "express";
import { validationResult } from "express-validator";
import VSRModel from "src/models/vsr";
import validationErrorParser from "src/util/validationErrorParser";

export const createVSR: RequestHandler = async (req, res, next) => {
// extract any errors that were found by the validator
const errors = validationResult(req);
const {
name,
gender,
age,
maritalStatus,
spouseName,
agesOfBoys,
agesOfGirls,
ethnicity,
employmentStatus,
incomeLevel,
sizeOfHome,
} = req.body;

try {
// if there are errors, then this function throws an exception
validationErrorParser(errors);

// Get the current date as a timestamp for when VSR was submitted
const date = new Date();

const vsr = await VSRModel.create({
name,
date,
gender,
age,
maritalStatus,
spouseName,
agesOfBoys,
agesOfGirls,
ethnicity,
employmentStatus,
incomeLevel,
sizeOfHome,
});

// 201 means a new resource has been created successfully
// the newly created VSR is sent back to the user
res.status(201).json(vsr);
} catch (error) {
next(error);
}
};
20 changes: 20 additions & 0 deletions backend/src/models/vsr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { InferSchemaType, Schema, model } from "mongoose";

const vsrSchema = new Schema({
name: { type: String, required: true },
date: { type: Date, required: true },
gender: { type: String, require: true },
age: { type: Number, require: true },
maritalStatus: { type: String, required: true },
spouseName: { type: String },
agesOfBoys: { type: [Number] },
agesOfGirls: { type: [Number] },
ethnicity: { type: [String], require: true },
employmentStatus: { type: String, require: true },
incomeLevel: { type: String, require: true },
sizeOfHome: { type: String, require: true },
});

type VSR = InferSchemaType<typeof vsrSchema>;

export default model<VSR>("VSR", vsrSchema);
9 changes: 9 additions & 0 deletions backend/src/routes/vsr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from "express";
import * as VSRController from "src/controllers/vsr";
import * as VSRValidator from "src/validators/vsr";

const router = express.Router();

router.post("/", VSRValidator.createVSR, VSRController.createVSR);

export default router;
25 changes: 25 additions & 0 deletions backend/src/util/validationErrorParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Result, ValidationError } from "express-validator";
import createHttpError from "http-errors";

/**
* Parses through errors thrown by validator (if any exist). Error messages are
* added to a string and that string is used as the error message for the HTTP
* error.
*
* @param errors the validation result provided by express validator middleware
*/
const validationErrorParser = (errors: Result<ValidationError>) => {
if (!errors.isEmpty()) {
let errorString = "";

// parse through errors returned by the validator and append them to the error string
for (const error of errors.array()) {
errorString += error.msg + " ";
}

// trim removes the trailing space created in the for loop
throw createHttpError(400, errorString.trim());
}
};

export default validationErrorParser;
97 changes: 97 additions & 0 deletions backend/src/validators/vsr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { body } from "express-validator";

const makeNameValidator = () =>
body("name")
.exists({ checkFalsy: true })
.withMessage("Name is required")
.isString()
.withMessage("Name must be a string");

const makeGenderValidator = () =>
body("gender")
.exists({ checkFalsy: true })
.withMessage("Gender is required")
.isString()
.withMessage("Gender must be a string");

const makeAgeValidator = () =>
body("age")
.exists({ checkFalsy: true })
.withMessage("Age is required")
.isInt({ min: 0 })
.withMessage("Age must be a positive integer");

const makeMaritalStatusValidator = () =>
body("maritalStatus")
.exists({ checkFalsy: true })
.withMessage("Marital Status is required")
.isString()
.withMessage("Marital Status must be a string");

const makeSpouseNameValidator = () =>
body("spouseName")
.optional({ checkFalsy: true })
.isString()
.withMessage("Spouse Name must be a string");

const makeAgesOfBoysValidator = () =>
body("agesOfBoys")
.exists({ checkFalsy: true })
.isArray()
.withMessage("Ages of Boys must be an array of numbers")
.custom((ages: number[]) => ages.every((age) => Number.isInteger(age) && age >= 0))
.withMessage("Each age in Ages of Boys must be a positive integer");

const makeAgesOfGirlsValidator = () =>
body("agesOfGirls")
.exists({ checkFalsy: true })
.isArray()
.withMessage("Ages of Girls must be an array of numbers")
.custom((ages: number[]) => ages.every((age) => Number.isInteger(age) && age >= 0))
.withMessage("Each age in Ages of Girls must be a positive integer");

const makeEthnicityValidator = () =>
body("ethnicity")
.exists({ checkFalsy: true })
.withMessage("Ethnicity is required")
.isArray()
.withMessage("Ethnicity must be an array")
.custom((ethnicities: string[]) =>
ethnicities.every((ethnicity) => typeof ethnicity === "string"),
)
.withMessage("Each ethnicity in Ethnicities must be a positive integer");

const makeEmploymentStatusValidator = () =>
body("employmentStatus")
.exists({ checkFalsy: true })
.withMessage("Employment Status is required")
.isString()
.withMessage("Employment Status must be a string");

const makeIncomeLevelValidator = () =>
body("incomeLevel")
.exists({ checkFalsy: true })
.withMessage("Income Level is required")
.isString()
.withMessage("Income Level must be a string");

const makeSizeOfHomeValidator = () =>
body("sizeOfHome")
.exists({ checkFalsy: true })
.withMessage("Size of Home is required")
.isString()
.withMessage("Size of Home must be a string");

export const createVSR = [
makeNameValidator(),
makeGenderValidator(),
makeAgeValidator(),
makeMaritalStatusValidator(),
makeSpouseNameValidator(),
makeAgesOfBoysValidator(),
makeAgesOfGirlsValidator(),
makeEthnicityValidator(),
makeEmploymentStatusValidator(),
makeIncomeLevelValidator(),
makeSizeOfHomeValidator(),
];
Loading

0 comments on commit 3750b82

Please sign in to comment.