From 40dea32048000a0ee7e4cd5bad23e5e098ef696a Mon Sep 17 00:00:00 2001 From: Maieul BOYER Date: Tue, 6 Jan 2026 16:23:23 +0100 Subject: [PATCH] feat(pong): added history api to get list of games --- .../api/generated/.openapi-generator/FILES | 5 + .../src/api/generated/apis/OpenapiOtherApi.ts | 64 ++++++ .../models/PongHistory200Response.ts | 110 +++++++++ .../models/PongHistory200ResponsePayload.ts | 74 +++++++ .../PongHistory200ResponsePayloadDataInner.ts | 131 +++++++++++ ...gHistory200ResponsePayloadDataInnerLeft.ts | 84 +++++++ .../models/PongHistory404Response.ts | 93 ++++++++ frontend/src/api/generated/models/index.ts | 5 + src/@shared/src/database/mixin/pong.ts | 51 +++-- src/openapi.json | 208 ++++++++++++++++++ src/pong/openapi.json | 208 +++++++++++++++++- src/pong/src/routes/broadcast.ts | 60 ----- src/pong/src/routes/pongHistory.ts | 68 ++++++ src/redocly.yaml | 2 + 14 files changed, 1089 insertions(+), 74 deletions(-) create mode 100644 frontend/src/api/generated/models/PongHistory200Response.ts create mode 100644 frontend/src/api/generated/models/PongHistory200ResponsePayload.ts create mode 100644 frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInner.ts create mode 100644 frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInnerLeft.ts create mode 100644 frontend/src/api/generated/models/PongHistory404Response.ts delete mode 100644 src/pong/src/routes/broadcast.ts create mode 100644 src/pong/src/routes/pongHistory.ts diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index 3f9a357..8ab2789 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -47,6 +47,11 @@ models/LoginOtp500Response.ts models/LoginOtpRequest.ts models/LoginRequest.ts models/Logout200Response.ts +models/PongHistory200Response.ts +models/PongHistory200ResponsePayload.ts +models/PongHistory200ResponsePayloadDataInner.ts +models/PongHistory200ResponsePayloadDataInnerLeft.ts +models/PongHistory404Response.ts models/ProviderList200Response.ts models/ProviderList200ResponsePayload.ts models/ProviderList200ResponsePayloadListInner.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index 5a31d09..7d116bf 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -54,6 +54,8 @@ import type { LoginOtpRequest, LoginRequest, Logout200Response, + PongHistory200Response, + PongHistory404Response, ProviderList200Response, Signin200Response, Signin400Response, @@ -141,6 +143,10 @@ import { LoginRequestToJSON, Logout200ResponseFromJSON, Logout200ResponseToJSON, + PongHistory200ResponseFromJSON, + PongHistory200ResponseToJSON, + PongHistory404ResponseFromJSON, + PongHistory404ResponseToJSON, ProviderList200ResponseFromJSON, ProviderList200ResponseToJSON, Signin200ResponseFromJSON, @@ -185,6 +191,10 @@ export interface LoginOtpOperationRequest { loginOtpRequest: LoginOtpRequest; } +export interface PongHistoryRequest { + user: string; +} + export interface SigninRequest { loginRequest: LoginRequest; } @@ -823,6 +833,60 @@ export class OpenapiOtherApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async pongHistoryRaw(requestParameters: PongHistoryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['user'] == null) { + throw new runtime.RequiredError( + 'user', + 'Required parameter "user" was null or undefined when calling pongHistory().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + + let urlPath = `/api/pong/history/{user}`; + urlPath = urlPath.replace(`{${"user"}}`, encodeURIComponent(String(requestParameters['user']))); + + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // Object response for status 200 + return new runtime.JSONApiResponse(response, (jsonValue) => PongHistory200ResponseFromJSON(jsonValue)); + } + if (response.status === 401) { + // Object response for status 401 + return new runtime.JSONApiResponse(response, (jsonValue) => StatusOtp401ResponseFromJSON(jsonValue)); + } + if (response.status === 404) { + // Object response for status 404 + return new runtime.JSONApiResponse(response, (jsonValue) => PongHistory404ResponseFromJSON(jsonValue)); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 404`); + } + + /** + */ + async pongHistory(requestParameters: PongHistoryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.pongHistoryRaw(requestParameters, initOverrides); + return await response.value(); + } + /** */ async providerListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { diff --git a/frontend/src/api/generated/models/PongHistory200Response.ts b/frontend/src/api/generated/models/PongHistory200Response.ts new file mode 100644 index 0000000..82104ee --- /dev/null +++ b/frontend/src/api/generated/models/PongHistory200Response.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { PongHistory200ResponsePayload } from './PongHistory200ResponsePayload'; +import { + PongHistory200ResponsePayloadFromJSON, + PongHistory200ResponsePayloadFromJSONTyped, + PongHistory200ResponsePayloadToJSON, + PongHistory200ResponsePayloadToJSONTyped, +} from './PongHistory200ResponsePayload'; + +/** + * + * @export + * @interface PongHistory200Response + */ +export interface PongHistory200Response { + /** + * + * @type {string} + * @memberof PongHistory200Response + */ + kind: PongHistory200ResponseKindEnum; + /** + * + * @type {string} + * @memberof PongHistory200Response + */ + msg: PongHistory200ResponseMsgEnum; + /** + * + * @type {PongHistory200ResponsePayload} + * @memberof PongHistory200Response + */ + payload: PongHistory200ResponsePayload; +} + + +/** + * @export + */ +export const PongHistory200ResponseKindEnum = { + Success: 'success' +} as const; +export type PongHistory200ResponseKindEnum = typeof PongHistory200ResponseKindEnum[keyof typeof PongHistory200ResponseKindEnum]; + +/** + * @export + */ +export const PongHistory200ResponseMsgEnum = { + PonghistorySuccess: 'ponghistory.success' +} as const; +export type PongHistory200ResponseMsgEnum = typeof PongHistory200ResponseMsgEnum[keyof typeof PongHistory200ResponseMsgEnum]; + + +/** + * Check if a given object implements the PongHistory200Response interface. + */ +export function instanceOfPongHistory200Response(value: object): value is PongHistory200Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + if (!('payload' in value) || value['payload'] === undefined) return false; + return true; +} + +export function PongHistory200ResponseFromJSON(json: any): PongHistory200Response { + return PongHistory200ResponseFromJSONTyped(json, false); +} + +export function PongHistory200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): PongHistory200Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + 'payload': PongHistory200ResponsePayloadFromJSON(json['payload']), + }; +} + +export function PongHistory200ResponseToJSON(json: any): PongHistory200Response { + return PongHistory200ResponseToJSONTyped(json, false); +} + +export function PongHistory200ResponseToJSONTyped(value?: PongHistory200Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + 'payload': PongHistory200ResponsePayloadToJSON(value['payload']), + }; +} + diff --git a/frontend/src/api/generated/models/PongHistory200ResponsePayload.ts b/frontend/src/api/generated/models/PongHistory200ResponsePayload.ts new file mode 100644 index 0000000..0a13083 --- /dev/null +++ b/frontend/src/api/generated/models/PongHistory200ResponsePayload.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { PongHistory200ResponsePayloadDataInner } from './PongHistory200ResponsePayloadDataInner'; +import { + PongHistory200ResponsePayloadDataInnerFromJSON, + PongHistory200ResponsePayloadDataInnerFromJSONTyped, + PongHistory200ResponsePayloadDataInnerToJSON, + PongHistory200ResponsePayloadDataInnerToJSONTyped, +} from './PongHistory200ResponsePayloadDataInner'; + +/** + * + * @export + * @interface PongHistory200ResponsePayload + */ +export interface PongHistory200ResponsePayload { + /** + * + * @type {Array} + * @memberof PongHistory200ResponsePayload + */ + data: Array; +} + +/** + * Check if a given object implements the PongHistory200ResponsePayload interface. + */ +export function instanceOfPongHistory200ResponsePayload(value: object): value is PongHistory200ResponsePayload { + if (!('data' in value) || value['data'] === undefined) return false; + return true; +} + +export function PongHistory200ResponsePayloadFromJSON(json: any): PongHistory200ResponsePayload { + return PongHistory200ResponsePayloadFromJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): PongHistory200ResponsePayload { + if (json == null) { + return json; + } + return { + + 'data': ((json['data'] as Array).map(PongHistory200ResponsePayloadDataInnerFromJSON)), + }; +} + +export function PongHistory200ResponsePayloadToJSON(json: any): PongHistory200ResponsePayload { + return PongHistory200ResponsePayloadToJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadToJSONTyped(value?: PongHistory200ResponsePayload | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'data': ((value['data'] as Array).map(PongHistory200ResponsePayloadDataInnerToJSON)), + }; +} + diff --git a/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInner.ts b/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInner.ts new file mode 100644 index 0000000..89f5732 --- /dev/null +++ b/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInner.ts @@ -0,0 +1,131 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +import type { PongHistory200ResponsePayloadDataInnerLeft } from './PongHistory200ResponsePayloadDataInnerLeft'; +import { + PongHistory200ResponsePayloadDataInnerLeftFromJSON, + PongHistory200ResponsePayloadDataInnerLeftFromJSONTyped, + PongHistory200ResponsePayloadDataInnerLeftToJSON, + PongHistory200ResponsePayloadDataInnerLeftToJSONTyped, +} from './PongHistory200ResponsePayloadDataInnerLeft'; + +/** + * + * @export + * @interface PongHistory200ResponsePayloadDataInner + */ +export interface PongHistory200ResponsePayloadDataInner { + /** + * gameId + * @type {string} + * @memberof PongHistory200ResponsePayloadDataInner + */ + gameId: string; + /** + * + * @type {PongHistory200ResponsePayloadDataInnerLeft} + * @memberof PongHistory200ResponsePayloadDataInner + */ + left: PongHistory200ResponsePayloadDataInnerLeft; + /** + * + * @type {PongHistory200ResponsePayloadDataInnerLeft} + * @memberof PongHistory200ResponsePayloadDataInner + */ + right: PongHistory200ResponsePayloadDataInnerLeft; + /** + * + * @type {boolean} + * @memberof PongHistory200ResponsePayloadDataInner + */ + local: boolean; + /** + * + * @type {string} + * @memberof PongHistory200ResponsePayloadDataInner + */ + date: string; + /** + * + * @type {string} + * @memberof PongHistory200ResponsePayloadDataInner + */ + outcome: PongHistory200ResponsePayloadDataInnerOutcomeEnum; +} + + +/** + * @export + */ +export const PongHistory200ResponsePayloadDataInnerOutcomeEnum = { + WinL: 'winL', + WinR: 'winR', + Other: 'other' +} as const; +export type PongHistory200ResponsePayloadDataInnerOutcomeEnum = typeof PongHistory200ResponsePayloadDataInnerOutcomeEnum[keyof typeof PongHistory200ResponsePayloadDataInnerOutcomeEnum]; + + +/** + * Check if a given object implements the PongHistory200ResponsePayloadDataInner interface. + */ +export function instanceOfPongHistory200ResponsePayloadDataInner(value: object): value is PongHistory200ResponsePayloadDataInner { + if (!('gameId' in value) || value['gameId'] === undefined) return false; + if (!('left' in value) || value['left'] === undefined) return false; + if (!('right' in value) || value['right'] === undefined) return false; + if (!('local' in value) || value['local'] === undefined) return false; + if (!('date' in value) || value['date'] === undefined) return false; + if (!('outcome' in value) || value['outcome'] === undefined) return false; + return true; +} + +export function PongHistory200ResponsePayloadDataInnerFromJSON(json: any): PongHistory200ResponsePayloadDataInner { + return PongHistory200ResponsePayloadDataInnerFromJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadDataInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): PongHistory200ResponsePayloadDataInner { + if (json == null) { + return json; + } + return { + + 'gameId': json['gameId'], + 'left': PongHistory200ResponsePayloadDataInnerLeftFromJSON(json['left']), + 'right': PongHistory200ResponsePayloadDataInnerLeftFromJSON(json['right']), + 'local': json['local'], + 'date': json['date'], + 'outcome': json['outcome'], + }; +} + +export function PongHistory200ResponsePayloadDataInnerToJSON(json: any): PongHistory200ResponsePayloadDataInner { + return PongHistory200ResponsePayloadDataInnerToJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadDataInnerToJSONTyped(value?: PongHistory200ResponsePayloadDataInner | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'gameId': value['gameId'], + 'left': PongHistory200ResponsePayloadDataInnerLeftToJSON(value['left']), + 'right': PongHistory200ResponsePayloadDataInnerLeftToJSON(value['right']), + 'local': value['local'], + 'date': value['date'], + 'outcome': value['outcome'], + }; +} + diff --git a/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInnerLeft.ts b/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInnerLeft.ts new file mode 100644 index 0000000..e39019e --- /dev/null +++ b/frontend/src/api/generated/models/PongHistory200ResponsePayloadDataInnerLeft.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface PongHistory200ResponsePayloadDataInnerLeft + */ +export interface PongHistory200ResponsePayloadDataInnerLeft { + /** + * + * @type {number} + * @memberof PongHistory200ResponsePayloadDataInnerLeft + */ + score: number; + /** + * + * @type {string} + * @memberof PongHistory200ResponsePayloadDataInnerLeft + */ + id: string; + /** + * + * @type {string} + * @memberof PongHistory200ResponsePayloadDataInnerLeft + */ + name: string; +} + +/** + * Check if a given object implements the PongHistory200ResponsePayloadDataInnerLeft interface. + */ +export function instanceOfPongHistory200ResponsePayloadDataInnerLeft(value: object): value is PongHistory200ResponsePayloadDataInnerLeft { + if (!('score' in value) || value['score'] === undefined) return false; + if (!('id' in value) || value['id'] === undefined) return false; + if (!('name' in value) || value['name'] === undefined) return false; + return true; +} + +export function PongHistory200ResponsePayloadDataInnerLeftFromJSON(json: any): PongHistory200ResponsePayloadDataInnerLeft { + return PongHistory200ResponsePayloadDataInnerLeftFromJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadDataInnerLeftFromJSONTyped(json: any, ignoreDiscriminator: boolean): PongHistory200ResponsePayloadDataInnerLeft { + if (json == null) { + return json; + } + return { + + 'score': json['score'], + 'id': json['id'], + 'name': json['name'], + }; +} + +export function PongHistory200ResponsePayloadDataInnerLeftToJSON(json: any): PongHistory200ResponsePayloadDataInnerLeft { + return PongHistory200ResponsePayloadDataInnerLeftToJSONTyped(json, false); +} + +export function PongHistory200ResponsePayloadDataInnerLeftToJSONTyped(value?: PongHistory200ResponsePayloadDataInnerLeft | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'score': value['score'], + 'id': value['id'], + 'name': value['name'], + }; +} + diff --git a/frontend/src/api/generated/models/PongHistory404Response.ts b/frontend/src/api/generated/models/PongHistory404Response.ts new file mode 100644 index 0000000..41315ab --- /dev/null +++ b/frontend/src/api/generated/models/PongHistory404Response.ts @@ -0,0 +1,93 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @fastify/swagger + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 9.6.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface PongHistory404Response + */ +export interface PongHistory404Response { + /** + * + * @type {string} + * @memberof PongHistory404Response + */ + kind: PongHistory404ResponseKindEnum; + /** + * + * @type {string} + * @memberof PongHistory404Response + */ + msg: PongHistory404ResponseMsgEnum; +} + + +/** + * @export + */ +export const PongHistory404ResponseKindEnum = { + Failure: 'failure' +} as const; +export type PongHistory404ResponseKindEnum = typeof PongHistory404ResponseKindEnum[keyof typeof PongHistory404ResponseKindEnum]; + +/** + * @export + */ +export const PongHistory404ResponseMsgEnum = { + PonghistoryFailureNotfound: 'ponghistory.failure.notfound' +} as const; +export type PongHistory404ResponseMsgEnum = typeof PongHistory404ResponseMsgEnum[keyof typeof PongHistory404ResponseMsgEnum]; + + +/** + * Check if a given object implements the PongHistory404Response interface. + */ +export function instanceOfPongHistory404Response(value: object): value is PongHistory404Response { + if (!('kind' in value) || value['kind'] === undefined) return false; + if (!('msg' in value) || value['msg'] === undefined) return false; + return true; +} + +export function PongHistory404ResponseFromJSON(json: any): PongHistory404Response { + return PongHistory404ResponseFromJSONTyped(json, false); +} + +export function PongHistory404ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): PongHistory404Response { + if (json == null) { + return json; + } + return { + + 'kind': json['kind'], + 'msg': json['msg'], + }; +} + +export function PongHistory404ResponseToJSON(json: any): PongHistory404Response { + return PongHistory404ResponseToJSONTyped(json, false); +} + +export function PongHistory404ResponseToJSONTyped(value?: PongHistory404Response | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'kind': value['kind'], + 'msg': value['msg'], + }; +} + diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index 226ff73..f085a6b 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -46,6 +46,11 @@ export * from './LoginOtp500Response'; export * from './LoginOtpRequest'; export * from './LoginRequest'; export * from './Logout200Response'; +export * from './PongHistory200Response'; +export * from './PongHistory200ResponsePayload'; +export * from './PongHistory200ResponsePayloadDataInner'; +export * from './PongHistory200ResponsePayloadDataInnerLeft'; +export * from './PongHistory404Response'; export * from './ProviderList200Response'; export * from './ProviderList200ResponsePayload'; export * from './ProviderList200ResponsePayloadListInner'; diff --git a/src/@shared/src/database/mixin/pong.ts b/src/@shared/src/database/mixin/pong.ts index 592a2ed..bcabe39 100644 --- a/src/@shared/src/database/mixin/pong.ts +++ b/src/@shared/src/database/mixin/pong.ts @@ -19,6 +19,11 @@ export interface IPongDb extends Database { this: IPongDb, id: PongGameId, ): PongGame | undefined, + + getAllPongGameForUser( + this: IPongDb, + id: UserId, + ): (PongGame & { nameL: string, nameR: string })[], } export const PongImpl: Omit = { @@ -49,6 +54,35 @@ export const PongImpl: Omit = { const q = this.prepare('SELECT * FROM pong WHERE id = @id').get({ id }) as Partial | undefined; return pongGameFromRow(q); }, + + getAllPongGameForUser( + this: IPongDb, + id: UserId, + ): (PongGame & { nameL: string, nameR: string })[] { + const q = this.prepare(` + SELECT + pong.*, + userL.name AS nameL, + userR.name AS nameR + FROM pong + INNER JOIN user AS userL + ON pong.playerL = userL.id + INNER JOIN user AS userR + ON pong.playerR = userR.id + WHERE + pong.playerL = @id + OR pong.playerR = @id; +`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return q.all({ id }).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)); + }, }; export type PongGameId = UUID & { readonly __uuid: unique symbol }; @@ -59,6 +93,7 @@ export type PongGame = { readonly right: { id: UserId, score: number }; readonly outcome: PongGameOutcome; readonly time: Date; + readonly local: boolean; }; // this is an internal type, never to be seen outside @@ -70,6 +105,7 @@ type PongGameTable = { scoreR: number, outcome: PongGameOutcome, time: string, + local: number, }; function pongGameFromRow(r: Partial | undefined): PongGame | undefined { @@ -81,6 +117,7 @@ function pongGameFromRow(r: Partial | undefined): PongGame | unde if (isNullish(r.scoreR)) return undefined; if (isNullish(r.outcome)) return undefined; if (isNullish(r.time)) return undefined; + if (isNullish(r.local)) return undefined; if (r.outcome !== 'winR' && r.outcome !== 'winL' && r.outcome !== 'other') return undefined; const date = Date.parse(r.time); @@ -93,18 +130,6 @@ function pongGameFromRow(r: Partial | undefined): PongGame | unde right: { id: r.playerR, score: r.scoreR }, outcome: r.outcome, time: new Date(date), + local: r.local !== 0, }; } - -// this function will be able to be called from everywhere -// export async function freeFloatingExportedFunction(): Promise { -// return false; -// } - -// this function will never be able to be called outside of this module -// async function privateFunction(): Promise { -// return undefined; -// } - -// silence warnings -// void privateFunction; diff --git a/src/openapi.json b/src/openapi.json index d177616..8b87c61 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -1916,6 +1916,214 @@ "openapi_other" ] } + }, + "/api/pong/history/{user}": { + "get": { + "operationId": "pongHistory", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "user", + "required": true, + "description": "'me' | " + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg", + "payload" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "ponghistory.success" + ] + }, + "payload": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "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" + ] + } + } + } + } + } + } + } + } + } + } + }, + "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": [ + "ponghistory.failure.notfound" + ] + } + } + } + } + } + } + }, + "tags": [ + "openapi_other" + ] + } } }, "components": { diff --git a/src/pong/openapi.json b/src/pong/openapi.json index 38cd725..2082ef0 100644 --- a/src/pong/openapi.json +++ b/src/pong/openapi.json @@ -7,7 +7,213 @@ "components": { "schemas": {} }, - "paths": {}, + "paths": { + "/api/pong/history/{user}": { + "get": { + "operationId": "pongHistory", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "user", + "required": true, + "description": "'me' | " + } + ], + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "kind", + "msg", + "payload" + ], + "properties": { + "kind": { + "enum": [ + "success" + ] + }, + "msg": { + "enum": [ + "ponghistory.success" + ] + }, + "payload": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "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" + ] + } + } + } + } + } + } + } + } + } + } + }, + "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": [ + "ponghistory.failure.notfound" + ] + } + } + } + } + } + } + } + } + } + }, "servers": [ { "url": "https://local.maix.me:8888", diff --git a/src/pong/src/routes/broadcast.ts b/src/pong/src/routes/broadcast.ts deleted file mode 100644 index a37b460..0000000 --- a/src/pong/src/routes/broadcast.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from 'typebox'; - -export const PongReq = Type.Object({ - message: Type.String(), -}); - -export type PongReq = Static; - -const route: FastifyPluginAsync = async (fastify): Promise => { - fastify.post<{ Body: PongReq }>( - '/api/pong/broadcast', - { - schema: { - body: PongReq, - hide: true, - }, - config: { requireAuth: false }, - }, - async function(req, res) { - void res; - }, - ); -}; -export default route; - -/** - * - * try this in a terminal - * - * curl -k --data-raw '{"message": "Message SENT from the terminal en REMOTE"}' 'https://local.maix.me:8888/api/pong/broadcast' -H "Content-Type: application/json" - * - * send message info to the fronatend via the route '/api/pong/broadcast' - */ - -// const route: FastifyPluginAsync = async (fastify): Promise => { -// fastify.post('/api/chat/broadcast', { -// schema: { -// body: { -// type: 'object', -// required: ['nextGame'], -// properties: { -// nextGame: { type: 'string' } -// } -// } -// } -// }, async (req, reply) => { - -// // Body only contains nextGame now -// const gameLink: Promise = Promise.resolve(req.body as string ); - -// // Broadcast nextGame -// if (gameLink) -// broadcastNextGame(fastify, gameLink); - -// return reply.send({ status: 'ok' }); -// }); -// }; -// export default route; - diff --git a/src/pong/src/routes/pongHistory.ts b/src/pong/src/routes/pongHistory.ts new file mode 100644 index 0000000..34ce106 --- /dev/null +++ b/src/pong/src/routes/pongHistory.ts @@ -0,0 +1,68 @@ +import { UserId } from '@shared/database/mixin/user'; +import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils'; +import { FastifyPluginAsync } from 'fastify'; +import { Static, Type } from 'typebox'; + +const PongHistoryParams = Type.Object({ + user: Type.String({ description: '\'me\' | ' }), +}); + +type PongHistoryParams = Static; + +const PongHistoryResponse = { + '200': typeResponse('success', 'ponghistory.success', { + data: 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']), + }), + ), + }), + '404': typeResponse('failure', 'ponghistory.failure.notfound'), +}; +type PongHistoryResponse = MakeStaticResponse; + +const route: FastifyPluginAsync = async (fastify): Promise => { + fastify.get<{ Params: PongHistoryParams }>( + '/api/pong/history/:user', + { + schema: { + params: PongHistoryParams, + response: PongHistoryResponse, + operationId: 'pongHistory', + }, + config: { requireAuth: true }, + }, + async function(req, res) { + if (req.params.user === 'me') { req.params.user = req.authUser!.id; } + const user = this.db.getUser(req.params.user); + if (isNullish(user)) { return res.makeResponse(404, 'failure', 'ponghistory.failure.notfound'); } + const data = this.db.getAllPongGameForUser(req.params.user as UserId); + if (isNullish(data)) { return res.makeResponse(404, 'failure', 'ponghistory.failure.notfound'); } + + return res.makeResponse(200, 'success', 'ponghistory.success', { + data: data.map(v => ({ + gameId: v.id, + left: { score: v.left.score, id: v.left.id, name: v.nameL }, + right: { score: v.right.score, id: v.right.id, name: v.nameR }, + local: v.local, + date: v.time.toString(), + outcome: v.outcome, + })), + }); + }, + ); +}; +export default route; diff --git a/src/redocly.yaml b/src/redocly.yaml index e54e41f..6687ad5 100644 --- a/src/redocly.yaml +++ b/src/redocly.yaml @@ -7,6 +7,8 @@ apis: root: ./chat/openapi.json ttt: root: ./tic-tac-toe/openapi.json + pong: + root: ./pong/openapi.json rules: info-license: warn