diff --git a/app/api/crew/route.ts b/app/api/crew/route.ts new file mode 100644 index 0000000..6ca4457 --- /dev/null +++ b/app/api/crew/route.ts @@ -0,0 +1,8 @@ +import { getGameDb } from '@/lib/db/client' +import { getCrewState } from '@/lib/game/save' + +// The ship's crew roster: starting crew plus members found while exploring. Auto-initializes on first hit. +export async function GET() { + const db = await getGameDb() + return Response.json(await getCrewState(db)) +} diff --git a/lib/openapi.test.ts b/lib/openapi.test.ts index 4da6060..1538aca 100644 --- a/lib/openapi.test.ts +++ b/lib/openapi.test.ts @@ -5,6 +5,7 @@ import { openApiSpec } from './openapi'; describe('OpenAPI spec', () => { it('documents every public endpoint', () => { expect(Object.keys(openApiSpec.paths).sort()).toEqual([ + '/api/crew', '/api/explore', '/api/jump', '/api/system', @@ -13,6 +14,19 @@ describe('OpenAPI spec', () => { ]); }); + 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'); + expect((crew.items as { $ref: string }).$ref).toBe('#/components/schemas/CrewMember'); + + const member = openApiSpec.components.schemas.CrewMember.properties; + expect(Object.keys(member)).toEqual(['id', 'name', 'role', 'trait_good', 'trait_bad', 'origin']); + + const exploreResult = openApiSpec.components.schemas.ExploreResult as { allOf: unknown[] }; + const extra = (exploreResult.allOf[1] as { properties: Record }).properties; + expect(Object.keys(extra)).toEqual(['outcome', 'crewFound']); + }); + it('describes the system endpoints as SystemState in and out', () => { const system = openApiSpec.paths['/api/system'].get.responses[200].content['application/json'].schema; expect(system.$ref).toBe('#/components/schemas/SystemState'); diff --git a/lib/openapi.ts b/lib/openapi.ts index cb22c07..3102655 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -168,6 +168,24 @@ export const openApiSpec = { }, }, }, + '/api/crew': { + get: { + summary: 'Crew roster', + description: + 'The ship crew in join order — the starting crew (seeded on new game) plus members found while exploring planets/moons. Each member has one good and one bad trait and an origin. Auto-initializes a new game when no save exists yet.', + operationId: 'getCrew', + responses: { + 200: { + description: 'Crew roster', + content: { + 'application/json': { + schema: { type: 'array', items: { $ref: '#/components/schemas/CrewMember' } }, + }, + }, + }, + }, + }, + }, }, components: { schemas: { @@ -245,6 +263,18 @@ export const openApiSpec = { }, required: ['kind', 'name', 'qty'], }, + CrewMember: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + role: { type: 'string', description: 'Ship role, e.g. PILOT or ENGINEER' }, + trait_good: { type: 'string', description: 'The member good trait' }, + trait_bad: { type: 'string', description: 'The member bad trait' }, + origin: { type: 'string', description: "'STARTING CREW' or ', ' for members found while exploring" }, + }, + required: ['id', 'name', 'role', 'trait_good', 'trait_bad', 'origin'], + }, SystemState: { type: 'object', properties: { @@ -278,8 +308,12 @@ export const openApiSpec = { description: 'What this scan found — may be empty (some scans find nothing)', items: { $ref: '#/components/schemas/FoundItem' }, }, + crewFound: { + description: 'A crew member who joined the ship on this scan (~18% of scans), null when none', + anyOf: [{ $ref: '#/components/schemas/CrewMember' }, { type: 'null' }], + }, }, - required: ['outcome'], + required: ['outcome', 'crewFound'], }, ], },