-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
397 additions
and
2 deletions.
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
backend/typescript/middlewares/validators/activityValidators.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
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 (!validatePrimitive(body.activityId, "integer")) { | ||
return res.status(400).send(getApiValidationError("activityId", "integer")); | ||
} | ||
|
||
if (!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 (!validateDate(body.scheduledStartTime)) { | ||
return res.status(400).send(getApiValidationError("scheduledStartTime", "integer")); | ||
} | ||
|
||
if (!validateDate(body.startTime)) { | ||
return res.status(400).send(getApiValidationError("startTime", "integer")); | ||
} | ||
|
||
if (!validateDate(body.endTime)) { | ||
return res.status(400).send(getApiValidationError("endTime", "integer")); | ||
} | ||
|
||
if (!validatePrimitive(body.notes, "string")) { | ||
return res.status(400).send(getApiValidationError("notes", "string")); | ||
} | ||
|
||
return next(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { Router } from "express"; | ||
import { getAccessToken, isAuthorizedByRole } from "../middlewares/auth"; | ||
import { activityRequestDtoValidator } from "../middlewares/validators/activityValidators"; | ||
import ActivityService from "../services/implementations/activityService"; | ||
import { | ||
ActivityResponseDTO, | ||
IActivityService, | ||
} from "../services/interfaces/activityService"; | ||
import { getErrorMessage, INTERNAL_SERVER_ERROR_MESSAGE, 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])))); | ||
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: INTERNAL_SERVER_ERROR_MESSAGE, | ||
}, | ||
]); | ||
} | ||
}); | ||
|
||
/* 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 (error: unknown) { | ||
if (error instanceof NotFoundError) { | ||
res.status(404).send(getErrorMessage(error)); | ||
} else { | ||
res.status(500).send(INTERNAL_SERVER_ERROR_MESSAGE); | ||
} | ||
} | ||
}); | ||
|
||
/* Create Activity */ | ||
activityRouter.post("/", activityRequestDtoValidator, async (req, res) => { | ||
const accessToken = getAccessToken(req); | ||
if (!accessToken) { | ||
res.status(404).json({ error: "Access token not found" }); | ||
return; | ||
} | ||
|
||
const isBehaviourist = await authService.isAuthorizedByRole( | ||
accessToken, | ||
new Set([Role.ANIMAL_BEHAVIOURIST]), | ||
); | ||
|
||
const isAdministrator = await authService.isAuthorizedByRole( | ||
accessToken, | ||
new Set([Role.ADMINISTRATOR]), | ||
); | ||
|
||
try { | ||
|
||
const { body } = req; | ||
const newActivity = await activityService.createActivity({ | ||
activityId: body.activityId, | ||
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 (e: unknown) { | ||
res.status(500).send(INTERNAL_SERVER_ERROR_MESSAGE); | ||
} | ||
}, | ||
); | ||
|
||
|
||
|
||
/* Update Activity by id */ | ||
activityRouter.put( | ||
"/:id", | ||
activityRequestDtoValidator, | ||
async (req, res) => { | ||
const { id } = req.params; | ||
try { | ||
const { body } = req; | ||
const Activity = await activityService.updateActivity(id, { | ||
stringField: body.stringField, | ||
intField: body.intField, | ||
enumField: body.enumField, | ||
stringArrayField: body.stringArrayField, | ||
boolField: body.boolField, | ||
}); | ||
res.status(200).json(Activity); | ||
} catch (e: unknown) { | ||
res.status(500).send(getErrorMessage(e)); | ||
} | ||
}, | ||
); | ||
|
||
/* Delete Activity by id */ | ||
activityRouter.delete("/:id", 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; |
157 changes: 157 additions & 0 deletions
157
backend/typescript/services/implementations/activityService.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import PgActivity from "../../models/Activity.model"; | ||
import { | ||
IActivityService, | ||
ActivityRequestDTO, | ||
ActivityResponseDTO, | ||
} from "../interfaces/activityService"; | ||
import { getErrorMessage, NotFoundError } from "../../utilities/errorUtils"; | ||
import logger from "../../utilities/logger"; | ||
|
||
const Logger = logger(__filename); | ||
|
||
class ActivityService implements IActivityService { | ||
/* eslint-disable class-methods-use-this */ | ||
async getActivity(id: string): Promise<ActivityResponseDTO> { | ||
let activity: PgActivity | null; | ||
try { | ||
activity = await PgActivity.findByPk(id, { raw: true }); | ||
if (!activity) { | ||
throw new NotFoundError(`Activity id ${id} not found`); | ||
} | ||
} catch (error: unknown) { | ||
Logger.error(`Failed to get activity. Reason = ${getErrorMessage(error)}`); | ||
throw error; | ||
} | ||
|
||
return { | ||
id: activity.id, | ||
activityId: activity.activity_id, | ||
userId: activity.user_id, | ||
petId: activity.pet_id, | ||
activityTypeId: activity.activity_type_id, | ||
scheduledStartTime: activity.scheduled_start_time, | ||
startTime: activity.start_time, | ||
endTime: activity.end_time, | ||
notes: activity.notes, | ||
}; | ||
} | ||
|
||
async getActivities(): Promise<ActivityResponseDTO[]> { | ||
try { | ||
const activities: Array<PgActivity> = await PgActivity.findAll({ raw: true }); | ||
return activities.map((activity) => ({ | ||
id: activity.id, | ||
activityId: activity.activity_id, | ||
userId: activity.user_id, | ||
petId: activity.pet_id, | ||
activityTypeId: activity.activity_type_id, | ||
scheduledStartTime: activity.scheduled_start_time, | ||
startTime: activity.start_time, | ||
endTime: activity.end_time, | ||
notes: activity.notes, | ||
})); | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to get activites. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
} | ||
|
||
async createActivity( | ||
activity: ActivityRequestDTO, | ||
): Promise<ActivityResponseDTO> { | ||
let newActivity: PgActivity | null; | ||
try { | ||
newActivity = await PgActivity.create({ | ||
activity_id: activity.activityId, | ||
user_id: activity.userId, | ||
pet_id: activity.petId, | ||
activity_type_id: activity.activityTypeId, | ||
scheduled_start_time: activity.scheduledStartTime, | ||
start_time: activity.startTime, | ||
end_time: activity.endTime, | ||
notes: activity.notes, | ||
}); | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to create activity. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
return { | ||
id: newActivity.id, | ||
activityId: newActivity.activity_id, | ||
userId: newActivity.user_id, | ||
petId: newActivity.pet_id, | ||
activityTypeId: newActivity.activity_type_id, | ||
scheduledStartTime: newActivity.scheduled_start_time, | ||
startTime: newActivity.start_time, | ||
endTime: newActivity.end_time, | ||
notes: newActivity.notes, | ||
}; | ||
} | ||
|
||
async updateActivity( | ||
id: string, | ||
activity: ActivityRequestDTO, | ||
): Promise<ActivityResponseDTO | null> { | ||
let resultingActivity: PgActivity | null; | ||
let updateResult: [number, PgActivity[]] | null; | ||
try { | ||
updateResult = await PgActivity.update( | ||
{ | ||
activity_id: activity.activityId, | ||
user_id: activity.userId, | ||
pet_id: activity.petId, | ||
activity_type_id: activity.activityTypeId, | ||
scheduled_start_time: activity.scheduledStartTime, | ||
start_time: activity.startTime, | ||
end_time: activity.endTime, | ||
notes: activity.notes, | ||
}, | ||
{ where: { id }, returning: true }, | ||
); | ||
|
||
if (!updateResult[0]) { | ||
throw new NotFoundError(`Activity id ${id} not found`); | ||
} | ||
[, [resultingActivity]] = updateResult; | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to update activity. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
return { | ||
id: resultingActivity.id, | ||
activityId: resultingActivity.activity_id, | ||
userId: resultingActivity.user_id, | ||
petId: resultingActivity.pet_id, | ||
activityTypeId: resultingActivity.activity_type_id, | ||
scheduledStartTime: resultingActivity.scheduled_start_time, | ||
startTime: resultingActivity.start_time, | ||
endTime: resultingActivity.end_time, | ||
notes: resultingActivity.notes, | ||
}; | ||
} | ||
|
||
async deleteActivity(id: string): Promise<string> { | ||
try { | ||
const deleteResult: number | null = await PgActivity.destroy({ | ||
where: { id }, | ||
}); | ||
if (!deleteResult) { | ||
throw new NotFoundError(`Activity id ${id} not found`); | ||
} | ||
return id; | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to delete activity. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
export default ActivityService; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.