Skip to content

Commit

Permalink
Merge pull request #21 from uwblueprint/S24/sayi/add-activity-endpoints
Browse files Browse the repository at this point in the history
Add activity endpoints
  • Loading branch information
sthuray authored Jul 16, 2024
2 parents e7b2ff1 + 8e1742d commit 00f5559
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 1 deletion.
19 changes: 19 additions & 0 deletions backend/typescript/middlewares/validators/activityValidators.ts
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();
};
2 changes: 1 addition & 1 deletion backend/typescript/models/activity.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Column, Model, Table } from "sequelize-typescript";

@Table({ tableName: "activities" })
@Table({ timestamps: false, tableName: "activities" })
export default class Activities extends Model {
@Column
activity_name!: string;
Expand Down
106 changes: 106 additions & 0 deletions backend/typescript/rest/activityRoutes.ts
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;
2 changes: 2 additions & 0 deletions backend/typescript/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import YAML from "yamljs";

import { sequelize } from "./models";
import authRouter from "./rest/authRoutes";
import activityRouter from "./rest/activityRoutes";
import behaviourRouter from "./rest/behaviourRoutes";
import entityRouter from "./rest/entityRoutes";
import simpleEntityRouter from "./rest/simpleEntityRoutes";
Expand All @@ -33,6 +34,7 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use("/auth", authRouter);
app.use("/activities", activityRouter);
app.use("/behaviours", behaviourRouter);
app.use("/entities", entityRouter);
app.use("/simple-entities", simpleEntityRouter);
Expand Down
119 changes: 119 additions & 0 deletions backend/typescript/services/implementations/activityService.ts
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;
54 changes: 54 additions & 0 deletions backend/typescript/services/interfaces/activityService.ts
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>;
}

0 comments on commit 00f5559

Please sign in to comment.