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

F24/julia/activity management endpoints I #54

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
136 changes: 136 additions & 0 deletions backend/typescript/middlewares/validators/activityValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Request, Response, NextFunction } from "express";
import { getApiValidationError, validateDate, validatePrimitive } from "./util";

/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable-next-line import/prefer-default-export */
export const activityRequestDtoValidator = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { body } = req;

if (
body.userId !== undefined &&
body.userId !== null &&
!validatePrimitive(body.userId, "integer")
) {
return res.status(400).send(getApiValidationError("userId", "integer"));
}

if (!validatePrimitive(body.petId, "integer")) {
return res.status(400).send(getApiValidationError("petId", "integer"));
}

if (!validatePrimitive(body.activityTypeId, "integer")) {
return res
.status(400)
.send(getApiValidationError("activityTypeId", "integer"));
}

if (
body.scheduledStartTime !== undefined &&
body.scheduledStartTime !== null &&
!validateDate(body.scheduledStartTime)
) {
return res
.status(400)
.send(getApiValidationError("scheduledStartTime", "Date"));
}

if (
body.startTime !== undefined &&
body.startTime !== null &&
!validateDate(body.startTime)
) {
return res.status(400).send(getApiValidationError("startTime", "Date"));
}

if (
body.endTime !== undefined &&
body.endTime !== null &&
!validateDate(body.endTime)
) {
return res.status(400).send(getApiValidationError("endTime", "Date"));
}

if (
body.notes !== undefined &&
body.notes !== null &&
!validatePrimitive(body.notes, "string")
) {
return res.status(400).send(getApiValidationError("notes", "string"));
}

return next();
};

export const activityUpdateDtoValidator = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const { body } = req;

if (
body.userId !== undefined &&
body.userId !== null &&
!validatePrimitive(body.userId, "integer")
) {
return res.status(400).send(getApiValidationError("userId", "integer"));
}

if (
body.petId !== undefined &&
body.petId !== null &&
!validatePrimitive(body.petId, "integer")
) {
return res.status(400).send(getApiValidationError("petId", "integer"));
}

if (
body.activityTypeId !== undefined &&
body.activityTypeId !== null &&
!validatePrimitive(body.activityTypeId, "integer")
) {
return res
.status(400)
.send(getApiValidationError("activityTypeId", "integer"));
}

if (
body.scheduledStartTime !== undefined &&
body.scheduledStartTime !== null &&
!validateDate(body.scheduledStartTime)
) {
return res
.status(400)
.send(getApiValidationError("scheduledStartTime", "Date"));
}

if (
body.startTime !== undefined &&
body.startTime !== null &&
!validateDate(body.startTime)
) {
return res.status(400).send(getApiValidationError("startTime", "Date"));
}

if (
body.endTime !== undefined &&
body.endTime !== null &&
!validateDate(body.endTime)
) {
return res.status(400).send(getApiValidationError("endTime", "Date"));
}

if (
body.notes !== undefined &&
body.notes !== null &&
!validatePrimitive(body.notes, "string")
) {
return res.status(400).send(getApiValidationError("notes", "string"));
}

return next();
};
13 changes: 12 additions & 1 deletion backend/typescript/middlewares/validators/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
type Type = "string" | "integer" | "boolean" | "decimal" | "PetStatus" | "Sex";
type Type =
| "string"
| "integer"
| "boolean"
| "decimal"
| "PetStatus"
| "Sex"
| "Date";

const allowableContentTypes = new Set([
"text/plain",
Expand Down Expand Up @@ -50,6 +57,10 @@ export const validateFileType = (mimetype: string): boolean => {
return allowableContentTypes.has(mimetype);
};

export const validateDate = (value: any): boolean => {
return !Number.isNaN(Date.parse(value));
};

export const getApiValidationError = (
fieldName: string,
type: Type,
Expand Down
10 changes: 6 additions & 4 deletions backend/typescript/models/activity.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import User from "./user.model";
import Pet from "./pet.model";
import ActivityType from "./activityType.model";

@Table({ timestamps: false, tableName: "activities" })
@Table({
tableName: "activities",
timestamps: true,
createdAt: "created_at",
updatedAt: "updated_at",
})
export default class Activity extends Model {
@Column({})
activity_id!: number;

@ForeignKey(() => User) // in case of null, task has not been assigned
@Column({})
user_id?: number;
Expand Down
131 changes: 131 additions & 0 deletions backend/typescript/rest/activityRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Router } from "express";
import { isAuthorizedByRole } from "../middlewares/auth";
import {
activityRequestDtoValidator,
activityUpdateDtoValidator,
} from "../middlewares/validators/activityValidators";
import ActivityService from "../services/implementations/activityService";
import {
ActivityResponseDTO,
IActivityService,
} from "../services/interfaces/activityService";
import { getErrorMessage, NotFoundError } from "../utilities/errorUtils";
import { sendResponseByMimeType } from "../utilities/responseUtil";
import { Role } from "../types";

const activityRouter: Router = Router();
activityRouter.use(
isAuthorizedByRole(
new Set([
Role.ADMINISTRATOR,
Role.ANIMAL_BEHAVIOURIST,
Role.STAFF,
Role.VOLUNTEER,
]),
),
);
const activityService: IActivityService = new ActivityService();

/* Get all Activities */
activityRouter.get("/", async (req, res) => {
const contentType = req.headers["content-type"];
try {
const activities = await activityService.getActivities();
await sendResponseByMimeType<ActivityResponseDTO>(
res,
200,
contentType,
activities,
);
} catch (e: unknown) {
await sendResponseByMimeType(res, 500, contentType, [
{ error: getErrorMessage(e) },
]);
}
});

/* Get Activity by id */
activityRouter.get("/:id", async (req, res) => {
const { id } = req.params;
try {
const activity = await activityService.getActivity(id);
res.status(200).json(activity);
} catch (e: unknown) {
if (e instanceof NotFoundError) {
res.status(404).send(getErrorMessage(e));
} else {
res.status(500).send(getErrorMessage(e));
}
}
});

/* Create Activity */
activityRouter.post(
"/",
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
activityRequestDtoValidator,
async (req, res) => {
try {
const { body } = req;
const newActivity = await activityService.createActivity({
userId: body.userId,
petId: body.petId,
activityTypeId: body.activityTypeId,
scheduledStartTime: body.scheduledStartTime,
startTime: body.startTime,
endTime: body.endTime,
notes: body.notes,
});
res.status(201).json(newActivity);
} catch (error: unknown) {
if (error instanceof NotFoundError) {
res.status(404).send(getErrorMessage(error));
} else {
res.status(500).send(getErrorMessage(error));
}
}
},
);

/* Update Activity by id */
activityRouter.patch(
"/:id",
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
activityUpdateDtoValidator,
async (req, res) => {
const { id } = req.params;
try {
const { body } = req;
const Activity = await activityService.updateActivity(id, {
userId: body.userId,
petId: body.petId,
activityTypeId: body.activityTypeId,
scheduledStartTime: body.scheduledStartTime,
startTime: body.startTime,
endTime: body.endTime,
notes: body.notes,
});
res.status(200).json(Activity);
} catch (e: unknown) {
res.status(500).send(getErrorMessage(e));
}
},
);

/* Delete Activity by id */
activityRouter.delete(
"/:id",
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
async (req, res) => {
const { id } = req.params;

try {
const deletedId = await activityService.deleteActivity(id);
res.status(200).json({ id: deletedId });
} catch (e: unknown) {
res.status(500).send(getErrorMessage(e));
}
},
);

export default activityRouter;
2 changes: 2 additions & 0 deletions backend/typescript/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import entityRouter from "./rest/entityRoutes";
import petRouter from "./rest/petRoutes";
import simpleEntityRouter from "./rest/simpleEntityRoutes";
import userRouter from "./rest/userRoutes";
import activtyRouter from "./rest/activityRoutes";

const CORS_ALLOW_LIST = [
"http://localhost:3000",
Expand Down Expand Up @@ -43,6 +44,7 @@ app.use("/entities", entityRouter);
app.use("/pets", petRouter);
app.use("/simple-entities", simpleEntityRouter);
app.use("/users", userRouter);
app.use("/activities", activtyRouter);
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));

sequelize.authenticate();
Expand Down
Loading
Loading