try to fix this spaghetti of a code

This commit is contained in:
Frederik Spirgi
2026-09-09 10:50:23 +02:00
parent a5f8191c2e
commit b26155b49d
4 changed files with 2968 additions and 306 deletions
+6
View File
@@ -3,3 +3,9 @@ users.sqlite
users.sqlite-shm
users.sqlite-wal
.env
.npm-cache/
.playwright-browsers/
playwright-report/
test-results/
*.backup
+8 -2
View File
@@ -7,8 +7,11 @@
"start": "node gambling.js",
"dev": "node --watch gambling.js",
"docs": "node swagger.js",
"check": "node --check gambling.js && node --check lib/poker.js && node --check public/frontend_home.js && node --check public/frontend_login.js && node --check public/frontend_poker.js && node --check public/matrix.js && node --check swagger.js",
"test": "npm run check && node --test"
"check": "node scripts/check.js && node swagger.js --check",
"test": "npm run check && node --test --test-isolation=none test/*.test.js",
"test:e2e": "playwright test",
"migrate:legacy": "node scripts/migrate-legacy.js",
"audit:deps": "npm audit --audit-level=high"
},
"repository": {
"type": "git",
@@ -28,5 +31,8 @@
},
"devDependencies": {
"@playwright/test": "^1.63.0"
},
"engines": {
"node": ">=24"
}
}
+56 -24
View File
@@ -1,25 +1,57 @@
const swaggerAutogen = require('swagger-autogen')({openapi: '3.0.0'});
const doc = {
info: {
title: 'GAMBLE!',
description: 'Gambling API'
},
servers: [{url: '/'}],
components: {
securitySchemes:{
bearerAuth: {
type: 'http',
scheme: 'bearer'
}
}
}
const fs = require('node:fs');
const {schemas} = require('./public/protocol');
const object = (properties, required = Object.keys(properties)) => ({type: 'object', properties, required, additionalProperties: false});
const string = {type: 'string'};
const integer = {type: 'integer', minimum: 0, maximum: Number.MAX_SAFE_INTEGER};
const nullableString = {type: ['string', 'null']};
const numberMap = {type: 'object', additionalProperties: integer};
const array = items => ({type: 'array', items});
const user = object({username: string, money: integer});
const error = object({code: string, error: string});
const security = [{bearerAuth: []}, {cookieAuth: []}];
const response = schema => ({description: 'Success', content: {'application/json': {schema}}});
const responses = schema => ({200: response(schema), ...Object.fromEntries([400,401,403,404,409,429,500].map(status => [status, {description: `Error ${status}`, content: {'application/json': {schema: error}}}]))});
const paths = {};
function route(path, method, summary, output, body, auth = true) {
const entry = {summary, responses: responses(output), ...(auth ? {security} : {}), ...(body ? {requestBody: {required: true, content: {'application/json': {schema: body}}}} : {})};
paths[path] ||= {}; paths[path][method] = entry; return entry;
}
const session = object({username: string, token: string});
route('/register','post','Create a 200-chip account and session',session,schemas.credentials,false);
route('/login','post','Replace the current session',session,schemas.credentials,false);
route('/verify','post','Verify a cookie or bearer session',object({valid:{const:true},user:object({username:string,exp:integer})}));
route('/logout','delete','Fold/leave the lobby, revoke the session, and clear the cookie',object({message:string}));
route('/balance','get','Current spendable chips (excludes escrow)',object({balance:integer}));
route('/leaderboard','get','Rank spendable balances; username breaks ties',object({leaderboard:array(user),self:object({username:string,money:integer,position:integer})}));
const ack = object({lobbyId:string,username:string,type:string,requestId:string});
for (const [path,type] of [['/lobby/create','lobby:create'],['/lobby/join','lobby:join'],['/lobby/leave','lobby:leave'],['/poker/start','poker:start'],['/poker/action','poker:action']]) {
const body = structuredClone(schemas[type]); delete body.properties.type; body.required = body.required.filter(key => key !== 'type');
route(path,'post',`${type}; retry the identical body/requestId after a lost response`,ack,body);
paths[path].get = {summary:'Deprecated mutation method',responses:{405:{description:'Use POST',content:{'application/json':{schema:error}}}}};
}
route('/lobby/public','get','List public lobbies',object({lobbies:array(object({lobbyId:string,host:nullableString,players:integer,gameStarted:{type:'boolean'}}))}));
route('/lobby/players','get','List members of your lobby',object({players:array(string)})).parameters=[{in:'query',name:'lobbyId',required:true,schema:schemas['lobby:join'].properties.lobbyId}];
const doc = {openapi:'3.1.0',info:{title:'Gambling API',version:'2.0.0',description:'Virtual chips. Account balances exclude active-hand escrow. See docs/PROTOCOL.md and docs/RULES.md.'},servers:[{url:'/'}],paths,
components:{securitySchemes:{bearerAuth:{type:'http',scheme:'bearer',bearerFormat:'JWT'},cookieAuth:{type:'apiKey',in:'cookie',name:'authorization'}}}};
const card = object({rank:{enum:['A','K','Q','J','10','9','8','7','6','5','4','3','2']},suit:{enum:['S','H','D','C']}});
const result = object({rank:integer,name:string,score:array(integer),comboCards:array(card)});
const outputSchemas = {
'auth:success':object({type:{const:'auth:success'},username:string}),
'command:ack':object({type:{const:'command:ack'},commandType:string,lobbyId:string,username:string,requestId:string}),
error:object({type:{const:'error'},code:string,message:string,requestId:string},['type','code','message']),
'lobby:left':object({type:{const:'lobby:left'},lobbyId:string}),
'lobby:state':object({type:{const:'lobby:state'},lobbyId:string,host:nullableString,players:array(string),isPublic:{type:'boolean'},canStart:{type:'boolean'},gameStarted:{type:'boolean'},gameUrl:string}),
'poker:state':object({type:{const:'poker:state'},lobbyId:string,username:string,players:array(string),round:integer,pot:integer,currentBet:integer,bet:integer,
communityCards:array(card),hand:array(card),currentPlayer:nullableString,isYourTurn:{type:'boolean'},queue:array(string),folded:{type:'object',additionalProperties:{type:'boolean'}},
street:{enum:['intermission','preflop','flop','turn','river']},phase:{enum:['intermission','waiting','betting']},lastAction:string,yourContribution:integer,yourStack:integer,
isSpectator:{type:'boolean'},callAmount:integer,minimumStake:integer,minimumRaise:integer,canRaise:{type:'boolean'},
nextRoundStartsAt:{type:['integer','null']},turnEndsAt:{type:['integer','null']},winner:nullableString,
showdown:{anyOf:[{type:'null'},array(object({player:string,hand:array(card),result}))]},payouts:numberMap})
};
const outputFile = './swagger.json';
const routes = ['./gambling.js'];
/* NOTE: If you are using the express Router, you must pass in the 'routes' only the
root file where the route starts, such as index.js, app.js, routes.js, etc ... */
swaggerAutogen(outputFile, routes, doc);
const websocket = {$schema:'https://json-schema.org/draft/2020-12/schema',title:'WebSocket protocol',oneOf:Object.entries(schemas).filter(([key])=>key!=='credentials').map(([,value])=>value),$defs:outputSchemas};
const artifacts = [['swagger.json',doc],['docs/websocket.schema.json',websocket]];
for (const [filename, data] of artifacts) {
const text = JSON.stringify(data,null,2)+'\n';
if (process.argv.includes('--check')) {if (fs.readFileSync(filename,'utf8') !== text) throw new Error(`${filename} is stale; run npm run docs`);}
else fs.writeFileSync(filename,text);
}
+2898 -280
View File
File diff suppressed because it is too large Load Diff