Multiplayer scopone scientifico backend on the kaya framework:
- OIDC login (kaya-oidc), session-backed WebSocket auth
- Pure rules engine (forced captures, scopa, primiera scoring) with
full-match simulation tests
- Live game state in Redis (JSON + TTL, join codes, per-game locks,
pub/sub state push); in-memory fallback for tests
- WebSocket /ws/games/{id} for real-time play; REST lobby endpoints
(create/join/snapshot) with hidden-hand views
- Finished matches persisted to Postgres (Tortoise + aerich) for match
history and leaderboard endpoints
- Docker Compose stack: postgres, redis, mock-oauth2-server, db-migrate, app
- 45 tests passing; mypy clean
68 lines
3.0 KiB
Python
68 lines
3.0 KiB
Python
from tortoise import BaseDBAsyncClient
|
|
|
|
RUN_IN_TRANSACTION = True
|
|
|
|
|
|
async def upgrade(db: BaseDBAsyncClient) -> str:
|
|
return """
|
|
CREATE TABLE IF NOT EXISTS "match" (
|
|
"id" UUID NOT NULL PRIMARY KEY,
|
|
"team_a_score" SMALLINT NOT NULL,
|
|
"team_b_score" SMALLINT NOT NULL,
|
|
"winner_team" VARCHAR(1) NOT NULL,
|
|
"target_score" SMALLINT NOT NULL,
|
|
"hands_played" SMALLINT NOT NULL,
|
|
"started_at" TIMESTAMPTZ NOT NULL,
|
|
"finished_at" TIMESTAMPTZ NOT NULL
|
|
);
|
|
COMMENT ON TABLE "match" IS 'A completed scopone scientifico match.';
|
|
CREATE TABLE IF NOT EXISTS "match_player" (
|
|
"id" UUID NOT NULL PRIMARY KEY,
|
|
"user_sub" VARCHAR(255) NOT NULL,
|
|
"display_name" VARCHAR(200) NOT NULL,
|
|
"seat" SMALLINT NOT NULL,
|
|
"team" VARCHAR(1) NOT NULL,
|
|
"won" BOOL NOT NULL,
|
|
"match_id" UUID NOT NULL REFERENCES "match" ("id") ON DELETE CASCADE,
|
|
CONSTRAINT "uid_match_playe_match_i_f85e3e" UNIQUE ("match_id", "user_sub")
|
|
);
|
|
COMMENT ON TABLE "match_player" IS 'Participation of one user in one match.';
|
|
CREATE INDEX IF NOT EXISTS "idx_match_playe_user_su_914c0d" ON "match_player" ("user_sub");
|
|
CREATE TABLE IF NOT EXISTS "aerich" (
|
|
"id" SERIAL NOT NULL PRIMARY KEY,
|
|
"version" VARCHAR(255) NOT NULL,
|
|
"app" VARCHAR(100) NOT NULL,
|
|
"content" JSONB NOT NULL
|
|
);"""
|
|
|
|
|
|
async def downgrade(db: BaseDBAsyncClient) -> str:
|
|
return """
|
|
"""
|
|
|
|
|
|
MODELS_STATE = (
|
|
"eJztmW1v2joUx79KlFedtFWFdu10NU0KlGpsBaqS7l5tmiyTGLCa2FnsXIZ6+92v7Tw7Dy"
|
|
"3dmErFG0iOfZyTnx2f80/uTJ+6yGOHI8idpfmXcWcS6CNxUG54bZgwCHKzNHA48+KeWZcZ"
|
|
"4yF0uDDOoceQMLmIOSEOOKZEdrUMh/qBhzhyDebQgBIk/jEiHM+xQw011KEcy6WOGAyTxW"
|
|
"ZuEcE/IgQ4XSC+RKFw/vZdmDFx0U/E0tPgFswx8tzSDWNXDqDsgK8DZbu5GZ5fqJ4ypBlw"
|
|
"qBf5JO8drPmSkqx7FGH3UPrItgUiKIQi5AIOEnlegi01xRELAw8jlIXq5gYXzWHkSajm+3"
|
|
"lEHMnSUFeSPycfktAK3QAYT2wwHdgAmJU5kCFofBOTQ4mcP0y4BHV3H4+bA1FWU16g/9G6"
|
|
"Pjg+faUQUMYXoWpUuMx75Qg5jF0V9JwyR9AHEIhZDFGV99SHnjckvJ657qvRF4E/hXtqyM"
|
|
"HnizdFmkLbAmYRr/h7c9w9O30nWlWM8uSshf90ZF1eDse2Yq2xnf0C29mebRPbFSbi1oHE"
|
|
"VEXbX8KwHqvmplEVse4iVR/+BB4iCy4zRqeF5RfrWm0VHbVTUJEb4owxThq6skVbwzAUW/"
|
|
"cT17Dmu1/DZbZLSFwGAg+uUU2ua2er++7ZltkysfjEfQPIq2TPBRGOfVRPtuypcXUT18P0"
|
|
"YAcpt8C0h6PB1LZGV3J4n7EfnuJl2QPZ0lXWtWY9ONV2k2wQ4++h/dGQp8bXyXigVydZP/"
|
|
"urKWOCEaeA0BWAbpFJak5NpWmeY4LZ8knzrLnuJ/rZTbSUBvPbQtkqDTPo3K5g6IJSS74i"
|
|
"1JYYsupq6CWOF5+vkQcVy+q0F1XWlRppd+c9t5qxeFFAaZc2Ea02+V1ft0ACF+qW5LXllW"
|
|
"qQNenWnOgD6jVObOHjROyV2K+xgwM1pwadG1KORgyFBibquEHFbuBXI2O/5Tpb+gAWzczv"
|
|
"e237nLRtNi8bCISiz7bUQQX2HxMH3bdvHyEPRK9GgaDayqWWi5l8XIE63wC17vcCxVj36O"
|
|
"gxvI+OmnnLNq20RXXFTrtcSH32MqH6imaTNbt/cdCwTisvDlZxfaXVYJR6CJKGdzO1FdlM"
|
|
"uOwg2xaYvcnkslR294a2hvVm1BuktEUnzJW5unzjYmmzaqLo8ztriucL/IESoiIzNL5VuB"
|
|
"c0RHhBPqO1QjwUgUDi1CUx/avNbkKtKAlhDuEqK2xLa0rcvbhnFC/ZvjXtW+cD875Zum1T"
|
|
"lFgoxPXf0ZKW121SBOZ9HhIhzUR/8xewxgxf+0DX5Pdk+n5NHWw/uXc7J2cn745PT7IMn1"
|
|
"na0ny6RTaLgn9RyHBdbmpO+gWXF5j3t6IJ5EO1AeGk+wuk23mUAui0KIBOVQGIK3JEakTA"
|
|
"p+lkXE+44KK/6cQON/4zPMx2UQu0wJUwSnVWyvRgZP2j4+5fTnp6fSAH6NUVCH8ymd3/D7"
|
|
"yE7wc="
|
|
)
|