CI / build + unit + e2e (pull_request) Successful in 3m47s
- ADMIN button in the mission-control header opens a table picker + row grid with add/edit/delete (components/admin-panel.tsx), deliberately unauthenticated by explicit request
- /api/admin/tables and /api/admin/tables/{name}/rows driven off a name→table registry built from lib/db/schema.ts (lib/db/admin.ts) — no per-table code; 404 unknown table, 400 malformed payload, 409 constraint violation
- OpenAPI spec updated for all four admin operations + Admin* schemas
- unit: lib/db/admin.test.ts (registry, CRUD round-trips, composite PKs, error mapping); e2e: read-only admin.spec.ts
56 lines
2.0 KiB
TypeScript
56 lines
2.0 KiB
TypeScript
import { getGameDb } from '@/lib/db/client'
|
|
import { AdminError, deleteAdminRow, insertAdminRow, readAdminRows, updateAdminRow } from '@/lib/db/admin'
|
|
|
|
// #38 — rows of one admin table: GET (capped list), POST (insert), PATCH ({key, values}), DELETE ({key}).
|
|
async function jsonBody(req: Request): Promise<Record<string, unknown>> {
|
|
let body: unknown
|
|
try {
|
|
body = await req.json()
|
|
} catch {
|
|
throw new AdminError(400, 'Invalid JSON body')
|
|
}
|
|
if (typeof body !== 'object' || body === null || Array.isArray(body)) throw new AdminError(400, 'Body must be a JSON object')
|
|
return body as Record<string, unknown>
|
|
}
|
|
|
|
async function run<T>(fn: () => Promise<T>): Promise<Response> {
|
|
try {
|
|
return Response.json(await fn())
|
|
} catch (err) {
|
|
if (err instanceof AdminError) return Response.json({ error: err.message }, { status: err.status })
|
|
throw err
|
|
}
|
|
}
|
|
|
|
export async function GET(req: Request, ctx: { params: Promise<{ name: string }> }) {
|
|
const db = await getGameDb()
|
|
const { name } = await ctx.params
|
|
const url = new URL(req.url)
|
|
const limitRaw = url.searchParams.get('limit')
|
|
const offsetRaw = url.searchParams.get('offset')
|
|
const limit = limitRaw === null ? 50 : Number(limitRaw)
|
|
const offset = offsetRaw === null ? 0 : Number(offsetRaw)
|
|
return run(() => readAdminRows(db, name, limit, offset))
|
|
}
|
|
|
|
export async function POST(req: Request, ctx: { params: Promise<{ name: string }> }) {
|
|
const db = await getGameDb()
|
|
const { name } = await ctx.params
|
|
return run(async () => insertAdminRow(db, name, await jsonBody(req)))
|
|
}
|
|
|
|
export async function PATCH(req: Request, ctx: { params: Promise<{ name: string }> }) {
|
|
const db = await getGameDb()
|
|
const { name } = await ctx.params
|
|
return run(async () => {
|
|
const body = await jsonBody(req)
|
|
return updateAdminRow(db, name, body.key, body.values)
|
|
})
|
|
}
|
|
|
|
export async function DELETE(req: Request, ctx: { params: Promise<{ name: string }> }) {
|
|
const db = await getGameDb()
|
|
const { name } = await ctx.params
|
|
return run(async () => deleteAdminRow(db, name, (await jsonBody(req)).key))
|
|
}
|