Skip to content

Commit

Permalink
Add POST /api/v1/users/sign-in route
Browse files Browse the repository at this point in the history
  • Loading branch information
Cow-Van committed Jan 12, 2024
1 parent 1237094 commit 8c4fcdf
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 65 deletions.
9 changes: 9 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from "express"

import usersRouter from "./routes/users";

const router = express.Router();

router.use("/users", usersRouter);

export default router;
53 changes: 53 additions & 0 deletions src/api/routes/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import express from "express";
import { createSession } from "../../models/Session.model";

import User, { getUserByPassword, updateUser } from "../../models/User.model";


const router = express.Router();

// Middleware to check if a password in passed in the body
router.use(async (req, res, next) => {
if (!req.body.password) {
return res.status(401).json({
description: "Missing Password",
});
}

let user;

try {
user = await getUserByPassword(req.body.password);
} catch (err) {
if (err instanceof RowNotFoundError) {
return res.status(403).json({
description: err.message,
});
}

return res.sendStatus(500);
}

res.locals.user = user;
return next();
});

router.post("/sign-in", async (_req, res) => {
const user: User = res.locals.user;

if (user.signed_in) {
return res.status(400).json({
description: `${user.first_name} ${user.last_name} is already signed in!`,
});
}

await updateUser(user.user_id, { signed_in: true, last_signed_in: Date.now() });

return res.status(200).json({
description: `Signed in as ${user.first_name} ${user.last_name}`
});
});



export default router;
3 changes: 3 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ require("dotenv").config()
import express from "express";

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


const app = express();

app.use("/api/v1", api);

app.listen(process.env.PORT, () => {
logger.info(`Listening on port ${process.env.PORT}!`);
})
24 changes: 15 additions & 9 deletions src/models/Config.model.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { config } from "dotenv";
import { ResultSetHeader, RowDataPacket } from "mysql2";

import database from "../database";

interface Config extends RowDataPacket {
interface Config {
name: string;
value: string;
}

async function getAllConfigs(): Promise<Config[]> {
interface ConfigRowDataPacket extends Config, RowDataPacket {}

async function getAllConfigs(): Promise<ConfigRowDataPacket[]> {
const sql = "SELECT * FROM `configs`";
const [configs] = await database.query<Config[]>(sql);
const [configs] = await database.query<ConfigRowDataPacket[]>(sql);

return configs;
}

async function getConfigByName(name: string): Promise<Config> {
const sql = "SELECT * FROM `configs` WHERE name = ?";
const [configs] = await database.query<Config[]>(sql, [name]);
const [configs] = await database.query<ConfigRowDataPacket[]>(sql, [name]);

if (configs.length < 1) {
throw new RowNotFoundError(
Expand All @@ -30,22 +31,27 @@ async function getConfigByName(name: string): Promise<Config> {

async function createConfigs(
newConfigs: { name: string; value: string }[]
): Promise<Config[]> {
): Promise<ConfigRowDataPacket[]> {
const sql =
"INSERT INTO `configs` (name, value) VALUES ? RETURNING name, value";
const params = newConfigs.map((newConfig) => [
newConfig.name,
newConfig.value,
]);
const [configs] = await database.query<Config[]>(sql, [params]);
const [configs] = await database.query<ConfigRowDataPacket[]>(sql, [
params,
]);

return configs;
}

async function createConfig(name: string, value: string): Promise<Config> {
const sql =
"INSERT INTO `configs` (name, value) VALUES (?, ?) RETURNING name, value";
const [configs] = await database.query<Config[]>(sql, [name, value]);
const [configs] = await database.query<ConfigRowDataPacket[]>(sql, [
name,
value,
]);

return configs[0];
}
Expand All @@ -62,7 +68,7 @@ async function updateConfig(name: string, newValue: string): Promise<boolean> {

async function deleteConfig(name: string): Promise<Config> {
const sql = "DELETE FROM `configs` WHERE name = ? RETURNING name, value";
const [configs] = await database.query<Config[]>(sql, [name]);
const [configs] = await database.query<ConfigRowDataPacket[]>(sql, [name]);

return configs[0];
}
Expand Down
41 changes: 19 additions & 22 deletions src/models/Session.model.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { ResultSetHeader, RowDataPacket } from "mysql2";

import database from "../database";
import updateBuilder from "../utils/updateBuilder";

interface Session extends RowDataPacket {
interface Session {
session_id: number;
user_id: number;
start_time: bigint;
end_time: bigint;
start_time: number;
end_time: number;
amended: boolean;
}

async function getAllSessions(): Promise<Session[]> {
interface SessionRowDataPacket extends Session, RowDataPacket {}

async function getAllSessions(): Promise<SessionRowDataPacket[]> {
const sql = "SELECT * FROM `sessions`";
const [sessions] = await database.query<Session[]>(sql);
const [sessions] = await database.query<SessionRowDataPacket[]>(sql);

return sessions;
}

async function getSessionsByPassword(password: number): Promise<Session[]> {
async function getSessionsByPassword(password: number): Promise<SessionRowDataPacket[]> {
const sql =
"SELECT * FROM `sessions` WHERE user_id = (SELECT user_id from users WHERE password = ? )";
const [sessions] = await database.query<Session[]>(sql, [password]);
const [sessions] = await database.query<SessionRowDataPacket[]>(sql, [password]);

if (sessions.length < 1) {
throw new RowNotFoundError(`Session not found in table: sessions`);
Expand All @@ -30,9 +34,9 @@ async function getSessionsByPassword(password: number): Promise<Session[]> {

async function createSession(
password: number,
start_time: bigint,
start_time: number,
amended: boolean,
end_time?: bigint
end_time?: number
): Promise<boolean> {
let sql: string;
let params: any[];
Expand All @@ -48,21 +52,14 @@ async function createSession(
}
const [resHeader] = await database.query<ResultSetHeader>(sql, params);

return resHeader.affectedRows == 1;
return resHeader.affectedRows === 1;
}

async function updateSession(
password: number,
end_time: bigint
): Promise<boolean> {
const sql =
"UPDATE `sessions` SET end_time = ? WHERE user_id = (SELECT user_id from 'users' WHERE password = ?)";
const [resHeader] = await database.query<ResultSetHeader>(sql, [
end_time,
password,
]);
async function updateSession(session_id: number, values: Partial<Session>): Promise<boolean> {
const update = updateBuilder("sessions", values, { session_id });
const [resHeader] = await database.query<ResultSetHeader>(update.query, update.params);

return resHeader.affectedRows == 1;
return resHeader.affectedRows === 1;
}

async function deleteSession(session_id: number): Promise<boolean> {
Expand All @@ -71,7 +68,7 @@ async function deleteSession(session_id: number): Promise<boolean> {
session_id,
]);

return resHeader.affectedRows == 1;
return resHeader.affectedRows === 1;
}

async function deleteSessionsByUserPassword(
Expand Down
49 changes: 16 additions & 33 deletions src/models/User.model.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import { ResultSetHeader, RowDataPacket } from "mysql2";
import database from "../database";
import updateBuilder from "../utils/updateBuilder";

interface User extends RowDataPacket {
interface User {
user_id: number;
first_name: string;
last_name: string;
password: number;
signed_in: boolean;
last_signed_in: bigint;
total_time: bigint;
last_signed_in: number;
total_time: number;
}

async function getAllUsers(): Promise<User[]> {
interface UserRowDataPacket extends User, RowDataPacket {};

async function getAllUsers(): Promise<UserRowDataPacket[]> {
const sql = "SELECT * FROM `users`";
const [users] = await database.query<User[]>(sql);
const [users] = await database.query<UserRowDataPacket[]>(sql);

return users;
}

async function getUserByPassword(password: number): Promise<User> {
const sql = "SELECT * FROM `users` WHERE password = ?";
const [users] = await database.query<User[]>(sql, [password]);
const [users] = await database.query<UserRowDataPacket[]>(sql, [password]);

if (users.length < 1) {
throw new RowNotFoundError("User not found in table: users");
Expand All @@ -36,7 +39,7 @@ async function createUser(
): Promise<User> {
const sql =
"INSERT INTO `users` (first_name, last_name, password) VALUES (?, ?, ?); SELECT user_id, first_name, last_name FROM users where password = ?";
const [users] = await database.query<User[]>(sql, [
const [users] = await database.query<UserRowDataPacket[]>(sql, [
first_name,
last_name,
password,
Expand All @@ -46,45 +49,25 @@ async function createUser(
return users[0];
}

async function updateUserTotalTime(password: number): Promise<boolean> {
const sql =
"UPDATE users SET total_time = (SELECT SUM(CASE WHEN end_time - start_time < 43200000 THEN end_time - start_time ELSE 0 END) FROM sessions WHERE user_id = (SELECT user_id FROM users WHERE password = ?)); SELECT user_id, first_name, last_name from users where password = ?";
const [resHeader] = await database.query<ResultSetHeader>(sql, [
password,
password,
]);

return resHeader.affectedRows == 1;
}

async function updateUserLastSignedIn(
password: number,
newValue: bigint
): Promise<boolean> {
const sql =
"UPDATE `users` SET last_signed_in = ? WHERE password = ?; SELECT user_id, first_name, last_name, last_signed_in FROM `users` where password = ?";
const [resHeader] = await database.query<ResultSetHeader>(sql, [
newValue,
password,
password,
]);
async function updateUser(user_id: number, values: Partial<User>): Promise<boolean> {
const update = updateBuilder("users", values, { user_id });
const [resHeader] = await database.query<ResultSetHeader>(update.query, update.params);

return resHeader.affectedRows == 1;
return resHeader.affectedRows === 1;
}

async function deleteUser(password: number): Promise<boolean> {
const sql = "DELETE FROM `users` WHERE password = ?";
const [resHeader] = await database.query<ResultSetHeader>(sql, [password]);

return resHeader.affectedRows == 1;
return resHeader.affectedRows === 1;
}

export default User;
export {
getAllUsers,
getUserByPassword,
createUser,
updateUserTotalTime,
updateUserLastSignedIn,
updateUser,
deleteUser,
};
2 changes: 1 addition & 1 deletion src/tests/database/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import database from "../../database/";


describe("SQL Database Accessor", () => {
it("should be connected to database", async () => {
it("is connected to database", async () => {
const response: any = await database.query("SHOW STATUS WHERE `variable_name` = 'Threads_running'") // See how many threads are currently running
const threadsRunning = response[0][0]["Value"];

Expand Down
50 changes: 50 additions & 0 deletions src/tests/utils/updateBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from "chai";

import updateBuilder from "../../utils/updateBuilder";


describe("SQL Update Query Builder", () => {
it("returns full prepared statement given table, updates, and conditions", () => {
const table = "users";
const updates = {
first_name: "fod",
last_name: "bart",
bool_test: true
};
const conditions = {
user_id: 2,
password: "pwd",
};

const update = updateBuilder(table, updates, conditions);

expect(update).to.be.deep.equal({
query: "UPDATE ? SET ? WHERE ?",
params: [
"users",
"first_name = 'fod', last_name = 'bart', bool_test = true",
"user_id = 2 AND password = 'pwd'",
],
});
});

it("returns prepared statement without WHERE given table and updates", () => {
const table = "sessions";
const updates = {
start_time: 3408539567,
end_time: 948534534,
amended: true,
str_test: "blah blah blah",
};

const update = updateBuilder(table, updates);

expect(update).to.be.deep.equal({
query: "UPDATE ? SET ?",
params: [
"sessions",
"start_time = 3408539567, end_time = 948534534, amended = true, str_test = 'blah blah blah'",
],
});
});
});
Loading

0 comments on commit 8c4fcdf

Please sign in to comment.