feat(tournament): better frontend and database handling
This commit is contained in:
parent
ca618d64ca
commit
43e3b9af26
41 changed files with 2484 additions and 278 deletions
|
|
@ -6,13 +6,15 @@ import { IUserDb, UserImpl } from './mixin/user';
|
|||
import { IBlockedDb, BlockedImpl } from './mixin/blocked';
|
||||
import { ITicTacToeDb, TicTacToeImpl } from './mixin/tictactoe';
|
||||
import { IPongDb, PongImpl } from './mixin/pong';
|
||||
import { ITournamentDb, TournamentImpl } from './mixin/tournament';
|
||||
|
||||
Object.assign(DbImpl.prototype, UserImpl);
|
||||
Object.assign(DbImpl.prototype, BlockedImpl);
|
||||
Object.assign(DbImpl.prototype, TicTacToeImpl);
|
||||
Object.assign(DbImpl.prototype, PongImpl);
|
||||
Object.assign(DbImpl.prototype, TournamentImpl);
|
||||
|
||||
export interface Database extends DbImpl, IUserDb, IBlockedDb, ITicTacToeDb, IPongDb { }
|
||||
export interface Database extends DbImpl, IUserDb, IBlockedDb, ITicTacToeDb, IPongDb, ITournamentDb { }
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
declare module 'fastify' {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
----------------
|
||||
-- AUTH --
|
||||
----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
login TEXT UNIQUE,
|
||||
|
|
@ -10,6 +14,10 @@ CREATE TABLE IF NOT EXISTS user (
|
|||
allow_guest_message INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
----------------
|
||||
-- CHAT --
|
||||
----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blocked (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
|
|
@ -19,19 +27,27 @@ CREATE TABLE IF NOT EXISTS blocked (
|
|||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair ON blocked (user, blocked);
|
||||
|
||||
----------------
|
||||
-- TICTACTOE --
|
||||
----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tictactoe (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
time TEXT NOT NULL default (datetime('now')),
|
||||
playerX TEXT NOT NULL,
|
||||
playerO TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL,
|
||||
FOREIGN KEY(playerX) REFERENCES user(id),
|
||||
FOREIGN KEY(playerO) REFERENCES user(id)
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
time TEXT NOT NULL default (datetime ('now')),
|
||||
playerX TEXT NOT NULL,
|
||||
playerO TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL,
|
||||
FOREIGN KEY (playerX) REFERENCES user (id),
|
||||
FOREIGN KEY (playerO) REFERENCES user (id)
|
||||
);
|
||||
|
||||
----------------
|
||||
-- PONG --
|
||||
----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pong (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
time TEXT NOT NULL default (datetime('now')),
|
||||
time TEXT NOT NULL default (datetime ('now')),
|
||||
playerL TEXT NOT NULL,
|
||||
playerR TEXT NOT NULL,
|
||||
scoreL INTEGER NOT NULL,
|
||||
|
|
@ -41,3 +57,32 @@ CREATE TABLE IF NOT EXISTS pong (
|
|||
FOREIGN KEY (playerL) REFERENCES user (id),
|
||||
FOREIGN KEY (playerR) REFERENCES user (id)
|
||||
);
|
||||
|
||||
----------------
|
||||
-- TOURNAMENT --
|
||||
----------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tournament (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
time TEXT NOT NULL default (datetime ('now')),
|
||||
owner TEXT NOT NULL,
|
||||
FOREIGN KEY (owner) REFERENCES user (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tour_user (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
tournament TEXT NOT NULL,
|
||||
nickname TEXT NOT NULL,
|
||||
score INTEGER NOT NULL,
|
||||
FOREIGN KEY (user) REFERENCES user (id),
|
||||
FOREIGN KEY (tournament) REFERENCES tournament (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tour_game (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
tournament TEXT NOT NULL,
|
||||
game TEXT NOT NULL,
|
||||
FOREIGN KEY (game) REFERENCES pong (id),
|
||||
FOREIGN KEY (tournament) REFERENCES tournament (id)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ type PongGameTable = {
|
|||
local: number,
|
||||
};
|
||||
|
||||
function pongGameFromRow(r: Partial<PongGameTable> | undefined): PongGame | undefined {
|
||||
export function pongGameFromRow(r: Partial<PongGameTable> | undefined): PongGame | undefined {
|
||||
if (isNullish(r)) return undefined;
|
||||
if (isNullish(r.id)) return undefined;
|
||||
if (isNullish(r.playerL)) return undefined;
|
||||
|
|
|
|||
133
src/@shared/src/database/mixin/tournament.ts
Normal file
133
src/@shared/src/database/mixin/tournament.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import UUID, { newUUID } from '@shared/utils/uuid';
|
||||
import type { Database } from './_base';
|
||||
import { UserId } from './user';
|
||||
import { PongGame, pongGameFromRow, PongGameId } from './pong';
|
||||
import { isNullish } from '@shared/utils';
|
||||
|
||||
// never use this directly
|
||||
|
||||
// describe every function in the object
|
||||
export interface ITournamentDb extends Database {
|
||||
getTournamentById(
|
||||
this: ITournamentDb,
|
||||
id: TournamentId,
|
||||
): TournamentData | null,
|
||||
createNewTournamentById(
|
||||
this: ITournamentDb,
|
||||
owner: UserId,
|
||||
users: { id: UserId, name: string, score: number }[],
|
||||
games: PongGameId[],
|
||||
): void,
|
||||
getAllTournamentsData(this: ITournamentDb): TournamentTable[],
|
||||
getLastTournament(this: ITournamentDb): TournamentTable | undefined;
|
||||
};
|
||||
|
||||
export const TournamentImpl: Omit<ITournamentDb, keyof Database> = {
|
||||
/**
|
||||
* whole function description
|
||||
*
|
||||
* @param id the argument description
|
||||
*
|
||||
* @returns what does the function return ?
|
||||
*/
|
||||
getTournamentById(
|
||||
this: ITournamentDb,
|
||||
id: TournamentId,
|
||||
): TournamentData | null {
|
||||
// Fetch tournament
|
||||
const tournament = this
|
||||
.prepare('SELECT id, time, owner FROM tournament WHERE id = @id')
|
||||
.get({ id }) as TournamentTable;
|
||||
|
||||
if (!tournament) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch games
|
||||
|
||||
const games = this.prepare(`
|
||||
SELECT
|
||||
pong.*,
|
||||
userL.name AS nameL,
|
||||
userR.name AS nameR
|
||||
FROM
|
||||
tour_game
|
||||
INNER JOIN pong
|
||||
ON pong.id == tour_game.game
|
||||
INNER JOIN user AS userL
|
||||
ON pong.playerL = userL.id
|
||||
INNER JOIN user AS userR
|
||||
ON pong.playerR = userR.id
|
||||
WHERE
|
||||
tour_game.tournament = @id
|
||||
ORDER BY pong.id`).all({ id })
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.map((s: any) => {
|
||||
const g: (PongGame & { nameL?: string, nameR?: string }) | undefined = pongGameFromRow(s);
|
||||
if (isNullish(g)) return undefined;
|
||||
g.nameL = s.nameL;
|
||||
g.nameR = s.nameR;
|
||||
if (isNullish(g.nameL) || isNullish(g.nameR)) return undefined;
|
||||
return g as PongGame & { nameL: string, nameR: string };
|
||||
}).filter(v => !isNullish(v));
|
||||
;
|
||||
|
||||
// Fetch users
|
||||
const users = this.prepare('SELECT id, user, tournament, nickname, score FROM tour_user WHERE tournament = @id').all({ id }) as TournamentUser[];
|
||||
|
||||
return {
|
||||
...tournament,
|
||||
games,
|
||||
users,
|
||||
};
|
||||
},
|
||||
|
||||
createNewTournamentById(
|
||||
this: ITournamentDb,
|
||||
owner: UserId,
|
||||
users: { id: UserId, name: string, score: number }[],
|
||||
games: PongGameId[],
|
||||
): void {
|
||||
const tournamentId = newUUID() as TournamentId;
|
||||
|
||||
this.prepare('INSERT INTO tournament (id, owner) VALUES (@id, @owner)').run({ id: tournamentId, owner });
|
||||
for (const u of users) {
|
||||
this.prepare('INSERT INTO tour_user (user, nickname, score, tournament) VALUES (@id, @name, @score, @tournament)').run({ id: u.id, name: u.name, score: u.score, tournament: tournamentId });
|
||||
}
|
||||
for (const g of games) {
|
||||
this.prepare('INSERT INTO tour_game (tournament, game) VALUES (@tournament, @game)').run({ tournament: tournamentId, game: g });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getAllTournamentsData(this: ITournamentDb): TournamentTable[] {
|
||||
return this.prepare('SELECT * FROM tournament ORDER BY rowid').all() as TournamentTable[];
|
||||
},
|
||||
|
||||
getLastTournament(this: ITournamentDb): TournamentTable | undefined {
|
||||
return this.prepare('SELECT * FROM tournament ORDER BY rowid LIMIT 1').get() as TournamentTable | undefined;
|
||||
},
|
||||
};
|
||||
|
||||
export type TournamentId = UUID & { readonly __uuid: unique symbol };
|
||||
|
||||
export interface TournamentTable {
|
||||
id: TournamentId;
|
||||
time: string;
|
||||
owner: UserId;
|
||||
}
|
||||
|
||||
export interface TournamentUser {
|
||||
user: UserId;
|
||||
tournament: TournamentId;
|
||||
nickname: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export type TournamentGame = PongGame & { nameL: string, nameR: string };
|
||||
|
||||
export interface TournamentData extends TournamentTable {
|
||||
games: TournamentGame[];
|
||||
users: TournamentUser[];
|
||||
}
|
||||
|
||||
366
src/openapi.json
366
src/openapi.json
|
|
@ -2144,9 +2144,9 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/createPausedGame": {
|
||||
"/api/pong/createPausedGame": {
|
||||
"post": {
|
||||
"operationId": "pongCreatePauseGame",
|
||||
"operationId": "createPauseGame",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
|
@ -2451,28 +2451,20 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/startPausedGame": {
|
||||
"post": {
|
||||
"operationId": "pongstartPauseGame",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "'id' | <gameid>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"/api/pong/tournament/{id}": {
|
||||
"get": {
|
||||
"operationId": "TournamentData",
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"description": "the tournament id"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
|
|
@ -2493,18 +2485,188 @@
|
|||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"startPausedGame.success"
|
||||
"tournamentData.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner",
|
||||
"users",
|
||||
"games",
|
||||
"time"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "ownerId"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"nickname"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"nickname": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"games": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId",
|
||||
"left",
|
||||
"right",
|
||||
"local",
|
||||
"date",
|
||||
"outcome"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "gameId"
|
||||
},
|
||||
"left": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcome": {
|
||||
"enum": [
|
||||
"winL",
|
||||
"winR",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
|
|
@ -2523,7 +2685,157 @@
|
|||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"startPausedGame.no_such_game"
|
||||
"tournamentData.failure.notFound"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"openapi_other"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/pong/tournament/": {
|
||||
"get": {
|
||||
"operationId": "TournamentList",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"tournamentList.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"owner",
|
||||
"time"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "tournamentId"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "ownerId"
|
||||
},
|
||||
"time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"failure"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"tournamentList.failure.generic"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
"schemas": {}
|
||||
},
|
||||
"paths": {
|
||||
"/createPausedGame": {
|
||||
"/api/pong/createPausedGame": {
|
||||
"post": {
|
||||
"operationId": "pongCreatePauseGame",
|
||||
"operationId": "createPauseGame",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
|
@ -309,28 +309,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/startPausedGame": {
|
||||
"post": {
|
||||
"operationId": "pongstartPauseGame",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "'id' | <gameid>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"/api/pong/tournament/{id}": {
|
||||
"get": {
|
||||
"operationId": "TournamentData",
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"description": "the tournament id"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
|
|
@ -351,18 +343,188 @@
|
|||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"startPausedGame.success"
|
||||
"tournamentData.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owner",
|
||||
"users",
|
||||
"games",
|
||||
"time"
|
||||
],
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "ownerId"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"nickname"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"nickname": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"games": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId",
|
||||
"left",
|
||||
"right",
|
||||
"local",
|
||||
"date",
|
||||
"outcome"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "gameId"
|
||||
},
|
||||
"left": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"score",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcome": {
|
||||
"enum": [
|
||||
"winL",
|
||||
"winR",
|
||||
"other"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
|
|
@ -381,7 +543,154 @@
|
|||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"startPausedGame.no_such_game"
|
||||
"tournamentData.failure.notFound"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/pong/tournament/": {
|
||||
"get": {
|
||||
"operationId": "TournamentList",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"tournamentList.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"owner",
|
||||
"time"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "tournamentId"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "ownerId"
|
||||
},
|
||||
"time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"notLoggedIn"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"auth.noCookie",
|
||||
"auth.invalidKind",
|
||||
"auth.noUser",
|
||||
"auth.invalid"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"failure"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"tournamentList.failure.generic"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
101
src/pong/src/routes/tournamentData.ts
Normal file
101
src/pong/src/routes/tournamentData.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { TournamentId } from '@shared/database/mixin/tournament';
|
||||
import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils';
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { Static, Type } from 'typebox';
|
||||
|
||||
const TournamentDataParams = Type.Object({
|
||||
id: Type.String({ description: 'the tournament id' }),
|
||||
});
|
||||
|
||||
type TournamentDataParams = Static<typeof TournamentDataParams>;
|
||||
const TournamentDataResponse = {
|
||||
'200': typeResponse('success', 'tournamentData.success', {
|
||||
data: Type.Object({
|
||||
owner: Type.String({ description: 'ownerId' }),
|
||||
users: Type.Array(
|
||||
Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
nickname: Type.String(),
|
||||
}),
|
||||
),
|
||||
games: Type.Array(
|
||||
Type.Object({
|
||||
gameId: Type.String({ description: 'gameId' }),
|
||||
left: Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
name: Type.String(),
|
||||
}),
|
||||
right: Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
name: Type.String(),
|
||||
}),
|
||||
local: Type.Boolean(),
|
||||
date: Type.String(),
|
||||
outcome: Type.Enum(['winL', 'winR', 'other']),
|
||||
}),
|
||||
),
|
||||
time: Type.String(),
|
||||
}),
|
||||
}),
|
||||
'404': typeResponse('failure', 'tournamentData.failure.notFound'),
|
||||
};
|
||||
type TournamentDataResponse = MakeStaticResponse<typeof TournamentDataResponse>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<{ Params: TournamentDataParams }>(
|
||||
'/api/pong/tournament/:id',
|
||||
{
|
||||
schema: {
|
||||
params: TournamentDataParams,
|
||||
response: TournamentDataResponse,
|
||||
operationId: 'TournamentData',
|
||||
},
|
||||
config: { requireAuth: true },
|
||||
},
|
||||
async function(req, res) {
|
||||
const tourId = req.params.id;
|
||||
const data = this.db.getTournamentById(tourId as TournamentId);
|
||||
if (isNullish(data)) {
|
||||
return res.makeResponse(
|
||||
404,
|
||||
'failure',
|
||||
'tournamentData.failure.notFound',
|
||||
);
|
||||
}
|
||||
const typed_res: TournamentDataResponse['200']['payload']['data'] =
|
||||
{
|
||||
owner: data.owner,
|
||||
time: data.time,
|
||||
users: data.users.map((v) => ({
|
||||
nickname: v.nickname,
|
||||
score: v.score,
|
||||
id: v.user,
|
||||
})),
|
||||
games: data.games.map((v) => ({
|
||||
gameId: v.id,
|
||||
left: {
|
||||
score: v.left.score,
|
||||
id: v.left.id,
|
||||
name: `${v.nameL}-left`,
|
||||
},
|
||||
right: {
|
||||
score: v.right.score,
|
||||
id: v.right.id,
|
||||
name: `${v.nameR}-right`,
|
||||
},
|
||||
local: v.local,
|
||||
date: v.time.toString(),
|
||||
outcome: v.outcome,
|
||||
})),
|
||||
};
|
||||
console.log(JSON.stringify(typed_res));
|
||||
return res.makeResponse(200, 'success', 'tournamentData.success', {
|
||||
data: typed_res,
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
export default route;
|
||||
71
src/pong/src/routes/tournamentList.ts
Normal file
71
src/pong/src/routes/tournamentList.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { MakeStaticResponse, typeResponse } from '@shared/utils';
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { Type } from 'typebox';
|
||||
|
||||
const TournamentListResponse = {
|
||||
'200': typeResponse('success', 'tournamentList.success', {
|
||||
data: Type.Array(
|
||||
Type.Object({
|
||||
id: Type.String({ description: 'tournamentId' }),
|
||||
owner: Type.String({ description: 'ownerId' }),
|
||||
time: Type.String(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
'404': typeResponse('failure', 'tournamentList.failure.generic'),
|
||||
};
|
||||
/*
|
||||
const TournamentListResponse = {
|
||||
'200': typeResponse('success', 'tournamentHistory.success', {
|
||||
data: Type.Array(
|
||||
Type.Object({
|
||||
owner: Type.String({ description: 'ownerId' }),
|
||||
users: Type.Array(Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
name: Type.String(),
|
||||
})),
|
||||
game: Type.Object({
|
||||
gameId: Type.String({ description: 'gameId' }),
|
||||
left: Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
name: Type.String(),
|
||||
}),
|
||||
right: Type.Object({
|
||||
score: Type.Integer(),
|
||||
id: Type.String(),
|
||||
name: Type.String(),
|
||||
}),
|
||||
local: Type.Boolean(),
|
||||
date: Type.String(),
|
||||
outcome: Type.Enum(['winL', 'winR', 'other']),
|
||||
}),
|
||||
date: Type.String(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
'404': typeResponse('failure', 'tournamentHistory.failure.generic'),
|
||||
};
|
||||
*/
|
||||
type TournamentListResponse = MakeStaticResponse<typeof TournamentListResponse>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get(
|
||||
'/api/pong/tournament/',
|
||||
{
|
||||
schema: {
|
||||
response: TournamentListResponse,
|
||||
operationId: 'TournamentList',
|
||||
},
|
||||
config: { requireAuth: true },
|
||||
},
|
||||
async function(req, res) {
|
||||
void req;
|
||||
const typed_data: TournamentListResponse['200']['payload']['data'] = this.db.getAllTournamentsData();
|
||||
|
||||
return res.makeResponse(200, 'success', 'tournamentHistory.success', { data: typed_data });
|
||||
},
|
||||
);
|
||||
};
|
||||
export default route;
|
||||
|
|
@ -208,6 +208,7 @@ class StateI {
|
|||
|
||||
private cleanupTournament() {
|
||||
if (this.tournament === null) return;
|
||||
if (this.tournament.state === 'ended') { this.fastify.db.createNewTournamentById(this.tournament.owner, this.tournament.users.values().toArray(), this.tournament.games); }
|
||||
this.tournament = null;
|
||||
this.fastify.log.info('Tournament has been ended');
|
||||
}
|
||||
|
|
@ -305,6 +306,8 @@ class StateI {
|
|||
const user = this.users.get(sock.authUser.id);
|
||||
if (!user) return;
|
||||
|
||||
if (this.tournament && this.tournament.users.has(sock.authUser.id)) return;
|
||||
|
||||
const gameId = newUUID() as unknown as GameId;
|
||||
const g = Pong.makeLocal(user.id);
|
||||
const iState: GameUpdate = StateI.getGameUpdateData(gameId, g);
|
||||
|
|
@ -538,6 +541,8 @@ class StateI {
|
|||
|
||||
if (this.users.get(socket.authUser.id)?.currentGame !== null) return;
|
||||
|
||||
if (this.tournament && this.tournament.users.has(socket.authUser.id)) return;
|
||||
|
||||
this.queue.add(socket.authUser.id);
|
||||
socket.emit('queueEvent', 'registered');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export class Tournament {
|
|||
public matchup: [UserId, UserId][] = [];
|
||||
public state: TournamentState = 'prestart';
|
||||
public startTimeout: NodeJS.Timeout | undefined;
|
||||
public games: PongGameId[] = [];
|
||||
|
||||
constructor(public owner: UserId) { }
|
||||
|
||||
|
|
@ -69,6 +70,7 @@ export class Tournament {
|
|||
game.onEnd = () => this.gameEnd();
|
||||
}
|
||||
this.currentGame = gameId;
|
||||
this.games.push(gameId);
|
||||
}
|
||||
else {
|
||||
this.state = 'ended';
|
||||
|
|
@ -78,15 +80,9 @@ export class Tournament {
|
|||
}
|
||||
|
||||
public gameEnd() {
|
||||
console.log(this);
|
||||
State.fastify.log.info('tournament game ended');
|
||||
if (!isNullish(this.currentGame)) {
|
||||
State.fastify.log.info('HERE2');
|
||||
State.fastify.log.info(State.games);
|
||||
State.fastify.log.info(this.currentGame);
|
||||
const game = State.games.get(this.currentGame);
|
||||
if (game) {
|
||||
State.fastify.log.info('HERE3');
|
||||
const winner = game.checkWinner();
|
||||
const winnerId = winner === 'left' ? game.userLeft : game.userRight;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue