Skip to content

Commit 8bc4b03

Browse files
authored
Merge pull request #54 from uwblueprint/F24/julia/activity-management-endpoints-I
F24/julia/activity management endpoints I
2 parents 2a26d2d + b5ae141 commit 8bc4b03

File tree

7 files changed

+499
-5
lines changed

7 files changed

+499
-5
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import { getApiValidationError, validateDate, validatePrimitive } from "./util";
3+
4+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
5+
/* eslint-disable-next-line import/prefer-default-export */
6+
export const activityRequestDtoValidator = async (
7+
req: Request,
8+
res: Response,
9+
next: NextFunction,
10+
) => {
11+
const { body } = req;
12+
13+
if (
14+
body.userId !== undefined &&
15+
body.userId !== null &&
16+
!validatePrimitive(body.userId, "integer")
17+
) {
18+
return res.status(400).send(getApiValidationError("userId", "integer"));
19+
}
20+
21+
if (!validatePrimitive(body.petId, "integer")) {
22+
return res.status(400).send(getApiValidationError("petId", "integer"));
23+
}
24+
25+
if (!validatePrimitive(body.activityTypeId, "integer")) {
26+
return res
27+
.status(400)
28+
.send(getApiValidationError("activityTypeId", "integer"));
29+
}
30+
31+
if (
32+
body.scheduledStartTime !== undefined &&
33+
body.scheduledStartTime !== null &&
34+
!validateDate(body.scheduledStartTime)
35+
) {
36+
return res
37+
.status(400)
38+
.send(getApiValidationError("scheduledStartTime", "Date"));
39+
}
40+
41+
if (
42+
body.startTime !== undefined &&
43+
body.startTime !== null &&
44+
!validateDate(body.startTime)
45+
) {
46+
return res.status(400).send(getApiValidationError("startTime", "Date"));
47+
}
48+
49+
if (
50+
body.endTime !== undefined &&
51+
body.endTime !== null &&
52+
!validateDate(body.endTime)
53+
) {
54+
return res.status(400).send(getApiValidationError("endTime", "Date"));
55+
}
56+
57+
if (
58+
body.notes !== undefined &&
59+
body.notes !== null &&
60+
!validatePrimitive(body.notes, "string")
61+
) {
62+
return res.status(400).send(getApiValidationError("notes", "string"));
63+
}
64+
65+
return next();
66+
};
67+
68+
export const activityUpdateDtoValidator = async (
69+
req: Request,
70+
res: Response,
71+
next: NextFunction,
72+
) => {
73+
const { body } = req;
74+
75+
if (
76+
body.userId !== undefined &&
77+
body.userId !== null &&
78+
!validatePrimitive(body.userId, "integer")
79+
) {
80+
return res.status(400).send(getApiValidationError("userId", "integer"));
81+
}
82+
83+
if (
84+
body.petId !== undefined &&
85+
body.petId !== null &&
86+
!validatePrimitive(body.petId, "integer")
87+
) {
88+
return res.status(400).send(getApiValidationError("petId", "integer"));
89+
}
90+
91+
if (
92+
body.activityTypeId !== undefined &&
93+
body.activityTypeId !== null &&
94+
!validatePrimitive(body.activityTypeId, "integer")
95+
) {
96+
return res
97+
.status(400)
98+
.send(getApiValidationError("activityTypeId", "integer"));
99+
}
100+
101+
if (
102+
body.scheduledStartTime !== undefined &&
103+
body.scheduledStartTime !== null &&
104+
!validateDate(body.scheduledStartTime)
105+
) {
106+
return res
107+
.status(400)
108+
.send(getApiValidationError("scheduledStartTime", "Date"));
109+
}
110+
111+
if (
112+
body.startTime !== undefined &&
113+
body.startTime !== null &&
114+
!validateDate(body.startTime)
115+
) {
116+
return res.status(400).send(getApiValidationError("startTime", "Date"));
117+
}
118+
119+
if (
120+
body.endTime !== undefined &&
121+
body.endTime !== null &&
122+
!validateDate(body.endTime)
123+
) {
124+
return res.status(400).send(getApiValidationError("endTime", "Date"));
125+
}
126+
127+
if (
128+
body.notes !== undefined &&
129+
body.notes !== null &&
130+
!validatePrimitive(body.notes, "string")
131+
) {
132+
return res.status(400).send(getApiValidationError("notes", "string"));
133+
}
134+
135+
return next();
136+
};

backend/typescript/middlewares/validators/util.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
type Type = "string" | "integer" | "boolean" | "decimal" | "PetStatus" | "Sex";
1+
type Type =
2+
| "string"
3+
| "integer"
4+
| "boolean"
5+
| "decimal"
6+
| "PetStatus"
7+
| "Sex"
8+
| "Date";
29

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

60+
export const validateDate = (value: any): boolean => {
61+
return !Number.isNaN(Date.parse(value));
62+
};
63+
5364
export const getApiValidationError = (
5465
fieldName: string,
5566
type: Type,

backend/typescript/models/activity.model.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import User from "./user.model";
1010
import Pet from "./pet.model";
1111
import ActivityType from "./activityType.model";
1212

13-
@Table({ timestamps: false, tableName: "activities" })
13+
@Table({
14+
tableName: "activities",
15+
timestamps: true,
16+
createdAt: "created_at",
17+
updatedAt: "updated_at",
18+
})
1419
export default class Activity extends Model {
15-
@Column({})
16-
activity_id!: number;
17-
1820
@ForeignKey(() => User) // in case of null, task has not been assigned
1921
@Column({})
2022
user_id?: number;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { Router } from "express";
2+
import { isAuthorizedByRole } from "../middlewares/auth";
3+
import {
4+
activityRequestDtoValidator,
5+
activityUpdateDtoValidator,
6+
} from "../middlewares/validators/activityValidators";
7+
import ActivityService from "../services/implementations/activityService";
8+
import {
9+
ActivityResponseDTO,
10+
IActivityService,
11+
} from "../services/interfaces/activityService";
12+
import { getErrorMessage, NotFoundError } from "../utilities/errorUtils";
13+
import { sendResponseByMimeType } from "../utilities/responseUtil";
14+
import { Role } from "../types";
15+
16+
const activityRouter: Router = Router();
17+
activityRouter.use(isAuthorizedByRole(new Set(Object.values(Role))));
18+
const activityService: IActivityService = new ActivityService();
19+
20+
/* Get all Activities */
21+
activityRouter.get("/", async (req, res) => {
22+
const contentType = req.headers["content-type"];
23+
try {
24+
const activities = await activityService.getActivities();
25+
await sendResponseByMimeType<ActivityResponseDTO>(
26+
res,
27+
200,
28+
contentType,
29+
activities,
30+
);
31+
} catch (e: unknown) {
32+
await sendResponseByMimeType(res, 500, contentType, [
33+
{ error: getErrorMessage(e) },
34+
]);
35+
}
36+
});
37+
38+
/* Get Activity by id */
39+
activityRouter.get("/:id", async (req, res) => {
40+
const { id } = req.params;
41+
try {
42+
const activity = await activityService.getActivity(id);
43+
res.status(200).json(activity);
44+
} catch (e: unknown) {
45+
if (e instanceof NotFoundError) {
46+
res.status(404).send(getErrorMessage(e));
47+
} else {
48+
res.status(500).send(getErrorMessage(e));
49+
}
50+
}
51+
});
52+
53+
/* Create Activity */
54+
activityRouter.post(
55+
"/",
56+
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
57+
activityRequestDtoValidator,
58+
async (req, res) => {
59+
try {
60+
const { body } = req;
61+
const newActivity = await activityService.createActivity({
62+
userId: body.userId,
63+
petId: body.petId,
64+
activityTypeId: body.activityTypeId,
65+
scheduledStartTime: body.scheduledStartTime,
66+
startTime: body.startTime,
67+
endTime: body.endTime,
68+
notes: body.notes,
69+
});
70+
res.status(201).json(newActivity);
71+
} catch (error: unknown) {
72+
if (error instanceof NotFoundError) {
73+
res.status(404).send(getErrorMessage(error));
74+
} else {
75+
res.status(500).send(getErrorMessage(error));
76+
}
77+
}
78+
},
79+
);
80+
81+
/* Update Activity by id */
82+
activityRouter.patch(
83+
"/:id",
84+
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
85+
activityUpdateDtoValidator,
86+
async (req, res) => {
87+
const { id } = req.params;
88+
try {
89+
const { body } = req;
90+
const Activity = await activityService.updateActivity(id, {
91+
userId: body.userId,
92+
petId: body.petId,
93+
activityTypeId: body.activityTypeId,
94+
scheduledStartTime: body.scheduledStartTime,
95+
startTime: body.startTime,
96+
endTime: body.endTime,
97+
notes: body.notes,
98+
});
99+
res.status(200).json(Activity);
100+
} catch (e: unknown) {
101+
res.status(500).send(getErrorMessage(e));
102+
}
103+
},
104+
);
105+
106+
/* Delete Activity by id */
107+
activityRouter.delete(
108+
"/:id",
109+
isAuthorizedByRole(new Set([Role.ANIMAL_BEHAVIOURIST, Role.ADMINISTRATOR])),
110+
async (req, res) => {
111+
const { id } = req.params;
112+
113+
try {
114+
const deletedId = await activityService.deleteActivity(id);
115+
res.status(200).json({ id: deletedId });
116+
} catch (e: unknown) {
117+
res.status(500).send(getErrorMessage(e));
118+
}
119+
},
120+
);
121+
122+
export default activityRouter;

backend/typescript/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import entityRouter from "./rest/entityRoutes";
1414
import petRouter from "./rest/petRoutes";
1515
import simpleEntityRouter from "./rest/simpleEntityRoutes";
1616
import userRouter from "./rest/userRoutes";
17+
import activityRouter from "./rest/activityRoutes";
1718

1819
const CORS_ALLOW_LIST = [
1920
"http://localhost:3000",
@@ -43,6 +44,7 @@ app.use("/entities", entityRouter);
4344
app.use("/pets", petRouter);
4445
app.use("/simple-entities", simpleEntityRouter);
4546
app.use("/users", userRouter);
47+
app.use("/activities", activityRouter);
4648
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
4749

4850
sequelize.authenticate();

0 commit comments

Comments
 (0)