Skip to content

Commit

Permalink
Add PATCH /api/v1/users/session
Browse files Browse the repository at this point in the history
  • Loading branch information
Cow-Van committed Jan 12, 2024
1 parent 9f0ed09 commit 968c411
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 13 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"mysql2": "^3.6.5",
"rimraf": "^5.0.5",
"winston": "^3.11.0"
Expand Down
57 changes: 55 additions & 2 deletions src/api/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import express from "express";

import User, { getUserByPassword, updateUser } from "../../models/User.model";
import { createSession, getSessionsByUserId } from "../../models/Session.model";
import { createSession, getSessionsByUserId, updateSession } from "../../models/Session.model";
import Time, { isOverlappingPreviousTimes } from "../../utils/time";
import { InvalidTimeError, RowNotFoundError } from "../../utils/errors";

const router = express.Router();

Expand Down Expand Up @@ -114,7 +115,59 @@ router.post("/session", async (req, res) => {
);

return res.status(200).json({
description: "Amended new session",
description: "Amended new session!",
});
});

router.patch("/session", async (req, res) => {
const user: User = res.locals.user;

if (!req.body.start_time || !req.body.end_time || !req.body.session_id) {
return res.status(400).json({
description: "Missing start and/or end times!",
});
}

let editedSessionTime: Time;

try {
editedSessionTime = new Time(req.body.start_time, req.body.end_time);
} catch (err) {
if (err instanceof InvalidTimeError) {
return res.status(400).json({
description: "Start time must be less than end time!",
});
}

throw err;
}

const previousSessions = await getSessionsByUserId(user.user_id);
let previousSessionTimes: Time[] = [];

for (const session of previousSessions) {
if (session.session_id === req.body.session_id) {
continue;
}

previousSessionTimes.push(
new Time(session.start_time, session.end_time)
);
}

if (isOverlappingPreviousTimes(editedSessionTime, previousSessionTimes)) {
return res.status(400).json({
description: "Edited session overlaps with existing session!",
});
}

await updateSession(req.body.session_id, {
start_time: editedSessionTime.startTime,
end_time: editedSessionTime.endTime,
});

return res.status(200).json({
description: "Edited session!",
});
});

Expand Down
9 changes: 6 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
require("dotenv").config();
import dotenv from "dotenv";
dotenv.config();

import express from "express";
import bodyParser from "body-parser";
require("express-async-errors"); // Adds error handling for async functions, unnecessary Express v5+

import logger from "./utils/logger";
import api from "./api";
import middleware from "./utils/middleware";

const app = express();

app.use(bodyParser.json());
app.use(middleware.json);
app.use(middleware.errorHandler);
app.use("/api/v1", api);

app.listen(process.env.PORT, () => {
Expand Down
3 changes: 2 additions & 1 deletion src/models/Config.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ResultSetHeader, RowDataPacket } from "mysql2";

import database from "../database";
import { RowNotFoundError } from "../utils/errors";

interface Config {
name: string;
Expand All @@ -21,7 +22,7 @@ async function getConfigByName(name: string): Promise<Config> {
const [configs] = await database.query<ConfigRowDataPacket[]>(sql, [name]);

if (configs.length < 1) {
throw new RowNotFoundError("Config not found in table: configs");
throw new RowNotFoundError("Config not found in table: configs!");
}

return configs[0];
Expand Down
3 changes: 2 additions & 1 deletion src/models/Session.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ResultSetHeader, RowDataPacket } from "mysql2";

import database from "../database";
import { RowNotFoundError } from "../utils/errors";
import updateBuilder from "../utils/updateBuilder";

interface Session {
Expand Down Expand Up @@ -29,7 +30,7 @@ async function getSessionsByUserId(
]);

if (sessions.length < 1) {
throw new RowNotFoundError("Session not found in table: sessions");
throw new RowNotFoundError("Session not found in table: sessions!");
}

return sessions;
Expand Down
3 changes: 2 additions & 1 deletion src/models/User.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ResultSetHeader, RowDataPacket } from "mysql2";
import database from "../database";
import { RowNotFoundError } from "../utils/errors";
import updateBuilder from "../utils/updateBuilder";

interface User {
Expand All @@ -26,7 +27,7 @@ async function getUserByPassword(password: number): Promise<User> {
const [users] = await database.query<UserRowDataPacket[]>(sql, [password]);

if (users.length < 1) {
throw new RowNotFoundError("User not found in table: users");
throw new RowNotFoundError("User not found in table: users!");
}

return users[0];
Expand Down
27 changes: 22 additions & 5 deletions src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
class RowNotFoundError extends Error {
constructor(msg: string) {
class CustomError extends Error {
readonly statusCode: number | undefined;

constructor(msg: string, statusCode?: number) {
super(msg);
Object.setPrototypeOf(this, CustomError.prototype);

this.statusCode = statusCode;
}
}

class RowNotFoundError extends CustomError {
constructor(msg: string, statusCode?: number) {
super(msg, statusCode);
Object.setPrototypeOf(this, RowNotFoundError.prototype);
}
}

class InvalidTimeError extends Error {
constructor(msg: string) {
super(msg);
class InvalidTimeError extends CustomError {
constructor(msg: string, statusCode?: number) {
super(msg, statusCode);
Object.setPrototypeOf(this, InvalidTimeError.prototype);
}
}

export {
CustomError,
RowNotFoundError,
InvalidTimeError,
}
20 changes: 20 additions & 0 deletions src/utils/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express, { ErrorRequestHandler } from "express";
import { CustomError } from "./errors";
import logger from "./logger";

const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
if (err instanceof CustomError && !!err.statusCode) {
logger.warn(err.message)
return res.status(err.statusCode).json({
description: err.message,
});
} else {
logger.error(err);
return res.sendStatus(500);
}
} // TODO: Error handling

export default { // TODO: CORS
json: express.json(),
errorHandler: errorHandler,
}
3 changes: 3 additions & 0 deletions src/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Read only class that stores start and end times

import { InvalidTimeError } from "./errors";

// End time must be greater than start time
class Time {
readonly startTime: number;
Expand Down

0 comments on commit 968c411

Please sign in to comment.