Files
SpaceWanderer/lib/openapi.ts
T

330 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pkg from '@/package.json';
// OpenAPI 3.1 document for the app's public JSON API, kept in code so it can be
// served machine-readable at /api/openapi (see AGENTS.md "## API"). Must be updated
// whenever an endpoint changes.
export const openApiSpec = {
openapi: '3.1.0',
info: {
title: 'SpaceWanderer API',
description:
'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.
version: pkg.version,
},
// Empty url = current origin (the spec is served from the same host that runs the API).
servers: [{ url: '' }],
paths: {
'/api/version': {
get: {
summary: 'App version',
description: 'The running app version, read from package.json.',
operationId: 'getVersion',
responses: {
200: {
description: 'Current app version',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/AppVersion' },
},
},
},
},
},
},
'/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' },
},
},
},
},
},
},
'/api/system': {
get: {
summary: 'Current system state',
description:
'The bodies of the current solar system — planets and their moons (nested via parent) with hazard/explored flags — plus the save inventory and turn counter. Auto-initializes a new game when no save exists yet.',
operationId: 'getSystem',
responses: {
200: {
description: 'Current system state',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/SystemState' },
},
},
},
},
},
},
'/api/explore': {
post: {
summary: 'Explore a planet or moon',
description:
'Scans one body of the save with a deterministic haul (a resource and/or a part, or nothing), marks it explored, increments the turn counter, accumulates finds into inventory and writes an explore event. Returns the fresh system state plus this scan outcome.',
operationId: 'explorePlanet',
requestBody: {
required: true,
content: {
'application/json': {
schema: { $ref: '#/components/schemas/ExploreRequest' },
},
},
},
responses: {
200: {
description: 'Fresh system state plus the scan outcome',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/ExploreResult' },
},
},
},
400: {
description: 'Malformed body or non-integer planetId',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' },
},
},
},
404: {
description: 'Unknown planet id',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' },
},
},
},
409: {
description: 'Body was already explored (each can only be scanned once)',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' },
},
},
},
},
},
},
'/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: {
AppVersion: {
type: 'object',
properties: {
version: { type: 'string', description: 'Semver of the running build' },
},
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'],
},
Planet: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
type: { type: 'string', description: 'Body class, e.g. TERRESTRIAL or GAS GIANT' },
hazard: { type: 'boolean' },
explored: { type: 'boolean', description: 'True once scanned — each body can only be explored once' },
parent: { type: ['integer', 'null'], nullable: true, description: 'NULL for a planet, the parent planet id for a moon' },
},
required: ['id', 'name', 'type', 'hazard', 'explored', 'parent'],
},
InventoryItem: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['resource', 'part'] },
name: { type: 'string' },
qty: { type: 'integer' },
},
required: ['kind', 'name', 'qty'],
},
FoundItem: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['resource', 'part'] },
name: { type: 'string' },
qty: { type: 'integer' },
},
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 '<planet>, <system>' for members found while exploring" },
},
required: ['id', 'name', 'role', 'trait_good', 'trait_bad', 'origin'],
},
SystemState: {
type: 'object',
properties: {
systemId: { type: 'integer' },
systemName: { type: 'string' },
planets: {
type: 'array',
description: 'Planets and moons in display order — a moon always follows its parent row',
items: { $ref: '#/components/schemas/Planet' },
},
inventory: { type: 'array', items: { $ref: '#/components/schemas/InventoryItem' } },
turn: { type: 'integer', description: 'Turn counter — one per jump or exploration' },
},
required: ['systemId', 'systemName', 'planets', 'inventory', 'turn'],
},
ExploreRequest: {
type: 'object',
properties: {
planetId: { type: 'integer', description: 'planets.id of the body to scan (planet or moon)' },
},
required: ['planetId'],
},
ExploreResult: {
allOf: [
{ $ref: '#/components/schemas/SystemState' },
{
type: 'object',
properties: {
outcome: {
type: 'array',
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', 'crewFound'],
},
],
},
Error: {
type: 'object',
properties: {
error: { type: 'string' },
},
required: ['error'],
},
},
},
};