diff --git a/drizzle/0001_swift_miek.sql b/drizzle/0001_swift_miek.sql new file mode 100644 index 0000000..672856d --- /dev/null +++ b/drizzle/0001_swift_miek.sql @@ -0,0 +1,3 @@ +CREATE INDEX "idx_mappool_slot_mappool" ON "mappool_slot" USING btree ("mappool_id");--> statement-breakpoint +CREATE INDEX "idx_match_created_at" ON "match" USING btree ("created_at");--> statement-breakpoint +CREATE INDEX "idx_mpp_user" ON "match_participant_player" USING btree ("user_id"); \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..bd9e509 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,1806 @@ +{ + "id": "f27c8b9e-e9d6-47af-95aa-0014d9a77581", + "prevId": "2a33222f-3ef2-41cd-9454-eacae15729f6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.mappool": { + "name": "mappool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mappool_slot": { + "name": "mappool_slot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mappool_id": { + "name": "mappool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_in_category": { + "name": "order_in_category", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "beatmap_id": { + "name": "beatmap_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "star_rating": { + "name": "star_rating", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "bpm": { + "name": "bpm", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "total_length": { + "name": "total_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "mods": { + "name": "mods", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artist": { + "name": "artist", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "list_cover_url": { + "name": "list_cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_mappool_slot_mappool": { + "name": "idx_mappool_slot_mappool", + "columns": [ + { + "expression": "mappool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mappool_slot_mappool_id_mappool_id_fk": { + "name": "mappool_slot_mappool_id_mappool_id_fk", + "tableFrom": "mappool_slot", + "tableTo": "mappool", + "columnsFrom": [ + "mappool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match": { + "name": "match", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'CREATED'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "mappool_id": { + "name": "mappool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "osu_lobby_id": { + "name": "osu_lobby_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_match_state": { + "name": "idx_match_state", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_created_at": { + "name": "idx_match_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_mappool_id_mappool_id_fk": { + "name": "match_mappool_id_mappool_id_fk", + "tableFrom": "match", + "tableTo": "mappool", + "columnsFrom": [ + "mappool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_winner_id_team_id_fk": { + "name": "match_winner_id_team_id_fk", + "tableFrom": "match", + "tableTo": "team", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_game": { + "name": "match_game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "match_id": { + "name": "match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_number": { + "name": "game_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mappool_slot_id": { + "name": "mappool_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "picked_by_participant_id": { + "name": "picked_by_participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winner_participant_id": { + "name": "winner_participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PENDING'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_match_game_match": { + "name": "idx_match_game_match", + "columns": [ + { + "expression": "match_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_game_match_id_match_id_fk": { + "name": "match_game_match_id_match_id_fk", + "tableFrom": "match_game", + "tableTo": "match", + "columnsFrom": [ + "match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "match_game_mappool_slot_id_mappool_slot_id_fk": { + "name": "match_game_mappool_slot_id_mappool_slot_id_fk", + "tableFrom": "match_game", + "tableTo": "mappool_slot", + "columnsFrom": [ + "mappool_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_game_picked_by_participant_id_match_participant_id_fk": { + "name": "match_game_picked_by_participant_id_match_participant_id_fk", + "tableFrom": "match_game", + "tableTo": "match_participant", + "columnsFrom": [ + "picked_by_participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_game_winner_participant_id_match_participant_id_fk": { + "name": "match_game_winner_participant_id_match_participant_id_fk", + "tableFrom": "match_game", + "tableTo": "match_participant", + "columnsFrom": [ + "winner_participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_game_score": { + "name": "match_game_score", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "match_game_id": { + "name": "match_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "player_id": { + "name": "player_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "accuracy": { + "name": "accuracy", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_combo": { + "name": "max_combo", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "count_300": { + "name": "count_300", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "count_100": { + "name": "count_100", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "count_50": { + "name": "count_50", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "count_miss": { + "name": "count_miss", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "mods": { + "name": "mods", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "passed": { + "name": "passed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pp": { + "name": "pp", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_mgs_game": { + "name": "idx_mgs_game", + "columns": [ + { + "expression": "match_game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_game_score_match_game_id_match_game_id_fk": { + "name": "match_game_score_match_game_id_match_game_id_fk", + "tableFrom": "match_game_score", + "tableTo": "match_game", + "columnsFrom": [ + "match_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "match_game_score_player_id_match_participant_player_id_fk": { + "name": "match_game_score_player_id_match_participant_player_id_fk", + "tableFrom": "match_game_score", + "tableTo": "match_participant_player", + "columnsFrom": [ + "player_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_invite": { + "name": "match_invite", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_team_id": { + "name": "creator_team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invited_team_id": { + "name": "invited_team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "mappool_id": { + "name": "mappool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "match_id": { + "name": "match_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responded_at": { + "name": "responded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_invite_invited_team": { + "name": "idx_invite_invited_team", + "columns": [ + { + "expression": "invited_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invite_created_by": { + "name": "idx_invite_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invite_status": { + "name": "idx_invite_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_invite_creator_team_id_team_id_fk": { + "name": "match_invite_creator_team_id_team_id_fk", + "tableFrom": "match_invite", + "tableTo": "team", + "columnsFrom": [ + "creator_team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_invite_invited_team_id_team_id_fk": { + "name": "match_invite_invited_team_id_team_id_fk", + "tableFrom": "match_invite", + "tableTo": "team", + "columnsFrom": [ + "invited_team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_invite_mappool_id_mappool_id_fk": { + "name": "match_invite_mappool_id_mappool_id_fk", + "tableFrom": "match_invite", + "tableTo": "mappool", + "columnsFrom": [ + "mappool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "match_invite_match_id_match_id_fk": { + "name": "match_invite_match_id_match_id_fk", + "tableFrom": "match_invite", + "tableTo": "match", + "columnsFrom": [ + "match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_participant": { + "name": "match_participant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "match_id": { + "name": "match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "roll_value": { + "name": "roll_value", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pick_order": { + "name": "pick_order", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_mp_match_team": { + "name": "idx_mp_match_team", + "columns": [ + { + "expression": "match_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_mp_match_slot": { + "name": "idx_mp_match_slot", + "columns": [ + { + "expression": "match_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_mp_match": { + "name": "idx_mp_match", + "columns": [ + { + "expression": "match_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_participant_match_id_match_id_fk": { + "name": "match_participant_match_id_match_id_fk", + "tableFrom": "match_participant", + "tableTo": "match", + "columnsFrom": [ + "match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "match_participant_team_id_team_id_fk": { + "name": "match_participant_team_id_team_id_fk", + "tableFrom": "match_participant", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_participant_player": { + "name": "match_participant_player", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_mpp_participant_user": { + "name": "idx_mpp_participant_user", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_mpp_participant": { + "name": "idx_mpp_participant", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_mpp_user": { + "name": "idx_mpp_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_participant_player_participant_id_match_participant_id_fk": { + "name": "match_participant_player_participant_id_match_participant_id_fk", + "tableFrom": "match_participant_player", + "tableTo": "match_participant", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.match_queue": { + "name": "match_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "elo": { + "name": "elo", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1000 + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_queue_user": { + "name": "idx_queue_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_queue_elo": { + "name": "idx_queue_elo", + "columns": [ + { + "expression": "elo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "match_queue_team_id_team_id_fk": { + "name": "match_queue_team_id_team_id_fk", + "tableFrom": "match_queue", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "actioned_at": { + "name": "actioned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_notification_user": { + "name": "idx_notification_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_user_read": { + "name": "idx_notification_user_read", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.player_rating": { + "name": "player_rating", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "elo": { + "name": "elo", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1000 + }, + "wins": { + "name": "wins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "losses": { + "name": "losses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "initial_elo": { + "name": "initial_elo", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "osu_rank_at_seed": { + "name": "osu_rank_at_seed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "player_rating_user_id_unique": { + "name": "player_rating_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_personal": { + "name": "is_personal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'player'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 2e7ae40..bd01808 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1773082177642, "tag": "0000_tricky_jane_foster", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1773426173223, + "tag": "0001_swift_miek", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 2293671..8f5c49e 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -45,25 +45,29 @@ export const mappool = pgTable('mappool', { }); // ── Mappool Slots ─────────────────────────────────────────────────────── -export const mappoolSlot = pgTable('mappool_slot', { - id: uuid('id').primaryKey().defaultRandom(), - mappoolId: uuid('mappool_id') - .notNull() - .references(() => mappool.id, { onDelete: 'cascade' }), - category: text('category').notNull(), - orderInCategory: integer('order_in_category').notNull(), - beatmapId: text('beatmap_id').notNull(), - starRating: real('star_rating'), - bpm: real('bpm'), - totalLength: integer('total_length'), - mods: text('mods').array().default([]).notNull(), - // ── Cached beatmap metadata (stored at insert time → zero API calls on page load) ── - title: text('title'), - artist: text('artist'), - version: text('version'), - coverUrl: text('cover_url'), - listCoverUrl: text('list_cover_url') -}); +export const mappoolSlot = pgTable( + 'mappool_slot', + { + id: uuid('id').primaryKey().defaultRandom(), + mappoolId: uuid('mappool_id') + .notNull() + .references(() => mappool.id, { onDelete: 'cascade' }), + category: text('category').notNull(), + orderInCategory: integer('order_in_category').notNull(), + beatmapId: text('beatmap_id').notNull(), + starRating: real('star_rating'), + bpm: real('bpm'), + totalLength: integer('total_length'), + mods: text('mods').array().default([]).notNull(), + // ── Cached beatmap metadata (stored at insert time → zero API calls on page load) ── + title: text('title'), + artist: text('artist'), + version: text('version'), + coverUrl: text('cover_url'), + listCoverUrl: text('list_cover_url') + }, + (t) => [index('idx_mappool_slot_mappool').on(t.mappoolId)] +); // ── Matches ───────────────────────────────────────────────────────────── export const match = pgTable( @@ -82,7 +86,7 @@ export const match = pgTable( createdBy: text('created_by'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }, - (t) => [index('idx_match_state').on(t.state)] + (t) => [index('idx_match_state').on(t.state), index('idx_match_created_at').on(t.createdAt)] ); // ── Match Participants (a team in a match) ────────────────────────────── @@ -120,7 +124,8 @@ export const matchParticipantPlayer = pgTable( }, (t) => [ uniqueIndex('idx_mpp_participant_user').on(t.participantId, t.userId), - index('idx_mpp_participant').on(t.participantId) + index('idx_mpp_participant').on(t.participantId), + index('idx_mpp_user').on(t.userId) ] ); diff --git a/src/lib/server/match/queue.test.ts b/src/lib/server/match/queue.test.ts index d17bd87..d6200c9 100644 --- a/src/lib/server/match/queue.test.ts +++ b/src/lib/server/match/queue.test.ts @@ -1,5 +1,4 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { MATCH_STATES } from './types'; const mocks = { db: { @@ -8,6 +7,7 @@ const mocks = { playerRating: { findFirst: vi.fn() }, matchQueue: { findFirst: vi.fn() } }, + select: vi.fn(), delete: vi.fn() } }; @@ -18,6 +18,7 @@ vi.mock('drizzle-orm', () => ({ eq: vi.fn(() => ({})), and: vi.fn(() => ({})), asc: vi.fn(() => ({})), + desc: vi.fn(() => ({})), lt: vi.fn(() => ({})), inArray: vi.fn(() => ({})), sql: vi.fn(() => ({})), @@ -26,6 +27,7 @@ vi.mock('drizzle-orm', () => ({ vi.mock('$lib/server/db/schema', () => ({ match: {}, + matchParticipant: {}, matchQueue: {}, matchParticipantPlayer: {}, playerRating: {} @@ -43,11 +45,23 @@ vi.mock('$lib/server/match/engine', () => ({ const { joinQueue } = await import('./queue'); +function mockFindRecentActiveMatchRows(rows: Array<{ id: string }>) { + const limit = vi.fn().mockResolvedValue(rows); + const orderBy = vi.fn(() => ({ limit })); + const where = vi.fn(() => ({ orderBy })); + const innerJoinSecond = vi.fn(() => ({ where })); + const innerJoinFirst = vi.fn(() => ({ innerJoin: innerJoinSecond })); + const from = vi.fn(() => ({ innerJoin: innerJoinFirst })); + + mocks.db.select.mockReturnValue({ from }); +} + describe('joinQueue', () => { beforeEach(() => { mocks.db.query.matchParticipantPlayer.findMany.mockReset(); mocks.db.query.playerRating.findFirst.mockReset(); mocks.db.query.matchQueue.findFirst.mockReset(); + mocks.db.select.mockReset(); mocks.db.delete.mockReset(); }); @@ -56,13 +70,7 @@ describe('joinQueue', () => { where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) })) }); - mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([ - { - participant: { - match: { id: 'match-1', state: MATCH_STATES.PLAYING, createdAt: new Date().toISOString() } - } - } - ]); + mockFindRecentActiveMatchRows([{ id: 'match-1' }]); await expect(joinQueue('user-1', 'team-1')).rejects.toThrow( 'You are already in an active match' @@ -75,7 +83,7 @@ describe('joinQueue', () => { where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) })) }); - mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]); + mockFindRecentActiveMatchRows([]); mocks.db.query.playerRating.findFirst.mockResolvedValue(null); await expect(joinQueue('user-1', 'team-1')).rejects.toThrow( @@ -88,7 +96,7 @@ describe('joinQueue', () => { where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) })) }); - mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]); + mockFindRecentActiveMatchRows([]); mocks.db.query.playerRating.findFirst.mockResolvedValue({ elo: 1500 }); mocks.db.query.matchQueue.findFirst.mockResolvedValue({ userId: 'user-1', teamId: 'team-1' }); diff --git a/src/lib/server/match/queue.ts b/src/lib/server/match/queue.ts index 563f70c..d2d9f11 100644 --- a/src/lib/server/match/queue.ts +++ b/src/lib/server/match/queue.ts @@ -1,6 +1,12 @@ import { db } from '$lib/server/db'; -import { match, matchQueue, matchParticipantPlayer, playerRating } from '$lib/server/db/schema'; -import { eq, asc, sql, inArray, lt } from 'drizzle-orm'; +import { + match, + matchParticipant, + matchParticipantPlayer, + matchQueue, + playerRating +} from '$lib/server/db/schema'; +import { and, asc, desc, eq, inArray, lt, sql } from 'drizzle-orm'; import { MATCH_STATES } from './types'; import { createMatch } from './engine'; @@ -75,19 +81,6 @@ export async function getQueueStatus(userId: string) { * Used to redirect players who were matched via queue while polling. */ async function findRecentActiveMatch(userId: string): Promise { - // Find matchParticipantPlayer records for this user - const playerEntries = await db.query.matchParticipantPlayer.findMany({ - where: eq(matchParticipantPlayer.userId, userId), - with: { - participant: { - with: { - match: true - } - } - } - }); - - // Find the most recent match that's still active (not finished/cancelled) const activeStates = [ MATCH_STATES.CREATED, MATCH_STATES.LOBBY, @@ -96,12 +89,16 @@ async function findRecentActiveMatch(userId: string): Promise { MATCH_STATES.PLAYING ]; - const activeMatches = playerEntries - .filter((pe) => activeStates.includes(pe.participant.match.state as any)) - .map((pe) => pe.participant.match) - .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + const [activeMatch] = await db + .select({ id: match.id }) + .from(matchParticipantPlayer) + .innerJoin(matchParticipant, eq(matchParticipantPlayer.participantId, matchParticipant.id)) + .innerJoin(match, eq(matchParticipant.matchId, match.id)) + .where(and(eq(matchParticipantPlayer.userId, userId), inArray(match.state, activeStates))) + .orderBy(desc(match.createdAt)) + .limit(1); - return activeMatches[0]?.id ?? null; + return activeMatch?.id ?? null; } const MAX_CONCURRENT_MATCHES = 4; diff --git a/src/lib/stores/queue.svelte.ts b/src/lib/stores/queue.svelte.ts index 4a4b7f1..735081f 100644 --- a/src/lib/stores/queue.svelte.ts +++ b/src/lib/stores/queue.svelte.ts @@ -12,6 +12,8 @@ const queue = $state({ loading: false }); +const QUEUE_POLL_INTERVAL_MS = 8000; + let pollInterval: ReturnType | null = null; async function fetchQueueStatus() { @@ -57,7 +59,7 @@ async function leaveQueue() { function startPolling() { if (pollInterval) return; fetchQueueStatus(); - pollInterval = setInterval(fetchQueueStatus, 4000); + pollInterval = setInterval(fetchQueueStatus, QUEUE_POLL_INTERVAL_MS); } function stopPolling() { diff --git a/src/routes/(app)/matches/+page.server.ts b/src/routes/(app)/matches/+page.server.ts index 59bfa1c..7c96d02 100644 --- a/src/routes/(app)/matches/+page.server.ts +++ b/src/routes/(app)/matches/+page.server.ts @@ -12,7 +12,40 @@ export const load: PageServerLoad = async ({ locals }) => { requireAuth(locals); const matches = await db.query.match.findMany({ - with: { participants: { with: { team: true } }, mappool: { with: { slots: true } } }, + columns: { + id: true, + name: true, + state: true, + config: true, + createdAt: true, + finishedAt: true, + winnerId: true + }, + with: { + participants: { + columns: { + teamId: true, + score: true, + slot: true + }, + orderBy: (p, { asc }) => [asc(p.slot)], + with: { + team: { + columns: { + id: true, + name: true, + avatarUrl: true + } + } + } + }, + mappool: { + columns: { + id: true, + name: true + } + } + }, orderBy: desc(match.createdAt), limit: 50 });