feat(ttt): match history done
This commit is contained in:
parent
8f3ed71d8a
commit
2bf5e6e700
17 changed files with 1185 additions and 18 deletions
7
Makefile
7
Makefile
|
|
@ -44,7 +44,7 @@ prune:
|
|||
logs:
|
||||
@$(MAKE) --no-print-directory -f ./Docker.mk logs
|
||||
|
||||
sqlite:
|
||||
sql:
|
||||
docker compose exec auth apk add sqlite
|
||||
-docker compose exec -it auth sqlite3 /volumes/database/database.db
|
||||
|
||||
|
|
@ -125,11 +125,6 @@ npm@openapi: openapi.jar
|
|||
openapi.jar:
|
||||
wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.15.0/openapi-generator-cli-7.15.0.jar -O ./openapi.jar
|
||||
|
||||
# this convert the .dbml file to an actual sql file that SQLite can handle :)
|
||||
sql:
|
||||
@echo "if the command isn't found, contact maieul :)"
|
||||
dbml_sqlite -t -f -w ./src/@shared/src/database/init.sql ./src/@shared/src/database/init.dbml
|
||||
|
||||
tmux:
|
||||
@tmux new-session -d -s $(PROJECT)
|
||||
@tmux send-keys -t $(PROJECT):0 'vim' C-m
|
||||
|
|
|
|||
|
|
@ -66,5 +66,10 @@ models/StatusOtp200ResponseAnyOf1.ts
|
|||
models/StatusOtp200ResponseAnyOfPayload.ts
|
||||
models/StatusOtp401Response.ts
|
||||
models/StatusOtp500Response.ts
|
||||
models/TttHistory200Response.ts
|
||||
models/TttHistory200ResponsePayload.ts
|
||||
models/TttHistory200ResponsePayloadDataInner.ts
|
||||
models/TttHistory200ResponsePayloadDataInnerPlayerX.ts
|
||||
models/TttHistory404Response.ts
|
||||
models/index.ts
|
||||
runtime.ts
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ import type {
|
|||
StatusOtp200Response,
|
||||
StatusOtp401Response,
|
||||
StatusOtp500Response,
|
||||
TttHistory200Response,
|
||||
TttHistory404Response,
|
||||
} from '../models/index';
|
||||
import {
|
||||
AllowGuestMessage200ResponseFromJSON,
|
||||
|
|
@ -161,6 +163,10 @@ import {
|
|||
StatusOtp401ResponseToJSON,
|
||||
StatusOtp500ResponseFromJSON,
|
||||
StatusOtp500ResponseToJSON,
|
||||
TttHistory200ResponseFromJSON,
|
||||
TttHistory200ResponseToJSON,
|
||||
TttHistory404ResponseFromJSON,
|
||||
TttHistory404ResponseToJSON,
|
||||
} from '../models/index';
|
||||
|
||||
export interface ChangeDescOperationRequest {
|
||||
|
|
@ -199,6 +205,10 @@ export interface SigninRequest {
|
|||
loginRequest: LoginRequest;
|
||||
}
|
||||
|
||||
export interface TttHistoryRequest {
|
||||
user: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -1027,4 +1037,58 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
|
|||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async tttHistoryRaw(requestParameters: TttHistoryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<TttHistory200Response | StatusOtp401Response | TttHistory404Response>> {
|
||||
if (requestParameters['user'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'user',
|
||||
'Required parameter "user" was null or undefined when calling tttHistory().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
|
||||
let urlPath = `/api/ttt/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) => TttHistory200ResponseFromJSON(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) => TttHistory404ResponseFromJSON(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 tttHistory(requestParameters: TttHistoryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<TttHistory200Response | StatusOtp401Response | TttHistory404Response> {
|
||||
const response = await this.tttHistoryRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
110
frontend/src/api/generated/models/TttHistory200Response.ts
Normal file
110
frontend/src/api/generated/models/TttHistory200Response.ts
Normal file
|
|
@ -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 { TttHistory200ResponsePayload } from './TttHistory200ResponsePayload';
|
||||
import {
|
||||
TttHistory200ResponsePayloadFromJSON,
|
||||
TttHistory200ResponsePayloadFromJSONTyped,
|
||||
TttHistory200ResponsePayloadToJSON,
|
||||
TttHistory200ResponsePayloadToJSONTyped,
|
||||
} from './TttHistory200ResponsePayload';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface TttHistory200Response
|
||||
*/
|
||||
export interface TttHistory200Response {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200Response
|
||||
*/
|
||||
kind: TttHistory200ResponseKindEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200Response
|
||||
*/
|
||||
msg: TttHistory200ResponseMsgEnum;
|
||||
/**
|
||||
*
|
||||
* @type {TttHistory200ResponsePayload}
|
||||
* @memberof TttHistory200Response
|
||||
*/
|
||||
payload: TttHistory200ResponsePayload;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @export
|
||||
*/
|
||||
export const TttHistory200ResponseKindEnum = {
|
||||
Success: 'success'
|
||||
} as const;
|
||||
export type TttHistory200ResponseKindEnum = typeof TttHistory200ResponseKindEnum[keyof typeof TttHistory200ResponseKindEnum];
|
||||
|
||||
/**
|
||||
* @export
|
||||
*/
|
||||
export const TttHistory200ResponseMsgEnum = {
|
||||
TtthistorySuccess: 'ttthistory.success'
|
||||
} as const;
|
||||
export type TttHistory200ResponseMsgEnum = typeof TttHistory200ResponseMsgEnum[keyof typeof TttHistory200ResponseMsgEnum];
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TttHistory200Response interface.
|
||||
*/
|
||||
export function instanceOfTttHistory200Response(value: object): value is TttHistory200Response {
|
||||
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 TttHistory200ResponseFromJSON(json: any): TttHistory200Response {
|
||||
return TttHistory200ResponseFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): TttHistory200Response {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'kind': json['kind'],
|
||||
'msg': json['msg'],
|
||||
'payload': TttHistory200ResponsePayloadFromJSON(json['payload']),
|
||||
};
|
||||
}
|
||||
|
||||
export function TttHistory200ResponseToJSON(json: any): TttHistory200Response {
|
||||
return TttHistory200ResponseToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponseToJSONTyped(value?: TttHistory200Response | null, ignoreDiscriminator: boolean = false): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
'kind': value['kind'],
|
||||
'msg': value['msg'],
|
||||
'payload': TttHistory200ResponsePayloadToJSON(value['payload']),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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 { TttHistory200ResponsePayloadDataInner } from './TttHistory200ResponsePayloadDataInner';
|
||||
import {
|
||||
TttHistory200ResponsePayloadDataInnerFromJSON,
|
||||
TttHistory200ResponsePayloadDataInnerFromJSONTyped,
|
||||
TttHistory200ResponsePayloadDataInnerToJSON,
|
||||
TttHistory200ResponsePayloadDataInnerToJSONTyped,
|
||||
} from './TttHistory200ResponsePayloadDataInner';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface TttHistory200ResponsePayload
|
||||
*/
|
||||
export interface TttHistory200ResponsePayload {
|
||||
/**
|
||||
*
|
||||
* @type {Array<TttHistory200ResponsePayloadDataInner>}
|
||||
* @memberof TttHistory200ResponsePayload
|
||||
*/
|
||||
data: Array<TttHistory200ResponsePayloadDataInner>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TttHistory200ResponsePayload interface.
|
||||
*/
|
||||
export function instanceOfTttHistory200ResponsePayload(value: object): value is TttHistory200ResponsePayload {
|
||||
if (!('data' in value) || value['data'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadFromJSON(json: any): TttHistory200ResponsePayload {
|
||||
return TttHistory200ResponsePayloadFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): TttHistory200ResponsePayload {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'data': ((json['data'] as Array<any>).map(TttHistory200ResponsePayloadDataInnerFromJSON)),
|
||||
};
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadToJSON(json: any): TttHistory200ResponsePayload {
|
||||
return TttHistory200ResponsePayloadToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadToJSONTyped(value?: TttHistory200ResponsePayload | null, ignoreDiscriminator: boolean = false): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
'data': ((value['data'] as Array<any>).map(TttHistory200ResponsePayloadDataInnerToJSON)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/* 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 { TttHistory200ResponsePayloadDataInnerPlayerX } from './TttHistory200ResponsePayloadDataInnerPlayerX';
|
||||
import {
|
||||
TttHistory200ResponsePayloadDataInnerPlayerXFromJSON,
|
||||
TttHistory200ResponsePayloadDataInnerPlayerXFromJSONTyped,
|
||||
TttHistory200ResponsePayloadDataInnerPlayerXToJSON,
|
||||
TttHistory200ResponsePayloadDataInnerPlayerXToJSONTyped,
|
||||
} from './TttHistory200ResponsePayloadDataInnerPlayerX';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
export interface TttHistory200ResponsePayloadDataInner {
|
||||
/**
|
||||
* gameId
|
||||
* @type {string}
|
||||
* @memberof TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
gameId: string;
|
||||
/**
|
||||
*
|
||||
* @type {TttHistory200ResponsePayloadDataInnerPlayerX}
|
||||
* @memberof TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
playerX: TttHistory200ResponsePayloadDataInnerPlayerX;
|
||||
/**
|
||||
*
|
||||
* @type {TttHistory200ResponsePayloadDataInnerPlayerX}
|
||||
* @memberof TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
playerO: TttHistory200ResponsePayloadDataInnerPlayerX;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
date: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200ResponsePayloadDataInner
|
||||
*/
|
||||
outcome: TttHistory200ResponsePayloadDataInnerOutcomeEnum;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @export
|
||||
*/
|
||||
export const TttHistory200ResponsePayloadDataInnerOutcomeEnum = {
|
||||
WinX: 'winX',
|
||||
WinO: 'winO',
|
||||
Other: 'other'
|
||||
} as const;
|
||||
export type TttHistory200ResponsePayloadDataInnerOutcomeEnum = typeof TttHistory200ResponsePayloadDataInnerOutcomeEnum[keyof typeof TttHistory200ResponsePayloadDataInnerOutcomeEnum];
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TttHistory200ResponsePayloadDataInner interface.
|
||||
*/
|
||||
export function instanceOfTttHistory200ResponsePayloadDataInner(value: object): value is TttHistory200ResponsePayloadDataInner {
|
||||
if (!('gameId' in value) || value['gameId'] === undefined) return false;
|
||||
if (!('playerX' in value) || value['playerX'] === undefined) return false;
|
||||
if (!('playerO' in value) || value['playerO'] === 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 TttHistory200ResponsePayloadDataInnerFromJSON(json: any): TttHistory200ResponsePayloadDataInner {
|
||||
return TttHistory200ResponsePayloadDataInnerFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): TttHistory200ResponsePayloadDataInner {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'gameId': json['gameId'],
|
||||
'playerX': TttHistory200ResponsePayloadDataInnerPlayerXFromJSON(json['playerX']),
|
||||
'playerO': TttHistory200ResponsePayloadDataInnerPlayerXFromJSON(json['playerO']),
|
||||
'date': json['date'],
|
||||
'outcome': json['outcome'],
|
||||
};
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerToJSON(json: any): TttHistory200ResponsePayloadDataInner {
|
||||
return TttHistory200ResponsePayloadDataInnerToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerToJSONTyped(value?: TttHistory200ResponsePayloadDataInner | null, ignoreDiscriminator: boolean = false): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
'gameId': value['gameId'],
|
||||
'playerX': TttHistory200ResponsePayloadDataInnerPlayerXToJSON(value['playerX']),
|
||||
'playerO': TttHistory200ResponsePayloadDataInnerPlayerXToJSON(value['playerO']),
|
||||
'date': value['date'],
|
||||
'outcome': value['outcome'],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/* 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 TttHistory200ResponsePayloadDataInnerPlayerX
|
||||
*/
|
||||
export interface TttHistory200ResponsePayloadDataInnerPlayerX {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200ResponsePayloadDataInnerPlayerX
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory200ResponsePayloadDataInnerPlayerX
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TttHistory200ResponsePayloadDataInnerPlayerX interface.
|
||||
*/
|
||||
export function instanceOfTttHistory200ResponsePayloadDataInnerPlayerX(value: object): value is TttHistory200ResponsePayloadDataInnerPlayerX {
|
||||
if (!('id' in value) || value['id'] === undefined) return false;
|
||||
if (!('name' in value) || value['name'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerPlayerXFromJSON(json: any): TttHistory200ResponsePayloadDataInnerPlayerX {
|
||||
return TttHistory200ResponsePayloadDataInnerPlayerXFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerPlayerXFromJSONTyped(json: any, ignoreDiscriminator: boolean): TttHistory200ResponsePayloadDataInnerPlayerX {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'id': json['id'],
|
||||
'name': json['name'],
|
||||
};
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerPlayerXToJSON(json: any): TttHistory200ResponsePayloadDataInnerPlayerX {
|
||||
return TttHistory200ResponsePayloadDataInnerPlayerXToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory200ResponsePayloadDataInnerPlayerXToJSONTyped(value?: TttHistory200ResponsePayloadDataInnerPlayerX | null, ignoreDiscriminator: boolean = false): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
'id': value['id'],
|
||||
'name': value['name'],
|
||||
};
|
||||
}
|
||||
|
||||
93
frontend/src/api/generated/models/TttHistory404Response.ts
Normal file
93
frontend/src/api/generated/models/TttHistory404Response.ts
Normal file
|
|
@ -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 TttHistory404Response
|
||||
*/
|
||||
export interface TttHistory404Response {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory404Response
|
||||
*/
|
||||
kind: TttHistory404ResponseKindEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TttHistory404Response
|
||||
*/
|
||||
msg: TttHistory404ResponseMsgEnum;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @export
|
||||
*/
|
||||
export const TttHistory404ResponseKindEnum = {
|
||||
Failure: 'failure'
|
||||
} as const;
|
||||
export type TttHistory404ResponseKindEnum = typeof TttHistory404ResponseKindEnum[keyof typeof TttHistory404ResponseKindEnum];
|
||||
|
||||
/**
|
||||
* @export
|
||||
*/
|
||||
export const TttHistory404ResponseMsgEnum = {
|
||||
TtthistoryFailureNotfound: 'ttthistory.failure.notfound'
|
||||
} as const;
|
||||
export type TttHistory404ResponseMsgEnum = typeof TttHistory404ResponseMsgEnum[keyof typeof TttHistory404ResponseMsgEnum];
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given object implements the TttHistory404Response interface.
|
||||
*/
|
||||
export function instanceOfTttHistory404Response(value: object): value is TttHistory404Response {
|
||||
if (!('kind' in value) || value['kind'] === undefined) return false;
|
||||
if (!('msg' in value) || value['msg'] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function TttHistory404ResponseFromJSON(json: any): TttHistory404Response {
|
||||
return TttHistory404ResponseFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory404ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): TttHistory404Response {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'kind': json['kind'],
|
||||
'msg': json['msg'],
|
||||
};
|
||||
}
|
||||
|
||||
export function TttHistory404ResponseToJSON(json: any): TttHistory404Response {
|
||||
return TttHistory404ResponseToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function TttHistory404ResponseToJSONTyped(value?: TttHistory404Response | null, ignoreDiscriminator: boolean = false): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
'kind': value['kind'],
|
||||
'msg': value['msg'],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -65,3 +65,8 @@ export * from './StatusOtp200ResponseAnyOf1';
|
|||
export * from './StatusOtp200ResponseAnyOfPayload';
|
||||
export * from './StatusOtp401Response';
|
||||
export * from './StatusOtp500Response';
|
||||
export * from './TttHistory200Response';
|
||||
export * from './TttHistory200ResponsePayload';
|
||||
export * from './TttHistory200ResponsePayloadDataInner';
|
||||
export * from './TttHistory200ResponsePayloadDataInnerPlayerX';
|
||||
export * from './TttHistory404Response';
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import './ttt/ttt.ts'
|
|||
import './profile/profile.ts'
|
||||
import './logout/logout.ts'
|
||||
import './pongHistory/pongHistory.ts'
|
||||
import './tttHistory/tttHistory.ts'
|
||||
|
||||
// ---- Initial load ----
|
||||
setTitle("");
|
||||
|
|
|
|||
18
frontend/src/pages/tttHistory/tttHistory.html
Normal file
18
frontend/src/pages/tttHistory/tttHistory.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div class="fixed inset-0 flex items-center justify-center bg-[#43536b]">
|
||||
<div
|
||||
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-200 w-212.5 p-6 rounded-xl shadow-2xl text-center z-50">
|
||||
<h2 class="text-2xl font-bold text-center mb-4 text-gray-700">
|
||||
TicTacToe Match History For
|
||||
<span id="name" class="text-amber-500"></span>
|
||||
</h2>
|
||||
<div id="matchList" class="max-w-3xl mx-auto space-y-2 max-h-[50dvh] overflow-scroll"></div>
|
||||
<div style="display: none">
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] items-center bg-zinc-800 rounded-lg px-4 py-3"></div>
|
||||
<div class="text-right"></div>
|
||||
<div class="text-left"></div>
|
||||
<div class="text-green-400"></div>
|
||||
<div class="text-red-400"></div>
|
||||
<div class="text-semibold"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
89
frontend/src/pages/tttHistory/tttHistory.ts
Normal file
89
frontend/src/pages/tttHistory/tttHistory.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { addRoute, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
||||
import page from './tttHistory.html?raw';
|
||||
import { isNullish } from "@app/utils";
|
||||
import client from "@app/api";
|
||||
import { updateUser } from "@app/auth";
|
||||
|
||||
|
||||
function getHHMM(d: Date): string {
|
||||
let h = d.getHours();
|
||||
let m = d.getMinutes();
|
||||
return `${h < 9 ? '0' : ''}${h}:${m < 9 ? '0' : ''}${m}`
|
||||
}
|
||||
|
||||
async function tttHistory(_url: string, args: RouteHandlerParams): Promise<RouteHandlerReturn> {
|
||||
if (isNullish(args.userid))
|
||||
args.userid = 'me';
|
||||
let user = await updateUser();
|
||||
if (isNullish(user)) {
|
||||
return { html: '<span> You aren\'t logged in </span>' };
|
||||
}
|
||||
|
||||
let userInfoRes = await client.getUser({user: args.userid});
|
||||
if (userInfoRes.kind !== 'success')
|
||||
{
|
||||
return { html: '<span> You tried to open a game history with no data :(</span>' };
|
||||
}
|
||||
let userInfo = userInfoRes.payload;
|
||||
let res = await client.tttHistory({ user: args.userid });
|
||||
if (res.kind === 'failure' || res.kind === 'notLoggedIn') {
|
||||
// todo: make a real page on no data
|
||||
return { html: '<span> You tried to open a game history with no data :(</span>' };
|
||||
}
|
||||
let games = res.payload.data;
|
||||
games.reverse();
|
||||
|
||||
let gameElement = games.map(g => {
|
||||
let rdate = Date.parse(g.date);
|
||||
if (Number.isNaN(rdate)) return undefined;
|
||||
let date = new Date(rdate);
|
||||
const e = document.createElement('div');
|
||||
let color = 'bg-amber-200';
|
||||
// maybe we do want local games ? then put the check here :D
|
||||
// if (!g.local) {
|
||||
if (true) {
|
||||
let youwin = false;
|
||||
|
||||
if (g.playerX.id === user.id && g.outcome === 'winX')
|
||||
youwin = true;
|
||||
else if (g.playerO.id === user.id && g.outcome === 'winO')
|
||||
youwin = true;
|
||||
|
||||
if (youwin)
|
||||
color = 'bg-green-300';
|
||||
else
|
||||
color = 'bg-red-300';
|
||||
}
|
||||
e.className =
|
||||
'grid grid-cols-[1fr_auto_1fr] items-center rounded-lg px-4 py-3 ' + color;
|
||||
|
||||
e.innerHTML = `
|
||||
<div class="text-right">
|
||||
<div class="font-semibold ${g.outcome === 'winX' ? 'text-green-600' : 'text-red-600'}">${g.playerX.name}</div>
|
||||
<div class="text-lg ${g.outcome === 'winX' ? 'text-green-600' : 'text-red-600'}">${g.outcome === 'winX' ? 'WON' : 'LOST'}</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm text-gray-800 px-4 whitespace-nowrap">${date.toDateString()}<br />${getHHMM(date)}</div>
|
||||
|
||||
<div class="text-left">
|
||||
<div class="font-semibold ${g.outcome === 'winO' ? 'text-green-600' : 'text-red-600'}">${g.playerO.name}</div>
|
||||
<div class="text-lg ${g.outcome === 'winO' ? 'text-green-600' : 'text-red-600'}">${g.outcome === 'winO' ? 'WON' : 'LOST'}</div>
|
||||
</div>`;
|
||||
return e;
|
||||
}).filter(v => !isNullish(v));
|
||||
|
||||
return {
|
||||
html: page, postInsert: async (app) => {
|
||||
if (!app) return;
|
||||
const matchBox = app.querySelector<HTMLDivElement>("#matchList");
|
||||
if (!matchBox) return;
|
||||
gameElement.forEach(c => matchBox.appendChild(c));
|
||||
const userBox = app.querySelector<HTMLDivElement>("#name");
|
||||
if (!userBox) return;
|
||||
userBox.innerText = userInfo.name;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
addRoute('/ttt/games', tttHistory);
|
||||
addRoute('/ttt/games/:userid', tttHistory);
|
||||
|
|
@ -21,11 +21,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair ON blocked (user, blocke
|
|||
|
||||
CREATE TABLE IF NOT EXISTS tictactoe (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
player1 TEXT NOT NULL,
|
||||
player2 TEXT NOT NULL,
|
||||
time TEXT NOT NULL default (datetime('now')),
|
||||
playerX TEXT NOT NULL,
|
||||
playerO TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL,
|
||||
FOREIGN KEY(player1) REFERENCES user(id),
|
||||
FOREIGN KEY(player2) REFERENCES user(id)
|
||||
FOREIGN KEY(playerX) REFERENCES user(id),
|
||||
FOREIGN KEY(playerO) REFERENCES user(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pong (
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
import UUID from '@shared/utils/uuid';
|
||||
import type { Database } from './_base';
|
||||
import { UserId } from './user';
|
||||
import { isNullish } from '@shared/utils';
|
||||
|
||||
export type TicTacToeOutcome = 'winX' | 'winO' | 'other';
|
||||
// describe every function in the object
|
||||
export interface ITicTacToeDb extends Database {
|
||||
setTTTGameOutcome(this: ITicTacToeDb, id: TTTGameId, player1: UserId, player2: UserId, outcome: string): void,
|
||||
setTTTGameOutcome(this: ITicTacToeDb, id: TTTGameId, player1: UserId, player2: UserId, outcome: TicTacToeOutcome): void,
|
||||
getAllTTTGameForUser(
|
||||
this: ITicTacToeDb,
|
||||
id: UserId,
|
||||
): (TicTacToeGame & { nameX: string, nameO: string })[],
|
||||
};
|
||||
|
||||
|
||||
export const TicTacToeImpl: Omit<ITicTacToeDb, keyof Database> = {
|
||||
/**
|
||||
* @brief Write the outcome of the specified game to the database.
|
||||
|
|
@ -14,21 +21,81 @@ export const TicTacToeImpl: Omit<ITicTacToeDb, keyof Database> = {
|
|||
* @param gameId The game we want to write the outcome of.
|
||||
*
|
||||
*/
|
||||
setTTTGameOutcome(this: ITicTacToeDb, id: TTTGameId, player1: UserId, player2: UserId, outcome: string): void {
|
||||
setTTTGameOutcome(this: ITicTacToeDb, id: TTTGameId, playerX: UserId, playerO: UserId, outcome: TicTacToeOutcome): void {
|
||||
// Find a way to retrieve the outcome of the game.
|
||||
this.prepare('INSERT INTO tictactoe (id, player1, player2, outcome) VALUES (@id, @player1, @player2, @outcome)').run({ id, player1, player2, outcome });
|
||||
this.prepare('INSERT INTO tictactoe (id, playerX, playerO, outcome) VALUES (@id, @playerX, @playerO, @outcome)').run({ id, playerX, playerO, outcome });
|
||||
},
|
||||
|
||||
getAllTTTGameForUser(
|
||||
this: ITicTacToeDb,
|
||||
id: UserId,
|
||||
): (TicTacToeGame & { nameX: string, nameO: string })[] {
|
||||
const q = this.prepare(`
|
||||
SELECT
|
||||
tictactoe.*,
|
||||
userX.name AS nameX,
|
||||
userO.name AS nameO
|
||||
FROM tictactoe
|
||||
INNER JOIN user AS userX
|
||||
ON tictactoe.playerX = userX.id
|
||||
INNER JOIN user AS userO
|
||||
ON tictactoe.playerO = userO.id
|
||||
WHERE
|
||||
tictactoe.playerX = @id
|
||||
OR tictactoe.playerO = @id;
|
||||
`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return q.all({ id }).map((s: any) => {
|
||||
const g: (TicTacToeGame & { nameX?: string, nameO?: string }) | undefined = TicTacToeGameFromRow(s);
|
||||
if (isNullish(g)) return undefined;
|
||||
g.nameX = s.nameX;
|
||||
g.nameO = s.nameO;
|
||||
if (isNullish(g.nameO) || isNullish(g.nameO)) return undefined;
|
||||
return g as TicTacToeGame & { nameX: string, nameO: string };
|
||||
}).filter(v => !isNullish(v));
|
||||
}
|
||||
};
|
||||
|
||||
export type TTTGameId = UUID & { readonly __brand: unique symbol };
|
||||
export type TTTGameId = UUID & { readonly __uuid: unique symbol };
|
||||
|
||||
export type TicTacToeGame = {
|
||||
readonly id: TTTGameId;
|
||||
readonly player1: UserId;
|
||||
readonly player2: UserId;
|
||||
readonly outcome: string;
|
||||
readonly time: Date;
|
||||
readonly playerX: UserId;
|
||||
readonly playerO: UserId;
|
||||
readonly outcome: TicTacToeOutcome;
|
||||
};
|
||||
|
||||
type TicTacToeGameTable = {
|
||||
id: string;
|
||||
time: string;
|
||||
playerX: UserId;
|
||||
playerO: UserId;
|
||||
outcome: string;
|
||||
};
|
||||
|
||||
function TicTacToeGameFromRow(r: Partial<TicTacToeGameTable> | undefined): TicTacToeGame | undefined {
|
||||
if (isNullish(r)) return undefined;
|
||||
if (isNullish(r.id)) return undefined;
|
||||
if (isNullish(r.playerX)) return undefined;
|
||||
if (isNullish(r.playerO)) return undefined;
|
||||
if (isNullish(r.outcome)) return undefined;
|
||||
if (isNullish(r.time)) return undefined;
|
||||
|
||||
if (r.outcome !== 'winX' && r.outcome !== 'winO' && r.outcome !== 'other') return undefined;
|
||||
const date = Date.parse(r.time);
|
||||
if (Number.isNaN(date)) return undefined;
|
||||
|
||||
|
||||
return {
|
||||
id: r.id as TTTGameId,
|
||||
playerX: r.playerX,
|
||||
playerO: r.playerO,
|
||||
outcome: r.outcome,
|
||||
time: new Date(date),
|
||||
};
|
||||
}
|
||||
|
||||
// this function will be able to be called from everywhere
|
||||
// export async function freeFloatingExportedFunction(): Promise<boolean> {
|
||||
// return false;
|
||||
|
|
|
|||
196
src/openapi.json
196
src/openapi.json
|
|
@ -1917,6 +1917,202 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/ttt/history/{user}": {
|
||||
"get": {
|
||||
"operationId": "tttHistory",
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"in": "path",
|
||||
"name": "user",
|
||||
"required": true,
|
||||
"description": "'me' | <userid>"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"ttthistory.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId",
|
||||
"playerX",
|
||||
"playerO",
|
||||
"date",
|
||||
"outcome"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "gameId"
|
||||
},
|
||||
"playerX": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playerO": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcome": {
|
||||
"enum": [
|
||||
"winX",
|
||||
"winO",
|
||||
"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": [
|
||||
"ttthistory.failure.notfound"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"openapi_other"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/pong/history/{user}": {
|
||||
"get": {
|
||||
"operationId": "pongHistory",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,201 @@
|
|||
"components": {
|
||||
"schemas": {}
|
||||
},
|
||||
"paths": {},
|
||||
"paths": {
|
||||
"/api/ttt/history/{user}": {
|
||||
"get": {
|
||||
"operationId": "tttHistory",
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"in": "path",
|
||||
"name": "user",
|
||||
"required": true,
|
||||
"description": "'me' | <userid>"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Default Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"msg",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"msg": {
|
||||
"enum": [
|
||||
"ttthistory.success"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gameId",
|
||||
"playerX",
|
||||
"playerO",
|
||||
"date",
|
||||
"outcome"
|
||||
],
|
||||
"properties": {
|
||||
"gameId": {
|
||||
"type": "string",
|
||||
"description": "gameId"
|
||||
},
|
||||
"playerX": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playerO": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"outcome": {
|
||||
"enum": [
|
||||
"winX",
|
||||
"winO",
|
||||
"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": [
|
||||
"ttthistory.failure.notfound"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://local.maix.me:8888",
|
||||
|
|
|
|||
58
src/tic-tac-toe/src/routes/tttHistory.ts
Normal file
58
src/tic-tac-toe/src/routes/tttHistory.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { UserId } from '@shared/database/mixin/user';
|
||||
import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils';
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { Static, Type } from 'typebox';
|
||||
|
||||
const TTTHistoryParams = Type.Object({
|
||||
user: Type.String({ description: '\'me\' | <userid>' }),
|
||||
});
|
||||
|
||||
type TTTHistoryParams = Static<typeof TTTHistoryParams>;
|
||||
|
||||
const TTTHistoryResponse = {
|
||||
'200': typeResponse('success', 'ttthistory.success', {
|
||||
data: Type.Array(
|
||||
Type.Object({
|
||||
gameId: Type.String({ description: 'gameId' }),
|
||||
playerX: Type.Object({id: Type.String(), name: Type.String()}),
|
||||
playerO: Type.Object({id: Type.String(), name: Type.String()}),
|
||||
date: Type.String(),
|
||||
outcome: Type.Enum(['winX', 'winO', 'other']),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
'404': typeResponse('failure', 'ttthistory.failure.notfound'),
|
||||
};
|
||||
type TTTHistoryResponse = MakeStaticResponse<typeof TTTHistoryResponse>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<{ Params: TTTHistoryParams }>(
|
||||
'/api/ttt/history/:user',
|
||||
{
|
||||
schema: {
|
||||
params: TTTHistoryParams,
|
||||
response: TTTHistoryResponse,
|
||||
operationId: 'tttHistory',
|
||||
},
|
||||
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', 'ttthistory.failure.notfound'); }
|
||||
const data = this.db.getAllTTTGameForUser(req.params.user as UserId);
|
||||
if (isNullish(data)) { return res.makeResponse(404, 'failure', 'ttthistory.failure.notfound'); }
|
||||
|
||||
return res.makeResponse(200, 'success', 'ttthistory.success', {
|
||||
data: data.map(v => ({
|
||||
gameId: v.id,
|
||||
playerX: { id: v.playerX, name: v.nameX },
|
||||
playerO: { id: v.playerO, name: v.nameO },
|
||||
date: v.time.toString(),
|
||||
outcome: v.outcome,
|
||||
})),
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
export default route;
|
||||
Loading…
Add table
Add a link
Reference in a new issue