api: GET /api/world + POST /api/jump, OpenAPI spec updated in code
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import { getGameDb } from '@/lib/db/client'
|
||||||
|
import { JumpError, jumpToSystem } from '@/lib/game/save'
|
||||||
|
|
||||||
|
// Jump to an adjacent solar system. Body: { "systemId": number }. Returns the fresh world state.
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
let body: unknown
|
||||||
|
try {
|
||||||
|
body = await req.json()
|
||||||
|
} catch {
|
||||||
|
return Response.json({ error: 'Invalid JSON body' }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemId = (body as { systemId?: unknown }).systemId
|
||||||
|
if (typeof systemId !== 'number' || !Number.isInteger(systemId)) {
|
||||||
|
return Response.json({ error: 'systemId must be an integer' }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = await getGameDb()
|
||||||
|
return Response.json(await jumpToSystem(db, systemId))
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof JumpError) {
|
||||||
|
return Response.json({ error: err.message }, { status: err.status })
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { getGameDb } from '@/lib/db/client'
|
||||||
|
import { getWorldState } from '@/lib/game/save'
|
||||||
|
|
||||||
|
// The full world state for the single active save; auto-initializes on first hit.
|
||||||
|
export async function GET() {
|
||||||
|
const db = await getGameDb()
|
||||||
|
return Response.json(await getWorldState(db))
|
||||||
|
}
|
||||||
+113
-1
@@ -8,7 +8,7 @@ export const openApiSpec = {
|
|||||||
info: {
|
info: {
|
||||||
title: 'SpaceWanderer API',
|
title: 'SpaceWanderer API',
|
||||||
description:
|
description:
|
||||||
'JSON API for SpaceWanderer — a single-player turn-based space adventure. Scaffold stage: app metadata only; game endpoints (turn actions, state snapshots) land with the first game systems.',
|
'JSON API for SpaceWanderer — a single-player turn-based space adventure (one save, no accounts). World endpoints: read the generated world state and jump between adjacent solar systems.',
|
||||||
// Sourced from package.json so it stays in sync with every version bump.
|
// Sourced from package.json so it stays in sync with every version bump.
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
},
|
},
|
||||||
@@ -32,6 +32,74 @@ export const openApiSpec = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'/api/world': {
|
||||||
|
get: {
|
||||||
|
summary: 'World state',
|
||||||
|
description:
|
||||||
|
'The full world of the single active save — every known solar system, jump routes and the turn counter. Auto-initializes a new game (seeded world generation) when no save exists yet.',
|
||||||
|
operationId: 'getWorld',
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'Current world state',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/WorldState' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/jump': {
|
||||||
|
post: {
|
||||||
|
summary: 'Jump to an adjacent solar system',
|
||||||
|
description:
|
||||||
|
'Validates that the target is a jump route away from the current system, sets it as current (discovering it if new and generating its neighbours), increments the turn counter and writes a jump event. Returns the fresh world state.',
|
||||||
|
operationId: 'jumpToSystem',
|
||||||
|
requestBody: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/JumpRequest' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'Fresh world state after the jump',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/WorldState' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
description: 'Malformed body or non-integer systemId',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/Error' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
description: 'Unknown system id',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/Error' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
409: {
|
||||||
|
description: 'Target is not adjacent to the current system (or is the current system itself)',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: { $ref: '#/components/schemas/Error' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
schemas: {
|
schemas: {
|
||||||
@@ -42,6 +110,50 @@ export const openApiSpec = {
|
|||||||
},
|
},
|
||||||
required: ['version'],
|
required: ['version'],
|
||||||
},
|
},
|
||||||
|
WorldSystem: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
x: { type: 'number', description: '0–100 chart coordinate' },
|
||||||
|
y: { type: 'number', description: '0–100 chart coordinate' },
|
||||||
|
discovered: { type: 'boolean' },
|
||||||
|
current: { type: 'boolean', description: 'True for the system the ship is in now' },
|
||||||
|
},
|
||||||
|
required: ['id', 'name', 'x', 'y', 'discovered', 'current'],
|
||||||
|
},
|
||||||
|
WorldState: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
systems: { type: 'array', items: { $ref: '#/components/schemas/WorldSystem' } },
|
||||||
|
edges: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'Undirected jump routes as [fromId, toId] pairs (sorted, deduped)',
|
||||||
|
items: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'integer' },
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
turn: { type: 'integer', description: 'Turn counter — one per jump' },
|
||||||
|
},
|
||||||
|
required: ['systems', 'edges', 'turn'],
|
||||||
|
},
|
||||||
|
JumpRequest: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
systemId: { type: 'integer', description: 'solar_systems.id of the jump target' },
|
||||||
|
},
|
||||||
|
required: ['systemId'],
|
||||||
|
},
|
||||||
|
Error: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
error: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['error'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user