Skip to content

Commit

Permalink
fixed error validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lisasiliu committed Jan 23, 2024
1 parent 8af8ce6 commit 9675081
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 0 deletions.
3 changes: 3 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { json } from "body-parser";
import express from "express";
import mongoose from "mongoose";

import programFormRoutes from "../src/routes/program-form";

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

/**
Expand All @@ -24,6 +26,7 @@ void mongoose
});

server.app.use(json());
server.app.use("/api/programForm", programFormRoutes);

// make server listen on some port
server.app.listen(port, () => {
Expand Down
107 changes: 107 additions & 0 deletions backend/src/controllers/program-form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// copied from onboarding repo - TODO
// only need create

/**
* Functions that process task route requests.
*/

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

import ProgramFormModel from "../models/program-form";
import validationErrorParser from "../util/validationErrorParser";

export type typeProgramForm = {
name: string;
abbreviation: string;
type: string;
startDate: Date;
endDate: Date;
color: number;
};

/**
* This is an example of an Express API request handler. We'll tell Express to
* run this function when our backend receives a request to retrieve a
* particular task.
*
* Request handlers typically have 3 parameters: req, res, and next.
*
* @param req The Request object from Express. This contains all the data from
* the API request. (https://expressjs.com/en/4x/api.html#req)
* @param res The Response object from Express. We use this to generate the API
* response for Express to send back. (https://expressjs.com/en/4x/api.html#res)
* @param next The next function in the chain of middleware. If there's no more
* processing we can do in this handler, but we're not completely done handling
* the request, then we can pass it along by calling next(). For all of the
* handlers defined in `src/controllers`, the next function is the global error
* handler in `src/app.ts`.
*/
// export const getForm: RequestHandler = async (req, res, next) => {
// const { id } = req.params;

// try {
// // if the ID doesn't exist, then findById returns null
// const task = await TaskModel.findById(id);

// if (task === null) {
// throw createHttpError(404, "Task not found.");
// }

// // Set the status code (200) and body (the task object as JSON) of the response.
// // Note that you don't need to return anything, but you can still use a return
// // statement to exit the function early.
// res.status(200).json(task);
// } catch (error) {
// // pass errors to the error handler
// next(error);
// }
// };

export const createForm: RequestHandler = async (req, res, next) => {

Check warning on line 61 in backend/src/controllers/program-form.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
// extract any errors that were found by the validator
const errors = validationResult(req);

Check failure on line 63 in backend/src/controllers/program-form.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

Check failure on line 63 in backend/src/controllers/program-form.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe call of an `any` typed value
/* eslint-disable */
const { name, abbreviation, type, startDate, endDate, color } = req.body;
/* eslint-enable */

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

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

const programForm = await ProgramFormModel.create({
name: String,
abbreviation: String,
type: String,
startDate: Date,
endDate: Date,
color: Number,
});

// 201 means a new resource has been created successfully
// the newly created task is sent back to the user
res.status(201).json(programForm);
} catch (error) {
next(error);
}
};

// export const removeForm: RequestHandler = async (req, res, next) => {
// const { id } = req.params;

// try {
// const result = await TaskModel.deleteOne({ _id: id });

// res.status(200).json(result);
// } catch (error) {
// next(error);
// }
// };
16 changes: 16 additions & 0 deletions backend/src/models/program-form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//modified from onboarding repo for program form backend

import { InferSchemaType, Schema, model } from "mongoose";

const programFormSchema = new Schema({
name: { type: String, required: true },
abbreviation: { type: String, required: true }, // e.g. ENTR
type: { type: String, required: true }, // regular vs. varying
startDate: { type: Date, required: true },
endDate: { type: Date, required: true },
color: { type: Number, enum: [1, 2, 3, 4], required: true }, // options: 1 (teal, #4FA197), 2 (yellow, #FFB800), 3 (pink, #FF7A5E), 4 (olive, #B6BF0E)
});

type ProgramForm = InferSchemaType<typeof programFormSchema>;

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

import express from "express"; // { RequestHandler }

// import { ParamsDictionary } from "express-serve-static-core";
// import { ParsedQs } from "qs";

import * as ProgramFormController from "../controllers/program-form";
import * as ProgramFormValidator from "../validators/program-form";

const router = express.Router();

//router.get("/:id", ProgramFormController.getForm);
router.post("/", ProgramFormValidator.createForm, ProgramFormController.createForm);
//router.delete("/:id", ProgramFormController.removeForm);

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()) {

Check failure on line 12 in backend/src/util/validationErrorParser.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe call of an `any` typed value

Check failure on line 12 in backend/src/util/validationErrorParser.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe member access .isEmpty on an `any` value
let errorString = "";

// parse through errors returned by the validator and append them to the error string
for (const error of errors.array()) {

Check failure on line 16 in backend/src/util/validationErrorParser.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe call of an `any` typed value

Check failure on line 16 in backend/src/util/validationErrorParser.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe member access .array on an `any` value
errorString += error.msg + " ";

Check failure on line 17 in backend/src/util/validationErrorParser.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe member access .msg on an `any` value
}

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

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

import { typeProgramForm } from "../controllers/program-form";

// more info about validators:
// https://express-validator.github.io/docs/guides/validation-chain
// https://github.com/validatorjs/validator.js#validators

// const makeIDValidator = () =>
// body("_id")
// .exists()
// .withMessage("_id is required")
// .bail()
// .isMongoId()
// .withMessage("_id must be a MongoDB object ID");
// const makeTitleValidator = () =>
// body("title")
// // title must exist, if not this message will be displayed
// .exists()
// .withMessage("title is required")
// // bail prevents the remainder of the validation chain for this field from being executed if
// // there was an error
// .bail()
// .isString()
// .withMessage("title must be a string")
// .bail()
// .notEmpty()
// .withMessage("title cannot be empty");
const makeNameValidator = () => body("name").isString().withMessage("name must be a string");

Check failure on line 29 in backend/src/validators/program-form.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe return of an `any` typed value

Check failure on line 29 in backend/src/validators/program-form.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe call of an `any` typed value

Check failure on line 29 in backend/src/validators/program-form.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe call of an `any` typed value
const makeAbbreviationValidator = () =>
body("abbreviation").isString().withMessage("abbreviation must be a string");
const makeTypeValidator = () => body("type").isString().withMessage("type must be a string");
const makeStartDateValidator = () =>
body("startDate").isISO8601().withMessage("startDate must be a valid date-time string");
const makeEndDateValidator = () =>
body("endDate")
.isISO8601()
.custom((value, { req }) => {
const reqBody: typeProgramForm = req.body as typeProgramForm;
if (value < reqBody.startDate) throw new Error("end date must be after start date");
return true;
});
const makeColorValidator = () =>
body("color")
.isNumeric()
.custom((value) => {
if (value < 1 || value > 4) {
throw new Error("color must be an option number 1-4");
}
return true;
});

// assignee is for Part 2.1
// const makeAssigneeValidator = () =>
// body("assignee").optional().isMongoId().withMessage("assignee must be a MongoDB object ID");

// establishes a set of rules that the body of the task creation route must follow
export const createForm = [
makeNameValidator(),
makeAbbreviationValidator(),
makeTypeValidator(),
makeStartDateValidator(),
makeEndDateValidator(),
makeColorValidator(),
];

// export function createForm() {
// makeIDValidator();
// makeNameValidator();
// makeAbbreviationValidator();
// makeTypeValidator();
// makeStartDateValidator();
// makeEndDateValidator();
// makeColorValidator();
// }

// export const updateForm = [
// // makeIDValidator(),
// // makeTitleValidator(),
// // makeDescriptionValidator(),
// // makeIsCheckedValidator(),
// // makeDateCreatedValidator(),
// // makeAssigneeValidator(), // for Part 2.1
// ];

// export function editForm(arg0: string, editForm: any, createForm: RequestHandler<ParamsDictionary, any, any, ParsedQs, Record<string, any>>) {
// throw new Error("Function not implemented.");
// }
37 changes: 37 additions & 0 deletions package-lock.json

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

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"express-validator": "^7.0.1"
}
}

0 comments on commit 9675081

Please sign in to comment.