feat(db): add lobbies and lobby_players tables + model
This commit is contained in:
parent
a7be7152cc
commit
47a68c0315
8 changed files with 1310 additions and 10 deletions
122
packages/db/src/models/lobbyModel.ts
Normal file
122
packages/db/src/models/lobbyModel.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { db } from "@lila/db";
|
||||
import { lobbies, lobby_players } from "@lila/db/schema";
|
||||
import { eq, and, sql } from "drizzle-orm";
|
||||
|
||||
import type { LobbyStatus } from "@lila/shared";
|
||||
|
||||
export type Lobby = typeof lobbies.$inferSelect;
|
||||
export type LobbyPlayer = typeof lobby_players.$inferSelect;
|
||||
export type LobbyWithPlayers = Lobby & {
|
||||
players: (LobbyPlayer & { user: { id: string; name: string } })[];
|
||||
};
|
||||
|
||||
export const createLobby = async (
|
||||
code: string,
|
||||
hostUserId: string,
|
||||
): Promise<Lobby> => {
|
||||
const [newLobby] = await db
|
||||
.insert(lobbies)
|
||||
.values({ code, hostUserId, status: "waiting" })
|
||||
.returning();
|
||||
|
||||
if (!newLobby) {
|
||||
throw new Error("Failed to create lobby");
|
||||
}
|
||||
|
||||
return newLobby;
|
||||
};
|
||||
|
||||
export const getLobbyByCodeWithPlayers = async (
|
||||
code: string,
|
||||
): Promise<LobbyWithPlayers | undefined> => {
|
||||
return db.query.lobbies.findFirst({
|
||||
where: eq(lobbies.code, code),
|
||||
with: {
|
||||
players: { with: { user: { columns: { id: true, name: true } } } },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const updateLobbyStatus = async (
|
||||
lobbyId: string,
|
||||
status: LobbyStatus,
|
||||
): Promise<void> => {
|
||||
await db.update(lobbies).set({ status }).where(eq(lobbies.id, lobbyId));
|
||||
};
|
||||
|
||||
export const deleteLobby = async (lobbyId: string): Promise<void> => {
|
||||
await db.delete(lobbies).where(eq(lobbies.id, lobbyId));
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomically inserts a player into a lobby. Returns the new player row,
|
||||
* or undefined if the insert was skipped because:
|
||||
* - the lobby is at capacity, or
|
||||
* - the lobby is not in 'waiting' status, or
|
||||
* - the user is already in the lobby (PK conflict).
|
||||
*
|
||||
* Callers are expected to pre-check these conditions against a hydrated
|
||||
* lobby state to produce specific error messages; the undefined return
|
||||
* is a safety net for concurrent races.
|
||||
*/
|
||||
export const addPlayer = async (
|
||||
lobbyId: string,
|
||||
userId: string,
|
||||
maxPlayers: number,
|
||||
): Promise<LobbyPlayer | undefined> => {
|
||||
const result = await db.execute(sql`
|
||||
INSERT INTO lobby_players (lobby_id, user_id)
|
||||
SELECT ${lobbyId}::uuid, ${userId}
|
||||
WHERE (
|
||||
SELECT COUNT(*) FROM lobby_players WHERE lobby_id = ${lobbyId}::uuid
|
||||
) < ${maxPlayers}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM lobbies WHERE id = ${lobbyId}::uuid AND status = 'waiting'
|
||||
)
|
||||
ON CONFLICT (lobby_id, user_id) DO NOTHING
|
||||
`);
|
||||
|
||||
if (!result.rowCount) return undefined;
|
||||
const [player] = await db
|
||||
.select()
|
||||
.from(lobby_players)
|
||||
.where(
|
||||
and(eq(lobby_players.lobbyId, lobbyId), eq(lobby_players.userId, userId)),
|
||||
);
|
||||
|
||||
return player;
|
||||
};
|
||||
|
||||
export const removePlayer = async (
|
||||
lobbyId: string,
|
||||
userId: string,
|
||||
): Promise<void> => {
|
||||
await db
|
||||
.delete(lobby_players)
|
||||
.where(
|
||||
and(eq(lobby_players.lobbyId, lobbyId), eq(lobby_players.userId, userId)),
|
||||
);
|
||||
};
|
||||
|
||||
export const finishGame = async (
|
||||
lobbyId: string,
|
||||
scoresByUser: Map<string, number>,
|
||||
): Promise<void> => {
|
||||
await db.transaction(async (tx) => {
|
||||
for (const [userId, score] of scoresByUser) {
|
||||
await tx
|
||||
.update(lobby_players)
|
||||
.set({ score })
|
||||
.where(
|
||||
and(
|
||||
eq(lobby_players.lobbyId, lobbyId),
|
||||
eq(lobby_players.userId, userId),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tx
|
||||
.update(lobbies)
|
||||
.set({ status: "finished" })
|
||||
.where(eq(lobbies.id, lobbyId));
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue