overhauled structure, leaderboardRoute.ts and userRoute.ts working
This commit is contained in:
parent
c2cd74ebe2
commit
fff55edf79
25 changed files with 315 additions and 212 deletions
46
backend/api/package-lock.json
generated
46
backend/api/package-lock.json
generated
|
|
@ -9,8 +9,8 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.1",
|
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-validator": "^6.14.2",
|
||||||
"helmet": "^6.0.1",
|
"helmet": "^6.0.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"pg-promise": "^10.15.4",
|
"pg-promise": "^10.15.4",
|
||||||
|
|
@ -413,6 +413,18 @@
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-validator": {
|
||||||
|
"version": "6.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz",
|
||||||
|
"integrity": "sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"validator": "^13.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
|
@ -533,6 +545,11 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
"node_modules/make-error": {
|
"node_modules/make-error": {
|
||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
|
|
@ -1055,6 +1072,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/validator": {
|
||||||
|
"version": "13.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
|
||||||
|
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|
@ -1408,6 +1433,15 @@
|
||||||
"vary": "~1.1.2"
|
"vary": "~1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"express-validator": {
|
||||||
|
"version": "6.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz",
|
||||||
|
"integrity": "sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"validator": "^13.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
|
@ -1495,6 +1529,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||||
},
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
"make-error": {
|
"make-error": {
|
||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||||
|
|
@ -1857,6 +1896,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
||||||
},
|
},
|
||||||
|
"validator": {
|
||||||
|
"version": "13.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
|
||||||
|
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
|
||||||
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
"author": "jweissen",
|
"author": "jweissen",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.1",
|
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-validator": "^6.14.2",
|
||||||
"helmet": "^6.0.1",
|
"helmet": "^6.0.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"pg-promise": "^10.15.4",
|
"pg-promise": "^10.15.4",
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,14 @@ const port = 3000
|
||||||
|
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
|
|
||||||
|
|
||||||
// configure & use logger
|
// configure & use logger
|
||||||
let morganFormatted = morgan('[:date[iso]] :method :url - :status')
|
let morganFormatted = morgan('[:date[iso]] :method :url - :status')
|
||||||
app.use(morganFormatted);
|
app.use(morganFormatted);
|
||||||
|
|
||||||
|
|
||||||
|
app.use(express.json())
|
||||||
|
|
||||||
app.use('/leaderboard', leaderboardRoute)
|
app.use('/leaderboard', leaderboardRoute)
|
||||||
app.use('/user', userRoute)
|
app.use('/user', userRoute)
|
||||||
|
|
||||||
|
|
@ -21,6 +25,10 @@ app.get('/helloworld', (req, res) => {
|
||||||
res.json({message: "Hello World!"})
|
res.json({message: "Hello World!"})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post('/echo', async (req, res) => {
|
||||||
|
res.json(req.body)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server started at http://localhost:3000`);
|
console.log(`Server started at http://localhost:3000`);
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,33 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import {Database} from "./Database.js";
|
|
||||||
import {TimeLeaderboardManager} from "./manager/TimeLeaderboardManager.js";
|
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 {HighscoreLeaderboardManager} from "./manager/HighscoreLeaderboardManager.js";
|
||||||
import {
|
import {HighscoreLeaderboard, TimeLeaderboard} from "./model/Leaderboard.js";
|
||||||
HighscoreLeaderboardPgPromiseSerializer
|
|
||||||
} from "./serializer/pgpromise/HighscoreLeaderboardPgPromiseSerializer.js";
|
|
||||||
import {TimeLeaderboardPgPromiseSerializer} from "./serializer/pgpromise/TimeLeaderboardPgPromiseSerializer.js";
|
|
||||||
|
|
||||||
export const leaderboardRoute = express.Router()
|
export const leaderboardRoute = express.Router()
|
||||||
|
|
||||||
|
|
||||||
leaderboardRoute.get('/highscore', async (req, res) => {
|
leaderboardRoute.get('/highscore', async (req, res) => {
|
||||||
let data = await Database.db.manyOrNone('SELECT * FROM lb_highscore INNER JOIN "user" ON user_id = id ORDER BY RANK;')
|
try {
|
||||||
const leaderboardManager = new HighscoreLeaderboardManager(data, new HighscoreLeaderboardPgPromiseSerializer);
|
const highscoreLeaderboardManager: HighscoreLeaderboardManager = new HighscoreLeaderboardPgPromiseManager;
|
||||||
res.send(leaderboardManager.content)
|
const highscoreLeaderboard: HighscoreLeaderboard = await highscoreLeaderboardManager.getAll();
|
||||||
|
res.send(highscoreLeaderboard);
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ errors: [{msg: "Internal server error"}]})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
leaderboardRoute.get('/totalplaytime', async (req, res) => {
|
leaderboardRoute.get('/totalplaytime', async (req, res) => {
|
||||||
let data = await Database.db.manyOrNone('SELECT * FROM lb_total_playtime INNER JOIN "user" ON user_id = id ORDER BY RANK;')
|
try {
|
||||||
const leaderboardManager = new TimeLeaderboardManager(data, new TimeLeaderboardPgPromiseSerializer);
|
const timeLeaderboardManager: TimeLeaderboardManager = new TimeLeaderboardPgPromiseManager;
|
||||||
res.send(leaderboardManager.content)
|
const timeLeaderboard: TimeLeaderboard = await timeLeaderboardManager.getAll();
|
||||||
|
res.send(timeLeaderboard);
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ errors: [{msg: "Internal server error"}]})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1,32 +1,6 @@
|
||||||
import {Leaderboard} from "../model/Leaderboard.js";
|
import {HighscoreLeaderboard} from "../model/Leaderboard.js";
|
||||||
import {Manager} from "./Manager.js";
|
import {Manager} from "./Manager.js";
|
||||||
import {HighscoreLeaderboardSerializer} from "../serializer/HighscoreLeaderboardSerializer.js";
|
|
||||||
|
|
||||||
export class HighscoreLeaderboardManager implements Manager<Leaderboard<number>>{
|
|
||||||
private _content: Leaderboard<number>;
|
|
||||||
private _serializer: HighscoreLeaderboardSerializer;
|
|
||||||
|
|
||||||
constructor(rawData: any, serializer: HighscoreLeaderboardSerializer) {
|
|
||||||
this.serializer = serializer;
|
|
||||||
this._content = this.serializer.serialize(rawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
//region getter&setter
|
|
||||||
get content() {
|
|
||||||
return this._content;
|
|
||||||
}
|
|
||||||
|
|
||||||
set content(value: Leaderboard<number>) {
|
|
||||||
this._content = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get serializer() {
|
|
||||||
return this._serializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
set serializer(value: HighscoreLeaderboardSerializer) {
|
|
||||||
this._serializer = value;
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
export abstract class HighscoreLeaderboardManager extends Manager<HighscoreLeaderboard>{
|
||||||
|
abstract getAll(): Promise<HighscoreLeaderboard>;
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import {Serializer} from "../serializer/Serializer.js";
|
export abstract class Manager<T> {
|
||||||
|
//region getter&setter
|
||||||
|
protected abstract serialize(raw: any): T;
|
||||||
|
|
||||||
export interface Manager<T> {
|
protected abstract deserialize(parsed: T): any;
|
||||||
get content(),
|
//endregion
|
||||||
set content(value: T),
|
|
||||||
get serializer(),
|
|
||||||
set serializer(value: Serializer<T>),
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,34 +1,6 @@
|
||||||
import {Manager} from "./Manager.js";
|
import {Manager} from "./Manager.js";
|
||||||
import {Leaderboard} from "../model/Leaderboard.js";
|
import {TimeLeaderboard} from "../model/Leaderboard.js";
|
||||||
import {Time} from "../model/Time.js";
|
|
||||||
import {TimeLeaderboardSerializer} from "../serializer/TimeLeaderboardSerializer.js";
|
|
||||||
import {TimeLeaderboardPgPromiseSerializer} from "../serializer/pgpromise/TimeLeaderboardPgPromiseSerializer.js";
|
|
||||||
|
|
||||||
export class TimeLeaderboardManager implements Manager<Leaderboard<Time>> {
|
|
||||||
private _content: Leaderboard<Time>;
|
|
||||||
private _serializer: TimeLeaderboardPgPromiseSerializer
|
|
||||||
|
|
||||||
constructor(data: any, serializer: TimeLeaderboardSerializer) {
|
|
||||||
this._serializer = serializer;
|
|
||||||
this._content = this._serializer.serialize(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
//region getter&setter
|
|
||||||
get content() {
|
|
||||||
return this._content;
|
|
||||||
}
|
|
||||||
|
|
||||||
set content(value: Leaderboard<Time>) {
|
|
||||||
this._content = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get serializer(): TimeLeaderboardPgPromiseSerializer {
|
|
||||||
return this._serializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
set serializer(value: TimeLeaderboardPgPromiseSerializer) {
|
|
||||||
this._serializer = value;
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
export abstract class TimeLeaderboardManager extends Manager<TimeLeaderboard>{
|
||||||
|
abstract getAll(): Promise<TimeLeaderboard>;
|
||||||
}
|
}
|
||||||
10
backend/api/src/manager/UserManager.ts
Normal file
10
backend/api/src/manager/UserManager.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import {Manager} from "./Manager.js";
|
||||||
|
import {User} from "../model/User.js";
|
||||||
|
|
||||||
|
export abstract class UserManager extends Manager<User>{
|
||||||
|
abstract getById(id: number): Promise<User>;
|
||||||
|
abstract getByName(name: string): Promise<User>;
|
||||||
|
abstract withIdExists(userId: number): Promise<boolean>;
|
||||||
|
abstract withNameExists(username: string): Promise<boolean>
|
||||||
|
abstract insert(user: Omit<User, 'id'>): Promise<User>;
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,6 @@
|
||||||
import {UserScores} from "../model/UserScores.js";
|
import {UserScores} from "../model/UserScores.js";
|
||||||
import { Serializer } from "../serializer/Serializer.js";
|
|
||||||
import {UserScoresSerializer} from "../serializer/UserScoresSerializer.js";
|
|
||||||
import {Manager} from "./Manager.js";
|
import {Manager} from "./Manager.js";
|
||||||
|
|
||||||
export class UserScoresManager implements Manager<UserScores> {
|
export abstract class UserScoresManager extends Manager<UserScores>{
|
||||||
private _content: UserScores;
|
abstract getById(userId: number): Promise<UserScores>;
|
||||||
private _serializer: UserScoresSerializer;
|
|
||||||
|
|
||||||
constructor(rawData: any, serializer: UserScoresSerializer) {
|
|
||||||
this.serializer = serializer;
|
|
||||||
this._content = this.serializer.serialize(rawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
//region getter&setter
|
|
||||||
get content() {
|
|
||||||
return this._content;
|
|
||||||
}
|
|
||||||
|
|
||||||
set content(value: UserScores) {
|
|
||||||
this._content = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get serializer() {
|
|
||||||
return this._serializer
|
|
||||||
}
|
|
||||||
|
|
||||||
set serializer(value: Serializer<UserScores>) {
|
|
||||||
this._serializer = value
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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<HighscoreLeaderboard> {
|
||||||
|
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<number> = {
|
||||||
|
rank: item.rank,
|
||||||
|
username: item.name,
|
||||||
|
score: item.highscore,
|
||||||
|
}
|
||||||
|
return newItem;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected deserialize(parsed: HighscoreLeaderboard): any {
|
||||||
|
throw new Error("Mthod not implemented.");
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {TimeLeaderboardManager} from "../TimeLeaderboardManager.js";
|
||||||
|
import {LeaderboardEntry, TimeLeaderboard} from "../../model/Leaderboard.js";
|
||||||
|
import {Database} from "../../Database.js";
|
||||||
|
|
||||||
|
export class TimeLeaderboardPgPromiseManager extends TimeLeaderboardManager {
|
||||||
|
async getAll(): Promise<TimeLeaderboard> {
|
||||||
|
const raw: any = await Database.db.manyOrNone(
|
||||||
|
'SELECT * FROM lb_total_playtime INNER JOIN "user" ON user_id = id ORDER BY RANK;'
|
||||||
|
);
|
||||||
|
return this.serialize(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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<string> = {
|
||||||
|
rank: item.rank,
|
||||||
|
username: item.name,
|
||||||
|
score: item.total_playtime,
|
||||||
|
}
|
||||||
|
return newItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
53
backend/api/src/manager/pgPromise/UserPgPromiseManager.ts
Normal file
53
backend/api/src/manager/pgPromise/UserPgPromiseManager.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import {UserManager} from "../UserManager.js";
|
||||||
|
import {User} from "../../model/User.js";
|
||||||
|
import {Database} from "../../Database.js";
|
||||||
|
|
||||||
|
export class UserPgPromiseManager extends UserManager {
|
||||||
|
async getById(id: number): Promise<User> {
|
||||||
|
const raw = await Database.db.oneOrNone(
|
||||||
|
'SELECT * FROM "user" WHERE id = $1;', id
|
||||||
|
);
|
||||||
|
return this.serialize(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByName(name: string): Promise<User> {
|
||||||
|
const raw = await Database.db.oneOrNone(
|
||||||
|
'SELECT * FROM "user" WHERE name = $1;', name
|
||||||
|
);
|
||||||
|
return this.serialize(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
async withIdExists(id: number): Promise<boolean> {
|
||||||
|
const response = await Database.db.oneOrNone(
|
||||||
|
'SELECT count(*) AS row_count FROM "user" WHERE id = $1;', id
|
||||||
|
);
|
||||||
|
return response.row_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async withNameExists(name: string): Promise<boolean> {
|
||||||
|
const response = await Database.db.oneOrNone(
|
||||||
|
'SELECT count(*) AS row_count FROM "user" WHERE name = $1;', name
|
||||||
|
);
|
||||||
|
return response.row_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(user: Omit<User, 'id'>): Promise<User> {
|
||||||
|
const raw = await Database.db.oneOrNone(
|
||||||
|
'INSERT INTO "user" (name) VALUES (${name}) RETURNING *;', user
|
||||||
|
);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import {UserScoresManager} from "../UserScoresManager.js";
|
||||||
|
import {UserScores} from "../../model/UserScores.js";
|
||||||
|
import {Database} from "../../Database.js";
|
||||||
|
|
||||||
|
export class UserScoresPgPromiseManager extends UserScoresManager {
|
||||||
|
public async getById(id: number): Promise<UserScores> {
|
||||||
|
const raw = await Database.db.oneOrNone(
|
||||||
|
'SELECT * FROM user_scores WHERE user_id = $1;', id
|
||||||
|
);
|
||||||
|
return this.serialize(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serialize(raw: any): UserScores {
|
||||||
|
return {
|
||||||
|
userId: raw.user_id,
|
||||||
|
highscore: raw.highscore,
|
||||||
|
totalScore: raw.total_score,
|
||||||
|
totalPlaytime: raw.total_playtime,
|
||||||
|
averageScore: raw.average_score,
|
||||||
|
gamesPlayed: raw.games_played,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected deserialize(parsed: UserScores): any {
|
||||||
|
throw new Error("Method not implemented.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
export type Leaderboard<T> = LeaderboardEntry<T>[];
|
export type Leaderboard<T> = LeaderboardEntry<T>[];
|
||||||
|
|
||||||
|
export type HighscoreLeaderboard = Leaderboard<number>;
|
||||||
|
export type TimeLeaderboard = Leaderboard<string>;
|
||||||
|
|
||||||
export interface LeaderboardEntry<T> {
|
export interface LeaderboardEntry<T> {
|
||||||
|
username: number,
|
||||||
rank: number,
|
rank: number,
|
||||||
username: string,
|
|
||||||
score: T,
|
score: T,
|
||||||
}
|
}
|
||||||
4
backend/api/src/model/User.ts
Normal file
4
backend/api/src/model/User.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface User {
|
||||||
|
id?: number,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import {Time} from "./Time.js";
|
||||||
|
|
||||||
export interface UserScores {
|
export interface UserScores {
|
||||||
username: string,
|
userId: number,
|
||||||
highscore: number,
|
highscore: number,
|
||||||
totalScore: number,
|
totalScore: number,
|
||||||
totalPlaytime: string,
|
totalPlaytime: Time,
|
||||||
averageScore: number,
|
averageScore: number,
|
||||||
gamesPlayed: number,
|
gamesPlayed: number,
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import {Serializer} from "./Serializer.js";
|
|
||||||
import {Leaderboard} from "../model/Leaderboard.js";
|
|
||||||
|
|
||||||
export interface HighscoreLeaderboardSerializer extends Serializer<Leaderboard<number>> {}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export interface Serializer<T> {
|
|
||||||
serialize(rawData: any): T,
|
|
||||||
deserialize(parsedData: T): any,
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import {Serializer} from "./Serializer.js";
|
|
||||||
import {Leaderboard} from "../model/Leaderboard.js";
|
|
||||||
import {Time} from "../model/Time.js";
|
|
||||||
|
|
||||||
export interface TimeLeaderboardSerializer extends Serializer<Leaderboard<Time>> {}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import {UserScores} from "../model/UserScores.js";
|
|
||||||
import {Serializer} from "./Serializer.js";
|
|
||||||
|
|
||||||
export interface UserScoresSerializer extends Serializer<UserScores>{}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import {HighscoreLeaderboardSerializer} from "../HighscoreLeaderboardSerializer.js";
|
|
||||||
import {Leaderboard, LeaderboardEntry} from "../../model/Leaderboard.js";
|
|
||||||
|
|
||||||
export class HighscoreLeaderboardPgPromiseSerializer implements HighscoreLeaderboardSerializer {
|
|
||||||
deserialize(parsedData: Leaderboard<number>): any {
|
|
||||||
throw new Error("Method not implemented.")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(rawData: any): Leaderboard<number> {
|
|
||||||
return rawData.map((item) => {
|
|
||||||
let newItem: LeaderboardEntry<number> = {
|
|
||||||
rank: item.rank,
|
|
||||||
username: item.name,
|
|
||||||
score: item.highscore,
|
|
||||||
}
|
|
||||||
return newItem
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import {TimeLeaderboardSerializer} from "../TimeLeaderboardSerializer.js";
|
|
||||||
import {Leaderboard, LeaderboardEntry} from "../../model/Leaderboard.js";
|
|
||||||
import {Time} from "../../model/Time.js";
|
|
||||||
|
|
||||||
export class TimeLeaderboardPgPromiseSerializer implements TimeLeaderboardSerializer {
|
|
||||||
deserialize(parsedData: Leaderboard<Time>): any {
|
|
||||||
throw new Error("Method not implemented.")
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(rawData: any): Leaderboard<Time> {
|
|
||||||
return rawData.map((item) => {
|
|
||||||
let newItem: LeaderboardEntry<Time> = {
|
|
||||||
rank: item.rank,
|
|
||||||
username: item.name,
|
|
||||||
score: item.total_playtime,
|
|
||||||
}
|
|
||||||
return newItem
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import {UserScores} from "../../model/UserScores.js";
|
|
||||||
import {UserScoresSerializer} from "../UserScoresSerializer.js";
|
|
||||||
|
|
||||||
export class UserScoresPgPromiseSerializer implements UserScoresSerializer {
|
|
||||||
deserialize(parsedData: UserScores): any {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(rawData: any): UserScores {
|
|
||||||
return {
|
|
||||||
username: rawData.name,
|
|
||||||
highscore: rawData.highscore,
|
|
||||||
totalScore: rawData.total_score,
|
|
||||||
totalPlaytime: rawData.total_playtime,
|
|
||||||
averageScore: rawData.averageScore,
|
|
||||||
gamesPlayed: rawData.games_played,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +1,73 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {Database} from "./Database.js";
|
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 {UserScoresManager} from "./manager/UserScoresManager.js";
|
||||||
import {UserScoresPgPromiseSerializer} from "./serializer/pgpromise/UserScoresPgPromiseSerializer.js";
|
import {User} from "./model/User.js";
|
||||||
import bodyParser from "body-parser";
|
|
||||||
|
|
||||||
export const userRoute = express.Router()
|
export const userRoute = express.Router()
|
||||||
|
|
||||||
userRoute.use(bodyParser.json)
|
userRoute.post(
|
||||||
|
'/register',
|
||||||
|
body('name')
|
||||||
|
.isString()
|
||||||
|
.isLength({min: 3, max: 32})
|
||||||
|
.matches('[a-zA-Z0-9_.\\- ]*'),
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
//region validate parameters
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({ errors: errors.array() });
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
userRoute.get('/:username/scores', async (req, res) => {
|
const username: string = req.body.name;
|
||||||
let data = await Database.db.oneOrNone(
|
const userManager: UserManager = new UserPgPromiseManager();
|
||||||
'SELECT * FROM user_scores INNER JOIN "user" ON user_scores.user_id = "user".id WHERE "user".name = $1;',
|
|
||||||
[req.params.username])
|
|
||||||
.catch((error) => console.log(error.message)
|
|
||||||
)
|
|
||||||
let userDataManager: UserScoresManager = new UserScoresManager(data, new UserScoresPgPromiseSerializer);
|
|
||||||
res.json(userDataManager.content);
|
|
||||||
})
|
|
||||||
|
|
||||||
userRoute.post('/register', async (req, res) => {
|
// check if username already exists
|
||||||
if (req.body.name == undefined) {
|
if (await userManager.withNameExists(username)) {
|
||||||
res.status(400).send("'name' was not defined");
|
return res.status(400).json({ errors: [{msg: `User with name '${username}' already exists.`}] })
|
||||||
return;
|
}
|
||||||
|
// insert & return user
|
||||||
|
const inserted: User = await userManager.insert({name: username});
|
||||||
|
res.json(inserted);
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ errors: [{msg: "Internal server error"}]})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Database.db.none(
|
)
|
||||||
'INSERT INTO "user" (name) VALUES ($1);', [req.body.name]
|
|
||||||
);
|
userRoute.get('/:userId/scores',
|
||||||
res.status(200).send();
|
param('userId').isInt({min: 1}),
|
||||||
})
|
async (req, res) => {
|
||||||
|
//region validate parameters
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({ errors: errors.array() });
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
const userId: number = req.params.userId;
|
||||||
|
const userManager: UserManager = new UserPgPromiseManager;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// check if user with given id exists
|
||||||
|
if (!await userManager.withIdExists(userId)) {
|
||||||
|
return res.status(400).json({ errors: [{msg: `User with id ${userId} does not exist.`}] })
|
||||||
|
}
|
||||||
|
// get & return data
|
||||||
|
const userScoresManager: UserScoresManager = new UserScoresPgPromiseManager;
|
||||||
|
const userScores = await userScoresManager.getById(userId);
|
||||||
|
res.json(userScores);
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ errors: [{msg: "Internal server error"}]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ CREATE TABLE "user" (
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE user_scores (
|
CREATE TABLE user_scores (
|
||||||
user_id INT PRIMARY KEY REFERENCES "user",
|
user_id INT PRIMARY KEY,
|
||||||
highscore INT NOT NULL DEFAULT 0,
|
highscore INT NOT NULL DEFAULT 0,
|
||||||
total_score INT NOT NULL DEFAULT 0,
|
total_score INT NOT NULL DEFAULT 0,
|
||||||
total_playtime TIME NOT NULL DEFAULT '00:00:00',
|
total_playtime TIME NOT NULL DEFAULT '00:00:00',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue