diff --git a/backend/api/src/app.ts b/backend/api/src/app.ts index ae7a775..281b190 100644 --- a/backend/api/src/app.ts +++ b/backend/api/src/app.ts @@ -3,6 +3,7 @@ import helmet from "helmet"; import morgan from 'morgan'; import {leaderboardRoute} from "./leaderboardRoute.js"; import {userRoute} from "./userRoute.js"; +import {gameRoute} from "./gameRoute.js"; const app = express() @@ -10,24 +11,13 @@ const port = 3000 app.use(helmet()) - // configure & use logger let morganFormatted = morgan('[:date[iso]] :method :url - :status') app.use(morganFormatted); - -app.use(express.json()) - app.use('/leaderboard', leaderboardRoute) app.use('/user', userRoute) - -app.get('/helloworld', (req, res) => { - res.json({message: "Hello World!"}) -}) - -app.post('/echo', async (req, res) => { - res.json(req.body) -}) +app.use('/game', gameRoute) app.listen(port, () => { diff --git a/backend/api/src/gameRoute.ts b/backend/api/src/gameRoute.ts new file mode 100644 index 0000000..1b2c581 --- /dev/null +++ b/backend/api/src/gameRoute.ts @@ -0,0 +1,50 @@ +import express from "express"; +import {GameRepository} from "./repositories/GameRepository.js"; +import {GamePgPromiseRepository} from "./repositories/pgPromise/GamePgPromiseRepository.js"; +import {Game} from "./model/Game.js"; +import {body, CustomValidator, validationResult} from "express-validator"; +import {UserRepository} from "./repositories/UserRepository.js"; +import {UserPgPromiseRepository} from "./repositories/pgPromise/UserPgPromiseRepository.js"; + +export const gameRoute = express.Router() + +gameRoute.use(express.json()) + +const userWithIdExists: CustomValidator = userId => { + try { + const userRepo: UserRepository = new UserPgPromiseRepository; + return userRepo.withIdExists(userId).then(exists => { + if (!exists) return Promise.reject("User does not exist"); + }); + } catch (error) { + console.log(error); + } +} + +gameRoute.post( + '/add', + body('playtime') + .matches("([0-5]\\d:)?[0-5]\\d:[0-5]\\d"), + body('date') + .isDate(), + body('userId') + .isInt({min: 1}) + .custom(userWithIdExists), + async (req, res) => { + try { + //region validate parameters + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + //endregion + let game: Game = req.body; + const gameRepo: GameRepository = new GamePgPromiseRepository; + const inserted: Game = await gameRepo.insert(game); + res.send(inserted); + } catch (error) { + // handle errors + console.log(error) + res.status(500).json({ errors: [{msg: "Internal server error"}]}) + } +}) \ No newline at end of file diff --git a/backend/api/src/leaderboardRoute.ts b/backend/api/src/leaderboardRoute.ts index aaa0da9..01940ae 100644 --- a/backend/api/src/leaderboardRoute.ts +++ b/backend/api/src/leaderboardRoute.ts @@ -1,16 +1,15 @@ import express from 'express'; -import {TimeLeaderboardManager} from "./manager/TimeLeaderboardManager.js"; -import {TimeLeaderboardPgPromiseManager} from "./manager/pgPromise/TimeLeaderboardPgPromiseManager.js"; -import {HighscoreLeaderboardPgPromiseManager} from "./manager/pgPromise/HighscoreLeaderboardPgPromiseManager.js"; -import {HighscoreLeaderboardManager} from "./manager/HighscoreLeaderboardManager.js"; +import {TimeLeaderboardRepository} from "./repositories/TimeLeaderboardRepository.js"; +import {TimeLeaderboardPgPromiseRepository} from "./repositories/pgPromise/TimeLeaderboardPgPromiseRepository.js"; +import {HighscoreLeaderboardPgPromiseRepository} from "./repositories/pgPromise/HighscoreLeaderboardPgPromiseRepository.js"; +import {HighscoreLeaderboardRepository} from "./repositories/HighscoreLeaderboardRepository.js"; import {HighscoreLeaderboard, TimeLeaderboard} from "./model/Leaderboard.js"; export const leaderboardRoute = express.Router() - leaderboardRoute.get('/highscore', async (req, res) => { try { - const highscoreLeaderboardManager: HighscoreLeaderboardManager = new HighscoreLeaderboardPgPromiseManager; + const highscoreLeaderboardManager: HighscoreLeaderboardRepository = new HighscoreLeaderboardPgPromiseRepository; const highscoreLeaderboard: HighscoreLeaderboard = await highscoreLeaderboardManager.getAll(); res.send(highscoreLeaderboard); } catch (error) { @@ -22,7 +21,7 @@ leaderboardRoute.get('/highscore', async (req, res) => { leaderboardRoute.get('/totalplaytime', async (req, res) => { try { - const timeLeaderboardManager: TimeLeaderboardManager = new TimeLeaderboardPgPromiseManager; + const timeLeaderboardManager: TimeLeaderboardRepository = new TimeLeaderboardPgPromiseRepository; const timeLeaderboard: TimeLeaderboard = await timeLeaderboardManager.getAll(); res.send(timeLeaderboard); } catch (error) { diff --git a/backend/api/src/manager/HighscoreLeaderboardManager.ts b/backend/api/src/manager/HighscoreLeaderboardManager.ts deleted file mode 100644 index 995fc00..0000000 --- a/backend/api/src/manager/HighscoreLeaderboardManager.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {HighscoreLeaderboard} from "../model/Leaderboard.js"; -import {Manager} from "./Manager.js"; - -export abstract class HighscoreLeaderboardManager extends Manager{ - abstract getAll(): Promise; -} \ No newline at end of file diff --git a/backend/api/src/manager/Manager.ts b/backend/api/src/manager/Manager.ts deleted file mode 100644 index 1538078..0000000 --- a/backend/api/src/manager/Manager.ts +++ /dev/null @@ -1,7 +0,0 @@ -export abstract class Manager { - //region getter&setter - protected abstract serialize(raw: any): T; - - protected abstract deserialize(parsed: T): any; - //endregion -} \ No newline at end of file diff --git a/backend/api/src/manager/TimeLeaderboardManager.ts b/backend/api/src/manager/TimeLeaderboardManager.ts deleted file mode 100644 index 6b6f0b2..0000000 --- a/backend/api/src/manager/TimeLeaderboardManager.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {Manager} from "./Manager.js"; -import {TimeLeaderboard} from "../model/Leaderboard.js"; - -export abstract class TimeLeaderboardManager extends Manager{ - abstract getAll(): Promise; -} \ No newline at end of file diff --git a/backend/api/src/manager/pgPromise/HighscoreLeaderboardPgPromiseManager.ts b/backend/api/src/manager/pgPromise/HighscoreLeaderboardPgPromiseManager.ts deleted file mode 100644 index acdb9c2..0000000 --- a/backend/api/src/manager/pgPromise/HighscoreLeaderboardPgPromiseManager.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {HighscoreLeaderboardManager} from "../HighscoreLeaderboardManager.js"; -import {HighscoreLeaderboard, LeaderboardEntry} from "../../model/Leaderboard.js"; -import {Database} from "../../Database.js"; - -export class HighscoreLeaderboardPgPromiseManager extends HighscoreLeaderboardManager{ - async getAll(): Promise { - const raw: any = await Database.db.manyOrNone( - 'SELECT * FROM lb_highscore INNER JOIN "user" ON user_id = id ORDER BY RANK;' - ); - return this.serialize(raw); - } - - //region serialization - protected serialize(raw: any): HighscoreLeaderboard { - return raw.map((item) => { - let newItem: LeaderboardEntry = { - rank: item.rank, - username: item.name, - score: item.highscore, - } - return newItem; - }); - } - - protected deserialize(parsed: HighscoreLeaderboard): any { - throw new Error("Mthod not implemented."); - } - //endregion -} \ No newline at end of file diff --git a/backend/api/src/model/Game.ts b/backend/api/src/model/Game.ts new file mode 100644 index 0000000..ac7df6f --- /dev/null +++ b/backend/api/src/model/Game.ts @@ -0,0 +1,7 @@ +export interface Game { + id?: number, + score: number, + playtime: string, + date: Date, + userId: number, +} \ No newline at end of file diff --git a/backend/api/src/repositories/GameRepository.ts b/backend/api/src/repositories/GameRepository.ts new file mode 100644 index 0000000..05069b6 --- /dev/null +++ b/backend/api/src/repositories/GameRepository.ts @@ -0,0 +1,5 @@ +import {Game} from "../model/Game.js"; + +export abstract class GameRepository { + abstract insert(game: Game): Promise; +} \ No newline at end of file diff --git a/backend/api/src/repositories/HighscoreLeaderboardRepository.ts b/backend/api/src/repositories/HighscoreLeaderboardRepository.ts new file mode 100644 index 0000000..204feff --- /dev/null +++ b/backend/api/src/repositories/HighscoreLeaderboardRepository.ts @@ -0,0 +1,5 @@ +import {HighscoreLeaderboard} from "../model/Leaderboard.js"; + +export abstract class HighscoreLeaderboardRepository { + abstract getAll(): Promise; +} \ No newline at end of file diff --git a/backend/api/src/repositories/TimeLeaderboardRepository.ts b/backend/api/src/repositories/TimeLeaderboardRepository.ts new file mode 100644 index 0000000..ef9d8fe --- /dev/null +++ b/backend/api/src/repositories/TimeLeaderboardRepository.ts @@ -0,0 +1,5 @@ +import {TimeLeaderboard} from "../model/Leaderboard.js"; + +export abstract class TimeLeaderboardRepository { + abstract getAll(): Promise; +} \ No newline at end of file diff --git a/backend/api/src/manager/UserManager.ts b/backend/api/src/repositories/UserRepository.ts similarity index 77% rename from backend/api/src/manager/UserManager.ts rename to backend/api/src/repositories/UserRepository.ts index e8dbcce..699ba98 100644 --- a/backend/api/src/manager/UserManager.ts +++ b/backend/api/src/repositories/UserRepository.ts @@ -1,7 +1,6 @@ -import {Manager} from "./Manager.js"; import {User} from "../model/User.js"; -export abstract class UserManager extends Manager{ +export abstract class UserRepository { abstract getById(id: number): Promise; abstract getByName(name: string): Promise; abstract withIdExists(userId: number): Promise; diff --git a/backend/api/src/manager/UserScoresManager.ts b/backend/api/src/repositories/UserScoresRepository.ts similarity index 51% rename from backend/api/src/manager/UserScoresManager.ts rename to backend/api/src/repositories/UserScoresRepository.ts index 973699b..26bdd48 100644 --- a/backend/api/src/manager/UserScoresManager.ts +++ b/backend/api/src/repositories/UserScoresRepository.ts @@ -1,6 +1,5 @@ import {UserScores} from "../model/UserScores.js"; -import {Manager} from "./Manager.js"; -export abstract class UserScoresManager extends Manager{ +export abstract class UserScoresRepository { abstract getById(userId: number): Promise; } diff --git a/backend/api/src/repositories/pgPromise/GamePgPromiseRepository.ts b/backend/api/src/repositories/pgPromise/GamePgPromiseRepository.ts new file mode 100644 index 0000000..ce92435 --- /dev/null +++ b/backend/api/src/repositories/pgPromise/GamePgPromiseRepository.ts @@ -0,0 +1,24 @@ +import {GameRepository} from "../GameRepository.js"; +import {Game} from "../../model/Game.js"; +import {Database} from "../../Database.js"; + +export class GamePgPromiseRepository extends GameRepository{ + public async insert(game: Game): Promise { + const raw: any = await Database.db.oneOrNone( + 'INSERT INTO game (score, playtime, date, user_id) VALUES ($(score), $(playtime), $(date), $(userId)) RETURNING *;', + game + ); + return this.serialize(raw); + } + + serialize(raw: any): Game { + return { + id: raw.id, + score: raw.score, + playtime: raw.playtime, + date: raw.date, + userId: raw.userId, + }; + } + +} \ No newline at end of file diff --git a/backend/api/src/repositories/pgPromise/HighscoreLeaderboardPgPromiseRepository.ts b/backend/api/src/repositories/pgPromise/HighscoreLeaderboardPgPromiseRepository.ts new file mode 100644 index 0000000..8a7747c --- /dev/null +++ b/backend/api/src/repositories/pgPromise/HighscoreLeaderboardPgPromiseRepository.ts @@ -0,0 +1,23 @@ +import {HighscoreLeaderboardRepository} from "../HighscoreLeaderboardRepository.js"; +import {HighscoreLeaderboard, LeaderboardEntry} from "../../model/Leaderboard.js"; +import {Database} from "../../Database.js"; + +export class HighscoreLeaderboardPgPromiseRepository extends HighscoreLeaderboardRepository { + async getAll(): Promise { + const raw: any = await Database.db.manyOrNone( + 'SELECT * FROM lb_highscore INNER JOIN "user" ON user_id = id ORDER BY RANK;' + ); + return this.serialize(raw); + } + + protected serialize(raw: any): HighscoreLeaderboard { + return raw.map((item) => { + let newItem: LeaderboardEntry = { + rank: item.rank, + username: item.name, + score: item.highscore, + } + return newItem; + }); + } +} \ No newline at end of file diff --git a/backend/api/src/manager/pgPromise/TimeLeaderboardPgPromiseManager.ts b/backend/api/src/repositories/pgPromise/TimeLeaderboardPgPromiseRepository.ts similarity index 73% rename from backend/api/src/manager/pgPromise/TimeLeaderboardPgPromiseManager.ts rename to backend/api/src/repositories/pgPromise/TimeLeaderboardPgPromiseRepository.ts index 1a25ee1..1345562 100644 --- a/backend/api/src/manager/pgPromise/TimeLeaderboardPgPromiseManager.ts +++ b/backend/api/src/repositories/pgPromise/TimeLeaderboardPgPromiseRepository.ts @@ -1,8 +1,8 @@ -import {TimeLeaderboardManager} from "../TimeLeaderboardManager.js"; +import {TimeLeaderboardRepository} from "../TimeLeaderboardRepository.js"; import {LeaderboardEntry, TimeLeaderboard} from "../../model/Leaderboard.js"; import {Database} from "../../Database.js"; -export class TimeLeaderboardPgPromiseManager extends TimeLeaderboardManager { +export class TimeLeaderboardPgPromiseRepository extends TimeLeaderboardRepository { async getAll(): Promise { const raw: any = await Database.db.manyOrNone( 'SELECT * FROM lb_total_playtime INNER JOIN "user" ON user_id = id ORDER BY RANK;' @@ -11,10 +11,6 @@ export class TimeLeaderboardPgPromiseManager extends TimeLeaderboardManager { } //region serialization - protected deserialize(parsed: TimeLeaderboard): any { - throw new Error("Method not implemented.") - } - protected serialize(raw: any): TimeLeaderboard { return raw.map((item) => { let newItem: LeaderboardEntry = { diff --git a/backend/api/src/manager/pgPromise/UserPgPromiseManager.ts b/backend/api/src/repositories/pgPromise/UserPgPromiseRepository.ts similarity index 84% rename from backend/api/src/manager/pgPromise/UserPgPromiseManager.ts rename to backend/api/src/repositories/pgPromise/UserPgPromiseRepository.ts index 0e518c6..8b801a9 100644 --- a/backend/api/src/manager/pgPromise/UserPgPromiseManager.ts +++ b/backend/api/src/repositories/pgPromise/UserPgPromiseRepository.ts @@ -1,8 +1,8 @@ -import {UserManager} from "../UserManager.js"; +import {UserRepository} from "../UserRepository.js"; import {User} from "../../model/User.js"; import {Database} from "../../Database.js"; -export class UserPgPromiseManager extends UserManager { +export class UserPgPromiseRepository extends UserRepository { async getById(id: number): Promise { const raw = await Database.db.oneOrNone( 'SELECT * FROM "user" WHERE id = $1;', id @@ -38,16 +38,10 @@ export class UserPgPromiseManager extends UserManager { return this.serialize(raw); } - //region serialization protected serialize(raw: any): User { return { id: raw.id, name: raw.name }; } - - protected deserialize(parsed: User): any { - throw new Error("Method not implemented.") - } - //endregion } \ No newline at end of file diff --git a/backend/api/src/manager/pgPromise/UserScoresPgPromiseManager.ts b/backend/api/src/repositories/pgPromise/UserScoresPgPromiseRepository.ts similarity index 73% rename from backend/api/src/manager/pgPromise/UserScoresPgPromiseManager.ts rename to backend/api/src/repositories/pgPromise/UserScoresPgPromiseRepository.ts index b147463..0b89318 100644 --- a/backend/api/src/manager/pgPromise/UserScoresPgPromiseManager.ts +++ b/backend/api/src/repositories/pgPromise/UserScoresPgPromiseRepository.ts @@ -1,8 +1,8 @@ -import {UserScoresManager} from "../UserScoresManager.js"; +import {UserScoresRepository} from "../UserScoresRepository.js"; import {UserScores} from "../../model/UserScores.js"; import {Database} from "../../Database.js"; -export class UserScoresPgPromiseManager extends UserScoresManager { +export class UserScoresPgPromiseRepository extends UserScoresRepository { public async getById(id: number): Promise { const raw = await Database.db.oneOrNone( 'SELECT * FROM user_scores WHERE user_id = $1;', id @@ -20,8 +20,4 @@ export class UserScoresPgPromiseManager extends UserScoresManager { gamesPlayed: raw.games_played, }; } - - protected deserialize(parsed: UserScores): any { - throw new Error("Method not implemented.") - } } \ No newline at end of file diff --git a/backend/api/src/userRoute.ts b/backend/api/src/userRoute.ts index 4849c59..305a203 100644 --- a/backend/api/src/userRoute.ts +++ b/backend/api/src/userRoute.ts @@ -1,13 +1,13 @@ import express from "express"; import { body, param, validationResult } from 'express-validator'; -import {UserScoresPgPromiseManager} from "./manager/pgPromise/UserScoresPgPromiseManager.js"; -import {UserPgPromiseManager} from "./manager/pgPromise/UserPgPromiseManager.js"; -import {UserManager} from "./manager/UserManager.js"; -import {UserScoresManager} from "./manager/UserScoresManager.js"; +import {UserScoresPgPromiseRepository} from "./repositories/pgPromise/UserScoresPgPromiseRepository.js"; +import {UserPgPromiseRepository} from "./repositories/pgPromise/UserPgPromiseRepository.js"; +import {UserRepository} from "./repositories/UserRepository.js"; +import {UserScoresRepository} from "./repositories/UserScoresRepository.js"; import {User} from "./model/User.js"; - export const userRoute = express.Router() +userRoute.use(express.json()) userRoute.post( '/register', @@ -23,9 +23,8 @@ userRoute.post( return res.status(400).json({ errors: errors.array() }); } //endregion - const username: string = req.body.name; - const userManager: UserManager = new UserPgPromiseManager(); + const userManager: UserRepository = new UserPgPromiseRepository(); // check if username already exists if (await userManager.withNameExists(username)) { @@ -53,7 +52,7 @@ userRoute.get('/:userId/scores', //endregion const userId: number = req.params.userId; - const userManager: UserManager = new UserPgPromiseManager; + const userManager: UserRepository = new UserPgPromiseRepository; try { // check if user with given id exists @@ -61,7 +60,7 @@ userRoute.get('/:userId/scores', return res.status(400).json({ errors: [{msg: `User with id ${userId} does not exist.`}] }) } // get & return data - const userScoresManager: UserScoresManager = new UserScoresPgPromiseManager; + const userScoresManager: UserScoresRepository = new UserScoresPgPromiseRepository; const userScores = await userScoresManager.getById(userId); res.json(userScores); } catch (error) {