lib/game/events.ts: the latest N events of the save, newest first (ORDER BY id DESC), with parseEventLimit for the optional ?limit= param (default 50, cap 200, 400 otherwise). Unit tests cover default/accept/reject and newest-first ordering + slicing.
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
import { desc } from 'drizzle-orm'
|
|
import * as schema from '@/lib/db/schema'
|
|
import type { GameDb } from '@/lib/db/client'
|
|
|
|
export interface EventDto {
|
|
id: number
|
|
turn: number
|
|
type: string
|
|
message: string
|
|
createdAt: string
|
|
}
|
|
|
|
export const DEFAULT_LIMIT = 50
|
|
export const MAX_LIMIT = 200
|
|
|
|
export class EventError extends Error {
|
|
constructor(
|
|
public readonly status: number,
|
|
message: string,
|
|
) {
|
|
super(message)
|
|
}
|
|
}
|
|
|
|
// Parses the optional ?limit= query param. Absent → default; anything that is not an integer in
|
|
// [1, MAX_LIMIT] is a 400 (the route maps EventError to its status).
|
|
export function parseEventLimit(raw: string | null): number {
|
|
if (raw === null) return DEFAULT_LIMIT
|
|
const limit = Number(raw)
|
|
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) {
|
|
throw new EventError(400, `limit must be an integer between 1 and ${MAX_LIMIT}`)
|
|
}
|
|
return limit
|
|
}
|
|
|
|
// The latest N events of the save, newest first. Assumes a save exists (the route calls ensureSave).
|
|
export async function getEvents(db: GameDb, limit: number): Promise<EventDto[]> {
|
|
const rows = await db.db.select().from(schema.events).orderBy(desc(schema.events.id)).limit(limit)
|
|
return rows.map((e) => ({ id: e.id, turn: e.turn, type: e.type, message: e.message, createdAt: e.createdAt }))
|
|
}
|