feat(api): GET /api/events endpoint + OpenAPI
Latest game events, newest first; auto-initializes like the other reads. ?limit= defaults to 50 (cap 200), non-integer/out-of-range → 400. OpenAPI spec gains the path + Event schema (14 -> 15 paths).
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { getGameDb } from '@/lib/db/client'
|
||||
import { ensureSave } from '@/lib/game/save'
|
||||
import { EventError, getEvents, parseEventLimit } from '@/lib/game/events'
|
||||
|
||||
// The latest game events, newest first; auto-initializes on first hit.
|
||||
export async function GET(req: Request) {
|
||||
const db = await getGameDb()
|
||||
await ensureSave(db) // getEvents assumes a save exists
|
||||
|
||||
let limit: number
|
||||
try {
|
||||
limit = parseEventLimit(new URL(req.url).searchParams.get('limit'))
|
||||
} catch (err) {
|
||||
if (err instanceof EventError) {
|
||||
return Response.json({ error: err.message }, { status: err.status })
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
return Response.json(await getEvents(db, limit))
|
||||
}
|
||||
@@ -6,6 +6,7 @@ describe('OpenAPI spec', () => {
|
||||
it('documents every public endpoint', () => {
|
||||
expect(Object.keys(openApiSpec.paths).sort()).toEqual([
|
||||
'/api/crew',
|
||||
'/api/events',
|
||||
'/api/explore',
|
||||
'/api/jump',
|
||||
'/api/missions',
|
||||
@@ -77,6 +78,22 @@ describe('OpenAPI spec', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('describes GET /api/events → Event[] with a limit query param and a 400 error response', () => {
|
||||
const events = openApiSpec.paths['/api/events'].get.responses[200].content['application/json'].schema;
|
||||
expect(events.type).toBe('array');
|
||||
expect((events.items as { $ref: string }).$ref).toBe('#/components/schemas/Event');
|
||||
|
||||
const props = openApiSpec.components.schemas.Event.properties;
|
||||
expect(Object.keys(props)).toEqual(['id', 'turn', 'type', 'message', 'createdAt']);
|
||||
expect((props.type as { enum: string[] }).enum).toEqual(['new_game', 'jump', 'explore', 'research', 'upgrade', 'expand', 'build', 'mission']);
|
||||
|
||||
const get = openApiSpec.paths['/api/events'].get;
|
||||
const limit = (get.parameters ?? []).find((p) => p.name === 'limit');
|
||||
expect(limit?.in).toBe('query');
|
||||
expect(limit?.required).toBe(false);
|
||||
expect(get.responses['400'].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
||||
});
|
||||
|
||||
it('describes /api/crew as an array of CrewMember and crewFound on ExploreResult', () => {
|
||||
const crew = openApiSpec.paths['/api/crew'].get.responses[200].content['application/json'].schema;
|
||||
expect(crew.type).toBe('array');
|
||||
|
||||
@@ -523,6 +523,40 @@ export const openApiSpec = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/events': {
|
||||
get: {
|
||||
summary: 'Event log',
|
||||
description: "The save's game events, newest first — every action (new game, jump, scan, research, ship work, mission) appends one. Auto-initializes a new game when no save exists yet.",
|
||||
operationId: 'getEvents',
|
||||
parameters: [
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: { type: 'integer', minimum: 1, maximum: 200 },
|
||||
description: 'How many of the latest events to return (default 50)',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Latest events, newest first',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { type: 'array', items: { $ref: '#/components/schemas/Event' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
400: {
|
||||
description: 'limit is not an integer in [1, 200]',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
@@ -682,6 +716,17 @@ export const openApiSpec = {
|
||||
},
|
||||
required: ['id', 'type', 'description', 'targetSystemId', 'conditionKind', 'progressTarget', 'progress', 'rewardKind', 'rewardQty', 'state'],
|
||||
},
|
||||
Event: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
turn: { type: 'integer', description: 'The save turn the action cost (new_game is 0)' },
|
||||
type: { type: 'string', enum: ['new_game', 'jump', 'explore', 'research', 'upgrade', 'expand', 'build', 'mission'] },
|
||||
message: { type: 'string' },
|
||||
createdAt: { type: 'string', description: 'ISO 8601 timestamp' },
|
||||
},
|
||||
required: ['id', 'turn', 'type', 'message', 'createdAt'],
|
||||
},
|
||||
UpgradeRequest: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user