1262 lines
50 KiB
TypeScript
1262 lines
50 KiB
TypeScript
import pkg from '@/package.json';
|
||
import { MAX_TEAM_SIZE } from './game/crew';
|
||
|
||
// 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. Jump, exploration, ship work and research queue a crew team (lead + helpers) and their effects land when the task completes on the turn tick — queueing spends no turn (jump range is adjacent by default, two hops once NAVIGATION AI is researched); mission accept is instant and free (no turn).',
|
||
// 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/turn': {
|
||
post: {
|
||
summary: 'Advance exactly one turn (manual tick)',
|
||
description:
|
||
"Ticks the queue one turn — no request body: every ACTIVE task ages by one, tasks that reach 0 resolve (jump, scan, ship-work or research effects land, mission checks run for what resolved) and PENDING successors auto-start in FIFO order. An empty queue is a FREE SKIP — turn +1 with nothing else changed, always 200 (the UI's NEXT TURN button is never disabled; a task that fails to resolve simply does not land its effect). Returns the same world snapshot as GET /api/world, plus the task queue after the tick.",
|
||
operationId: 'nextTurn',
|
||
responses: {
|
||
200: {
|
||
description: 'World state after the tick, plus the task queue (every task row: ACTIVE, PENDING, DONE)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/TurnResult' },
|
||
},
|
||
},
|
||
},
|
||
500: {
|
||
description: 'Internal error — no domain failures escape the tick (a failed task is silently dropped, not an error)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/jump': {
|
||
post: {
|
||
summary: 'Queue a jump to a solar system within range',
|
||
description:
|
||
'Queues a JUMP task with the given crew team (lead + helpers): validates that the target is within jump range of the current system (one hop away, or two hops once NAVIGATION AI is researched — #35) and that the team is free, then the move — target becomes current, discovered if new with its neighbours generated, jump event — lands when the task completes on the turn tick. Queueing spends no turn. Returns the fresh world state.',
|
||
operationId: 'jumpToSystem',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/JumpRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh world state — the jump itself lands when the task completes on the turn tick',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/WorldState' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-integer systemId/leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown system id or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'Target is outside jump range of the current system (more than one hop away, or more than two without NAVIGATION AI; or is the current system itself) — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
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: 'Queue a scan of a planet or moon',
|
||
description:
|
||
'Queues an EXPLORE task with the given crew team (lead + helpers): validates that the body exists and has not been scanned yet, and that the team is free, then the scan — a deterministic haul into the cargo hold, the explored flag, the ~18% crew roll and the explore event — lands when the task completes on the turn tick. Queueing spends no turn. Returns the fresh system state, with the outcome key empty and crewFound null until the scan resolves.',
|
||
operationId: 'explorePlanet',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ExploreRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh system state — the scan itself lands when the task completes on the turn tick (outcome empty, crewFound null)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ExploreResult' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-integer planetId/leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown planet id or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'Body was already explored (each can only be scanned once) — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
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 — returned as the { crew, tasks } object, wrapped with the active + pending task queue. 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 plus the active + pending task queue',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/CrewState' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/ship': {
|
||
get: {
|
||
summary: 'Ship state',
|
||
description:
|
||
"The ship's systems (built flag derived from level), the cargo hold and the turn counter. The starting ship is seeded on new game — core types built at base values, the rest unbuilt until a build action. Auto-initializes a new game when no save exists yet.",
|
||
operationId: 'getShip',
|
||
responses: {
|
||
200: {
|
||
description: 'Current ship state',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ShipState' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/ship/upgrade': {
|
||
post: {
|
||
summary: 'Upgrade a ship system',
|
||
description:
|
||
"Level up a built ship system by one (capped at max level). Assign the crew team and queue the SHIP_UPGRADE task — the cost (`level` units of any resource) is deducted at queue time and the level-up lands when the task completes on the turn tick; queueing spends no turn. Returns the fresh ship state.",
|
||
operationId: 'upgradeShipSystem',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/UpgradeRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh ship state — the upgrade lands when the task completes on the turn tick',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ShipState' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-integer systemId/leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown ship system id or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'System is not built or already at max level — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
422: {
|
||
description: 'Not enough resources in the cargo hold for the cost',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/ship/expand': {
|
||
post: {
|
||
summary: 'Expand a ship system',
|
||
description:
|
||
"Raise a built ship system's capacity by one (capped at max capacity). Assign the crew team and queue the SHIP_EXPAND task — the cost (`capacity` units of any resource) is deducted at queue time and the capacity bump lands when the task completes on the turn tick; queueing spends no turn. Returns the fresh ship state.",
|
||
operationId: 'expandShipSystem',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ExpandRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh ship state — the expansion lands when the task completes on the turn tick',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ShipState' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-integer systemId/leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown ship system id or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'System is not built or already at max capacity — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
422: {
|
||
description: 'Not enough resources in the cargo hold for the cost',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/ship/build': {
|
||
post: {
|
||
summary: 'Build a ship system',
|
||
description:
|
||
'Build an unbuilt ship system type at base values. Assign the crew team and queue the SHIP_BUILD task — the flat parts cost is deducted at queue time and the build lands when the task completes on the turn tick; queueing spends no turn. Returns the fresh ship state.',
|
||
operationId: 'buildShipSystem',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/BuildRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh ship state — the build lands when the task completes on the turn tick',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ShipState' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-string or empty type, a non-integer leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown ship system type or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'System is already built, or the build-gate tech is not researched yet — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
422: {
|
||
description: 'Not enough parts in the cargo hold for the cost',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/tech': {
|
||
get: {
|
||
summary: 'Tech tree state',
|
||
description:
|
||
"The full technology tree with per-save state — RESEARCHED if done, AVAILABLE if all prerequisites are researched, LOCKED otherwise (with the missing prerequisite names). Researching a tech gates or raises ship actions (e.g. building WEAPONS requires the WEAPONS SYSTEMS tech). Auto-initializes a new game when no save exists yet.",
|
||
operationId: 'getTech',
|
||
responses: {
|
||
200: {
|
||
description: 'The tech tree with per-save state',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'array', items: { $ref: '#/components/schemas/Tech' } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/research': {
|
||
post: {
|
||
summary: 'Research a tech',
|
||
description:
|
||
"Validates the prerequisites (409, naming what is missing) and assigns the crew team, queueing the RESEARCH task — the tech cost is deducted at queue time and the tech is marked researched (unlock effect included) when the task completes on the turn tick; queueing spends no turn. Returns the fresh tech tree state.",
|
||
operationId: 'researchTech',
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/ResearchRequest' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh tech tree state — the research lands when the task completes on the turn tick',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'array', items: { $ref: '#/components/schemas/Tech' } },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, a non-integer techId/leadCrewId, a non-integer-array helperCrewIds, duplicate crew ids, or a team over the size cap',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown tech id or unknown crew member',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'Tech is already researched or a prerequisite is not researched yet (message names the missing prereq) — or the crew team is blocked (a member committed to a pending task, a helper already busy, a jump in transit, or a jump already queued)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
422: {
|
||
description: 'Not enough of the required kind in the cargo hold for the cost',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/missions': {
|
||
get: {
|
||
summary: 'Mission board',
|
||
description:
|
||
"The save's mission contracts — generated deterministically from the seed, always topped up to at least three AVAILABLE. One mission is ACTIVE at a time; completion is checked after jumps and scans (DELIVER on arrival at its target system, EXPLORE after N scans, FIND when a haul contains its kind) and grants the reward into the cargo hold. Auto-initializes a new game when no save exists yet.",
|
||
operationId: 'getMissions',
|
||
responses: {
|
||
200: {
|
||
description: 'The mission board',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'array', items: { $ref: '#/components/schemas/Mission' } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/missions/{id}/accept': {
|
||
post: {
|
||
summary: 'Accept a mission',
|
||
description:
|
||
"Makes an AVAILABLE contract the single ACTIVE one (409 while another is active, or when it isn't AVAILABLE). Instant and free — no turn, no body — and writes a mission event at the current turn. Returns the fresh board.",
|
||
operationId: 'acceptMission',
|
||
parameters: [
|
||
{
|
||
name: 'id',
|
||
in: 'path',
|
||
required: true,
|
||
schema: { type: 'integer' },
|
||
description: 'missions.id of the contract to accept',
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: 'Fresh mission board after the acceptance',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'array', items: { $ref: '#/components/schemas/Mission' } },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Non-integer id in the path',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: 'Unknown mission id',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: "Mission isn't AVAILABLE, or another mission is already ACTIVE (one at a time)",
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/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' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/admin/tables': {
|
||
get: {
|
||
summary: 'Admin: list DB tables',
|
||
description:
|
||
"The game's SQLite tables exposed by the admin CRUD (#38) — name, columns (with primary-key positions) and row count. No auth by design.",
|
||
operationId: 'adminListTables',
|
||
responses: {
|
||
200: {
|
||
description: 'Table list with column metadata and row counts',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'array', items: { $ref: '#/components/schemas/AdminTable' } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'/api/admin/tables/{name}/rows': {
|
||
get: {
|
||
summary: 'Admin: read rows of a table',
|
||
description: 'Rows of one admin table as objects keyed by column name, capped at 200 per page.',
|
||
operationId: 'adminReadRows',
|
||
parameters: [
|
||
{
|
||
name: 'name',
|
||
in: 'path',
|
||
required: true,
|
||
schema: { type: 'string' },
|
||
description: "Table name as listed by /api/admin/tables (e.g. 'saves')",
|
||
},
|
||
{
|
||
name: 'limit',
|
||
in: 'query',
|
||
required: false,
|
||
schema: { type: 'integer', minimum: 1, maximum: 200 },
|
||
description: 'Rows per page (default 50)',
|
||
},
|
||
{
|
||
name: 'offset',
|
||
in: 'query',
|
||
required: false,
|
||
schema: { type: 'integer', minimum: 0 },
|
||
description: 'Row offset for paging (default 0)',
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: 'Rows plus the total row count of the table',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/AdminRows' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'limit/offset out of range',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: "Unknown table name",
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
post: {
|
||
summary: 'Admin: insert a row',
|
||
description: "Insert one row. Body is an object of column → value (numbers for INTEGER/REAL columns, strings or null for TEXT); autoincrement ids may be omitted.",
|
||
operationId: 'adminInsertRow',
|
||
parameters: [
|
||
{
|
||
name: 'name',
|
||
in: 'path',
|
||
required: true,
|
||
schema: { type: 'string' },
|
||
description: "Table name as listed by /api/admin/tables",
|
||
},
|
||
],
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/AdminRowValues' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'The inserted row',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'object' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, unknown column, missing required column or wrong value type',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: "Unknown table name",
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'Constraint violation (UNIQUE or foreign key)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
patch: {
|
||
summary: 'Admin: update a row',
|
||
description: "Update one row by primary key. Body is { key, values } — key holds the table's PK columns (composite keys supported), values the new column → value pairs.",
|
||
operationId: 'adminUpdateRow',
|
||
parameters: [
|
||
{
|
||
name: 'name',
|
||
in: 'path',
|
||
required: true,
|
||
schema: { type: 'string' },
|
||
description: "Table name as listed by /api/admin/tables",
|
||
},
|
||
],
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/AdminRowUpdate' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'The updated row',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'object' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body, bad key or unknown column',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: "Unknown table name, or no row with that key",
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
409: {
|
||
description: 'Constraint violation (UNIQUE or foreign key)',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
delete: {
|
||
summary: 'Admin: delete a row',
|
||
description: "Delete one row by primary key. Body is { key } with the table's PK columns.",
|
||
operationId: 'adminDeleteRow',
|
||
parameters: [
|
||
{
|
||
name: 'name',
|
||
in: 'path',
|
||
required: true,
|
||
schema: { type: 'string' },
|
||
description: "Table name as listed by /api/admin/tables",
|
||
},
|
||
],
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/AdminRowKey' },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
200: {
|
||
description: 'The deleted row',
|
||
content: {
|
||
'application/json': {
|
||
schema: { type: 'object' },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: 'Malformed body or bad key',
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
404: {
|
||
description: "Unknown table name, or no row with that key",
|
||
content: {
|
||
'application/json': {
|
||
schema: { $ref: '#/components/schemas/Error' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
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 — advances on the turn tick; queued actions (jump, exploration, ship work, research) cost no turn, and mission accept is free' },
|
||
tasks: {
|
||
type: 'array',
|
||
description: 'The active + pending task queue — the ACTIVE block first, then the PENDING block, each ordered by (queuedAtTurn, id); DONE rows never appear',
|
||
items: { $ref: '#/components/schemas/TaskDto' },
|
||
},
|
||
},
|
||
required: ['systems', 'edges', 'turn', 'tasks'],
|
||
},
|
||
TurnResult: {
|
||
allOf: [
|
||
{ $ref: '#/components/schemas/WorldState' },
|
||
{
|
||
type: 'object',
|
||
properties: {
|
||
tasks: {
|
||
type: 'array',
|
||
description:
|
||
'Every task row after the tick — ACTIVE (decremented this turn, or auto-started this turn), PENDING (waiting for its team to free) and DONE (resolved this tick or earlier)',
|
||
items: { $ref: '#/components/schemas/Task' },
|
||
},
|
||
},
|
||
required: ['tasks'],
|
||
},
|
||
],
|
||
},
|
||
Task: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'integer' },
|
||
saveId: { type: 'integer' },
|
||
type: { type: 'string', enum: ['JUMP', 'EXPLORE', 'SHIP_UPGRADE', 'SHIP_EXPAND', 'SHIP_BUILD', 'RESEARCH'] },
|
||
payload: {
|
||
type: 'string',
|
||
description: 'JSON per type: JUMP {systemId, hops} · SHIP_UPGRADE/EXPAND {systemId} · SHIP_BUILD {type} · RESEARCH {techId} · EXPLORE {planetId}',
|
||
},
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead' },
|
||
helpers: { type: 'array', description: 'crew.id of the supporting crew (empty when none)', items: { type: 'integer' } },
|
||
duration: { type: 'integer' },
|
||
remaining: { type: 'integer' },
|
||
status: { type: 'string', enum: ['ACTIVE', 'PENDING', 'DONE'] },
|
||
queuedAtTurn: { type: 'integer' },
|
||
},
|
||
required: ['id', 'saveId', 'type', 'payload', 'leadCrewId', 'helpers', 'duration', 'remaining', 'status', 'queuedAtTurn'],
|
||
},
|
||
TaskDto: {
|
||
type: 'object',
|
||
description: 'One row of the snapshot task queue — the ACTIVE + PENDING rows of the tasks table as the GET snapshots expose them (DONE rows never appear)',
|
||
properties: {
|
||
id: { type: 'integer' },
|
||
type: { type: 'string', enum: ['JUMP', 'EXPLORE', 'SHIP_UPGRADE', 'SHIP_EXPAND', 'SHIP_BUILD', 'RESEARCH'] },
|
||
targetLabel: {
|
||
type: 'string',
|
||
description:
|
||
'The task target by display name — the jump destination, the scanned body, the ship system with the verb suffixed (e.g. Origin — upgrade), the built system type, or the researched tech; `unknown` when the target row no longer exists',
|
||
},
|
||
leadName: { type: 'string', description: 'The team lead by name; `unknown` when the crew row no longer exists' },
|
||
helperNames: { type: 'array', description: 'The supporting crew by name (empty when none)', items: { type: 'string' } },
|
||
remaining: { type: 'integer', description: 'Turns left — the stored row value (a pure read, not recomputed)' },
|
||
status: {
|
||
type: 'string',
|
||
enum: ['ACTIVE', 'PENDING'],
|
||
description: 'The row state — the ACTIVE block precedes the PENDING block in the array',
|
||
},
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: 'crew.id of the supporting crew (empty when none)',
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['id', 'type', 'targetLabel', 'leadName', 'helperNames', 'remaining', 'status', 'leadCrewId', 'helperCrewIds'],
|
||
},
|
||
JumpRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
systemId: { type: 'integer', description: 'solar_systems.id of the jump target' },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['systemId', 'leadCrewId'],
|
||
},
|
||
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' },
|
||
traitGood: { type: 'string', description: 'The member good trait' },
|
||
traitBad: { type: 'string', description: 'The member bad trait' },
|
||
origin: { type: 'string', description: "'STARTING CREW' or '<planet>, <system>' for members found while exploring" },
|
||
skills: {
|
||
type: 'array',
|
||
description: 'The member four crew_skills levels in the fixed NAVIGATION → SCANNING → ENGINEERING → RESEARCH order (display-oriented — levels only, no xp)',
|
||
items: {
|
||
type: 'object',
|
||
properties: {
|
||
skill: { type: 'string', enum: ['NAVIGATION', 'SCANNING', 'ENGINEERING', 'RESEARCH'] },
|
||
level: { type: 'integer' },
|
||
},
|
||
required: ['skill', 'level'],
|
||
},
|
||
},
|
||
title: { type: ['string', 'null'], nullable: true, description: "'SENIOR <ROLE>' from the member's highest skill level ≥ 3, 'MASTER <ROLE>' from ≥ 5 — null below" },
|
||
},
|
||
required: ['id', 'name', 'role', 'traitGood', 'traitBad', 'origin', 'skills', 'title'],
|
||
},
|
||
CrewState: {
|
||
type: 'object',
|
||
description: 'GET /api/crew response — the roster wrapped with the snapshot task queue (a bare array cannot carry a sibling tasks field)',
|
||
properties: {
|
||
crew: {
|
||
type: 'array',
|
||
description: 'The ship crew in join order — the starting crew (seeded on new game) plus members found while exploring',
|
||
items: { $ref: '#/components/schemas/CrewMember' },
|
||
},
|
||
tasks: {
|
||
type: 'array',
|
||
description: 'The active + pending task queue — the ACTIVE block first, then the PENDING block, each ordered by (queuedAtTurn, id); DONE rows never appear',
|
||
items: { $ref: '#/components/schemas/TaskDto' },
|
||
},
|
||
},
|
||
required: ['crew', 'tasks'],
|
||
},
|
||
ShipSystem: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'integer' },
|
||
type: { type: 'string', description: "System type, e.g. REACTOR or WEAPONS" },
|
||
level: { type: 'integer', description: '0 when not built yet' },
|
||
capacity: { type: 'integer', description: '0 when not built yet' },
|
||
built: { type: 'boolean', description: 'Derived — true when level > 0' },
|
||
},
|
||
required: ['id', 'type', 'level', 'capacity', 'built'],
|
||
},
|
||
ShipState: {
|
||
type: 'object',
|
||
properties: {
|
||
systems: {
|
||
type: 'array',
|
||
description: "The ship's systems in display order",
|
||
items: { $ref: '#/components/schemas/ShipSystem' },
|
||
},
|
||
inventory: { type: 'array', items: { $ref: '#/components/schemas/InventoryItem' } },
|
||
turn: { type: 'integer', description: 'Turn counter — advances on the turn tick; queued actions (jump, exploration, ship work, research) cost no turn, and mission accept is free' },
|
||
},
|
||
required: ['systems', 'inventory', 'turn'],
|
||
},
|
||
Tech: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'integer' },
|
||
slug: { type: 'string', description: "Stable key, e.g. WEAPONS_SYSTEMS" },
|
||
name: { type: 'string', description: "Display name, e.g. 'WEAPONS SYSTEMS'" },
|
||
costKind: { type: 'string', enum: ['resource', 'part'] },
|
||
costQty: { type: 'integer' },
|
||
prereqs: {
|
||
type: 'array',
|
||
description: 'Prerequisite tech names (empty for roots)',
|
||
items: { type: 'string' },
|
||
},
|
||
effect: { type: 'string', description: "Human-readable unlock, e.g. 'Unlocks building the WEAPONS system'" },
|
||
state: { type: 'string', enum: ['RESEARCHED', 'AVAILABLE', 'LOCKED'] },
|
||
missingPrereqs: {
|
||
type: 'array',
|
||
description: 'Unmet prerequisite names — non-empty only when LOCKED',
|
||
items: { type: 'string' },
|
||
},
|
||
},
|
||
required: ['id', 'slug', 'name', 'costKind', 'costQty', 'prereqs', 'effect', 'state', 'missingPrereqs'],
|
||
},
|
||
ResearchRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
techId: { type: 'integer', description: 'tech.id of the tech to research' },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['techId', 'leadCrewId'],
|
||
},
|
||
Mission: {
|
||
type: 'object',
|
||
properties: {
|
||
id: { type: 'integer' },
|
||
type: { type: 'string', enum: ['DELIVER', 'EXPLORE', 'FIND'] },
|
||
description: { type: 'string', description: "Human-readable objective, e.g. 'Jump to Kepler Drift'" },
|
||
targetSystemId: { type: ['integer', 'null'], nullable: true, description: 'DELIVER only — destination system (NULL otherwise)' },
|
||
conditionKind: { type: ['string', 'null'], enum: ['resource', 'part', null], description: 'FIND only — item kind to haul in (NULL otherwise)' },
|
||
progressTarget: { type: 'integer', description: 'EXPLORE: scans required; 1 for the others' },
|
||
progress: { type: 'integer', description: 'Scans counted since acceptance; 0/1 for DELIVER/FIND' },
|
||
rewardKind: { type: 'string', enum: ['resource', 'part'] },
|
||
rewardQty: { type: 'integer', description: 'Reward quantity (1–3), granted into the cargo hold on completion' },
|
||
state: { type: 'string', enum: ['AVAILABLE', 'ACTIVE', 'DONE'] },
|
||
},
|
||
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 event was written (new_game is 0)' },
|
||
type: { type: 'string', enum: ['new_game', 'jump', 'explore', 'research', 'upgrade', 'expand', 'build', 'mission', 'level_up'] },
|
||
message: { type: 'string' },
|
||
createdAt: { type: 'string', description: 'ISO 8601 timestamp' },
|
||
},
|
||
required: ['id', 'turn', 'type', 'message', 'createdAt'],
|
||
},
|
||
UpgradeRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
systemId: { type: 'integer', description: 'ship_systems.id of the built system to level up' },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['systemId', 'leadCrewId'],
|
||
},
|
||
ExpandRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
systemId: { type: 'integer', description: 'ship_systems.id of the built system to expand' },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['systemId', 'leadCrewId'],
|
||
},
|
||
BuildRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
type: { type: 'string', description: "Ship system type to build, e.g. WEAPONS (see ShipSystem.type)" },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['type', 'leadCrewId'],
|
||
},
|
||
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 — advances on the turn tick; queued actions (jump, exploration, ship work, research) cost no turn, and mission accept is free' },
|
||
tasks: {
|
||
type: 'array',
|
||
description: 'The active + pending task queue — the ACTIVE block first, then the PENDING block, each ordered by (queuedAtTurn, id); DONE rows never appear',
|
||
items: { $ref: '#/components/schemas/TaskDto' },
|
||
},
|
||
},
|
||
required: ['systemId', 'systemName', 'planets', 'inventory', 'turn', 'tasks'],
|
||
},
|
||
ExploreRequest: {
|
||
type: 'object',
|
||
properties: {
|
||
planetId: { type: 'integer', description: 'planets.id of the body to scan (planet or moon)' },
|
||
leadCrewId: { type: 'integer', description: 'crew.id of the team lead — the member the queued task is committed to first' },
|
||
helperCrewIds: {
|
||
type: 'array',
|
||
description: `Optional supporting crew (crew.id) — the whole team, lead + helpers, holds at most ${MAX_TEAM_SIZE} distinct members, all of them free`,
|
||
items: { type: 'integer' },
|
||
},
|
||
},
|
||
required: ['planetId', 'leadCrewId'],
|
||
},
|
||
ExploreResult: {
|
||
allOf: [
|
||
{ $ref: '#/components/schemas/SystemState' },
|
||
{
|
||
type: 'object',
|
||
properties: {
|
||
outcome: {
|
||
type: 'array',
|
||
description: 'What this scan found — always empty in the queueing response; populated only once the scan resolves on the turn tick (a resolved scan may still 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 — always null in the queueing response',
|
||
anyOf: [{ $ref: '#/components/schemas/CrewMember' }, { type: 'null' }],
|
||
},
|
||
},
|
||
required: ['outcome', 'crewFound'],
|
||
},
|
||
],
|
||
},
|
||
AdminColumn: {
|
||
type: 'object',
|
||
properties: {
|
||
name: { type: 'string', description: "SQL column name, e.g. 'save_id'" },
|
||
type: { type: 'string', enum: ['INTEGER', 'REAL', 'TEXT'], description: 'Declared SQLite type' },
|
||
pk: { type: 'integer', description: 'Position within the primary key (0 = not a PK); composite keys come out ordered' },
|
||
},
|
||
required: ['name', 'type', 'pk'],
|
||
},
|
||
AdminTable: {
|
||
type: 'object',
|
||
properties: {
|
||
name: { type: 'string', description: "SQL table name, e.g. 'saves'" },
|
||
columns: { type: 'array', items: { $ref: '#/components/schemas/AdminColumn' } },
|
||
count: { type: 'integer', description: 'Current row count' },
|
||
},
|
||
required: ['name', 'columns', 'count'],
|
||
},
|
||
AdminRows: {
|
||
type: 'object',
|
||
properties: {
|
||
rows: {
|
||
type: 'array',
|
||
description: 'Rows as objects keyed by column name (null for NULL cells)',
|
||
items: { type: 'object' },
|
||
},
|
||
total: { type: 'integer', description: 'Total row count of the table (before paging)' },
|
||
},
|
||
required: ['rows', 'total'],
|
||
},
|
||
AdminRowValues: {
|
||
type: 'object',
|
||
additionalProperties: true,
|
||
description: "Column → value pairs for an insert — numbers for INTEGER/REAL columns, strings or null for TEXT; autoincrement ids may be omitted",
|
||
},
|
||
AdminRowKey: {
|
||
type: 'object',
|
||
properties: {
|
||
key: {
|
||
type: 'object',
|
||
additionalProperties: { type: 'integer' },
|
||
description: "The table's primary-key columns (composite keys supported)",
|
||
},
|
||
},
|
||
required: ['key'],
|
||
},
|
||
AdminRowUpdate: {
|
||
allOf: [
|
||
{ $ref: '#/components/schemas/AdminRowKey' },
|
||
{
|
||
type: 'object',
|
||
properties: {
|
||
values: { $ref: '#/components/schemas/AdminRowValues' },
|
||
},
|
||
required: ['values'],
|
||
},
|
||
],
|
||
},
|
||
Error: {
|
||
type: 'object',
|
||
properties: {
|
||
error: { type: 'string' },
|
||
},
|
||
required: ['error'],
|
||
},
|
||
},
|
||
},
|
||
};
|