Compare commits
No commits in common. "b0aef8cc16c98026ea36187016ff8997aa7fe4e3" and "cf56399a5ed9c660b23e72c4f9455bdd15e46905" have entirely different histories.
b0aef8cc16
...
cf56399a5e
9 changed files with 0 additions and 145 deletions
|
|
@ -1,37 +0,0 @@
|
|||
import type { Request, Response, NextFunction } from "express";
|
||||
import { createLobby, joinLobby } from "../services/lobbyService.js";
|
||||
|
||||
export const createLobbyHandler = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
try {
|
||||
const userId = req.session!.user.id;
|
||||
const lobby = await createLobby(userId);
|
||||
res.json({ success: true, data: lobby });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const joinLobbyHandler = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
try {
|
||||
const userId = req.session!.user.id;
|
||||
const code = req.params["code"];
|
||||
if (!code) {
|
||||
return next(new Error("Missing code param"));
|
||||
}
|
||||
if (typeof code !== "string") {
|
||||
return next(new Error("Missing or invalid code param"));
|
||||
}
|
||||
const lobby = await joinLobby(code, userId);
|
||||
res.json({ success: true, data: lobby });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
|
@ -19,9 +19,3 @@ export class NotFoundError extends AppError {
|
|||
super(message, 404);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends AppError {
|
||||
constructor(message: string) {
|
||||
super(message, 409);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,5 @@ export const requireAuth = async (
|
|||
return;
|
||||
}
|
||||
|
||||
req.session = session;
|
||||
|
||||
next();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ import express from "express";
|
|||
import { Router } from "express";
|
||||
import { healthRouter } from "./healthRouter.js";
|
||||
import { gameRouter } from "./gameRouter.js";
|
||||
import { lobbyRouter } from "./lobbyRouter.js";
|
||||
|
||||
export const apiRouter: Router = express.Router();
|
||||
|
||||
apiRouter.use("/health", healthRouter);
|
||||
apiRouter.use("/game", gameRouter);
|
||||
apiRouter.use("/lobbies", lobbyRouter);
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import express from "express";
|
||||
import type { Router } from "express";
|
||||
import {
|
||||
createLobbyHandler,
|
||||
joinLobbyHandler,
|
||||
} from "../controllers/lobbyController.js";
|
||||
import { requireAuth } from "../middleware/authMiddleware.js";
|
||||
|
||||
export const lobbyRouter: Router = express.Router();
|
||||
|
||||
lobbyRouter.use(requireAuth);
|
||||
|
||||
lobbyRouter.post("/", createLobbyHandler);
|
||||
lobbyRouter.post("/:code/join", joinLobbyHandler);
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import { randomInt } from "crypto";
|
||||
import {
|
||||
createLobby as createLobbyModel,
|
||||
getLobbyByCodeWithPlayers,
|
||||
addPlayer,
|
||||
} from "@lila/db";
|
||||
import type { Lobby, LobbyWithPlayers } from "@lila/db";
|
||||
import { MAX_LOBBY_PLAYERS } from "@lila/shared";
|
||||
import { NotFoundError, ConflictError, AppError } from "../errors/AppError.js";
|
||||
|
||||
const CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford Base32
|
||||
const CODE_LENGTH = 6;
|
||||
const MAX_CODE_ATTEMPTS = 5;
|
||||
|
||||
const generateLobbyCode = (): string => {
|
||||
let code = "";
|
||||
for (let i = 0; i < CODE_LENGTH; i++) {
|
||||
code += CODE_ALPHABET[randomInt(CODE_ALPHABET.length)];
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
const isUniqueViolation = (err: unknown): boolean => {
|
||||
return (err as { code?: string })?.code === "23505";
|
||||
};
|
||||
|
||||
export const createLobby = async (hostUserId: string): Promise<Lobby> => {
|
||||
for (let i = 0; i < MAX_CODE_ATTEMPTS; i++) {
|
||||
const code = generateLobbyCode();
|
||||
try {
|
||||
return await createLobbyModel(code, hostUserId);
|
||||
} catch (err) {
|
||||
if (isUniqueViolation(err)) continue;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw new AppError("Could not generate a unique lobby code", 500);
|
||||
};
|
||||
|
||||
export const joinLobby = async (
|
||||
code: string,
|
||||
userId: string,
|
||||
): Promise<LobbyWithPlayers> => {
|
||||
const lobby = await getLobbyByCodeWithPlayers(code);
|
||||
if (!lobby) {
|
||||
throw new NotFoundError(`Lobby not found: ${code}`);
|
||||
}
|
||||
if (lobby.status !== "waiting") {
|
||||
throw new ConflictError("Game has already started");
|
||||
}
|
||||
if (lobby.players.some((p) => p.userId === userId)) {
|
||||
return lobby; // idempotent: already in lobby
|
||||
}
|
||||
if (lobby.players.length >= MAX_LOBBY_PLAYERS) {
|
||||
throw new ConflictError("Lobby is full");
|
||||
}
|
||||
|
||||
const player = await addPlayer(lobby.id, userId, MAX_LOBBY_PLAYERS);
|
||||
if (!player) {
|
||||
// Race fallback: another request filled the last slot, started the game,
|
||||
// or the user joined concurrently. Pre-checks above handle the common cases.
|
||||
throw new ConflictError("Lobby is no longer available");
|
||||
}
|
||||
|
||||
const fresh = await getLobbyByCodeWithPlayers(code);
|
||||
if (!fresh) {
|
||||
throw new AppError("Lobby disappeared during join", 500);
|
||||
}
|
||||
return fresh;
|
||||
};
|
||||
11
apps/api/src/types/express.d.ts
vendored
11
apps/api/src/types/express.d.ts
vendored
|
|
@ -1,11 +0,0 @@
|
|||
import type { Session, User } from "better-auth";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
session?: { session: Session; user: User };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -12,4 +12,3 @@ config({
|
|||
export const db = drizzle(process.env["DATABASE_URL"]!, { schema });
|
||||
|
||||
export * from "./models/termModel.js";
|
||||
export * from "./models/lobbyModel.js";
|
||||
|
|
|
|||
|
|
@ -16,5 +16,3 @@ export type DifficultyLevel = (typeof DIFFICULTY_LEVELS)[number];
|
|||
|
||||
export const LOBBY_STATUSES = ["waiting", "in_progress", "finished"] as const;
|
||||
export type LobbyStatus = (typeof LOBBY_STATUSES)[number];
|
||||
|
||||
export const MAX_LOBBY_PLAYERS = 4;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue