-
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.
Merge pull request #21 from uwblueprint/S24/sayi/add-activity-endpoints
Add activity endpoints
- Loading branch information
Showing
6 changed files
with
301 additions
and
1 deletion.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
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,19 @@ | ||
import { Request, Response, NextFunction } from "express"; | ||
import { getApiValidationError, 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.activityName, "string")) { | ||
return res | ||
.status(400) | ||
.send(getApiValidationError("activityName", "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,106 @@ | ||
import { Router } from "express"; | ||
import { activityRequestDtoValidator } from "../middlewares/validators/activityValidators"; | ||
import ActivityService from "../services/implementations/activityService"; | ||
import { | ||
ActivityResponseDTO, | ||
IActivityService, | ||
} from "../services/interfaces/activityService"; | ||
import { | ||
getErrorMessage, | ||
NotFoundError, | ||
INTERNAL_SERVER_ERROR_MESSAGE, | ||
} from "../utilities/errorUtils"; | ||
import { sendResponseByMimeType } from "../utilities/responseUtil"; | ||
|
||
const activityRouter: Router = Router(); | ||
|
||
const activityService: IActivityService = new ActivityService(); | ||
|
||
/* Create Activity */ | ||
activityRouter.post("/", activityRequestDtoValidator, async (req, res) => { | ||
try { | ||
const { body } = req; | ||
const newActivity = await activityService.createActivity({ | ||
activityName: body.activityName, | ||
}); | ||
res.status(201).json(newActivity); | ||
} catch (e: unknown) { | ||
if (e instanceof NotFoundError) { | ||
res.status(404).send(getErrorMessage(e)); | ||
} else { | ||
res.status(500).send(INTERNAL_SERVER_ERROR_MESSAGE); | ||
} | ||
} | ||
}); | ||
|
||
/* 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 (e: unknown) { | ||
if (e instanceof NotFoundError) { | ||
res.status(404).send(getErrorMessage(e)); | ||
} else { | ||
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, { | ||
activityName: body.activityName, | ||
}); | ||
res.status(200).json(activity); | ||
} catch (e: unknown) { | ||
if (e instanceof NotFoundError) { | ||
res.status(404).send(getErrorMessage(e)); | ||
} else { | ||
res.status(500).send(INTERNAL_SERVER_ERROR_MESSAGE); | ||
} | ||
} | ||
}); | ||
|
||
/* 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) { | ||
if (e instanceof NotFoundError) { | ||
res.status(404).send(getErrorMessage(e)); | ||
} else { | ||
res.status(500).send(INTERNAL_SERVER_ERROR_MESSAGE); | ||
} | ||
} | ||
}); | ||
|
||
export default activityRouter; |
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
119 changes: 119 additions & 0 deletions
119
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,119 @@ | ||
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, | ||
activityName: activity.activity_name, | ||
}; | ||
} | ||
|
||
async getActivities(): Promise<ActivityResponseDTO[]> { | ||
try { | ||
const activities: Array<PgActivity> = await PgActivity.findAll({ | ||
raw: true, | ||
}); | ||
return activities.map((activity) => ({ | ||
id: activity.id, | ||
activityName: activity.activity_name, | ||
})); | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to get activities. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
} | ||
|
||
async createActivity( | ||
activity: ActivityRequestDTO, | ||
): Promise<ActivityResponseDTO> { | ||
let newActivity: PgActivity | null; | ||
try { | ||
newActivity = await PgActivity.create({ | ||
activity_name: activity.activityName, | ||
}); | ||
} catch (error: unknown) { | ||
Logger.error( | ||
`Failed to create activity. Reason = ${getErrorMessage(error)}`, | ||
); | ||
throw error; | ||
} | ||
return { | ||
id: newActivity.id, | ||
activityName: newActivity.activity_name, | ||
}; | ||
} | ||
|
||
async updateActivity( | ||
id: string, | ||
activity: ActivityRequestDTO, | ||
): Promise<ActivityResponseDTO | null> { | ||
let resultingActivity: PgActivity | null; | ||
let updateResult: [number, PgActivity[]] | null; | ||
try { | ||
updateResult = await PgActivity.update( | ||
{ | ||
activity_name: activity.activityName, | ||
}, | ||
{ 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, | ||
activityName: resultingActivity?.activity_name, | ||
}; | ||
} | ||
|
||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
export interface ActivityRequestDTO { | ||
activityName: string; | ||
} | ||
|
||
export interface ActivityResponseDTO { | ||
id: number; | ||
activityName: string; | ||
} | ||
|
||
export interface IActivityService { | ||
/** | ||
* retrieve the Activity with the given id | ||
* @param id Activity id | ||
* @returns requested Activity | ||
* @throws Error if retrieval fails | ||
*/ | ||
getActivity(id: string): Promise<ActivityResponseDTO>; | ||
|
||
/** | ||
* retrieve all Activities | ||
* @param | ||
* @returns returns array of Activities | ||
* @throws Error if retrieval fails | ||
*/ | ||
getActivities(): Promise<ActivityResponseDTO[]>; | ||
|
||
/** | ||
* create a Activity with the fields given in the DTO, return created Activity | ||
* @param activity new Activity | ||
* @returns the created Activity | ||
* @throws Error if creation fails | ||
*/ | ||
createActivity(activity: ActivityRequestDTO): Promise<ActivityResponseDTO>; | ||
|
||
/** | ||
* update the Activity with the given id with fields in the DTO, return updated Activity | ||
* @param id Activity id | ||
* @param activity Updated Activity | ||
* @returns the updated Activity | ||
* @throws Error if update fails | ||
*/ | ||
updateActivity( | ||
id: string, | ||
activity: ActivityRequestDTO, | ||
): Promise<ActivityResponseDTO | null>; | ||
|
||
/** | ||
* delete the Activity with the given id | ||
* @param id Activity id | ||
* @returns id of the Activity deleted | ||
* @throws Error if deletion fails | ||
*/ | ||
deleteActivity(id: string): Promise<string>; | ||
} |