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: 'Flight Ops API', description: 'JSON API for Flight Ops — EU airport data for the map, the live game-state snapshot, and app metadata.', // 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/airports': { get: { summary: 'List EU airports', description: 'All fixed-wing EU airports (large, medium and seaplane bases) as a GeoJSON FeatureCollection.', operationId: 'listAirports', responses: { 200: { description: 'GeoJSON FeatureCollection of EU airports', content: { 'application/json': { schema: { $ref: '#/components/schemas/AirportFeatureCollection' }, }, }, }, }, }, }, '/api/state': { get: { summary: 'Game state snapshot', description: 'Full current game state for client polling — account, sim clock, planes and contracts. Tick-on-read: each call first advances the sim clock by (real time since last read) × timeScale (clamped per tick), so expiry/finalization/spawn are always applied before the snapshot is returned.', operationId: 'getState', responses: { 200: { description: 'Current game state', content: { 'application/json': { schema: { $ref: '#/components/schemas/StateSnapshot' }, }, }, }, }, }, }, '/api/sim': { post: { summary: 'Set sim time controls', description: 'Advance the sim clock (tick-on-read) and set the time scale / pause state. The clock is advanced first, so the returned simState already reflects current game time; contract expiry, arrival finalization and market spawn run inside that tick.', operationId: 'setSimControls', requestBody: { required: false, content: { 'application/json': { schema: { $ref: '#/components/schemas/SimControlsRequest' }, }, }, }, responses: { 200: { description: 'Updated sim state', content: { 'application/json': { schema: { $ref: '#/components/schemas/SimState' }, }, }, }, 400: { description: 'Invalid body (not an object, unknown field, or wrong types)', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, }, }, }, '/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' }, }, }, }, }, }, }, }, components: { schemas: { AppVersion: { type: 'object', properties: { version: { type: 'string', description: 'Semver of the running build' }, }, required: ['version'], }, AirportFeatureCollection: { type: 'object', properties: { type: { const: 'FeatureCollection' }, features: { type: 'array', items: { $ref: '#/components/schemas/AirportFeature' }, }, }, required: ['type', 'features'], }, AirportFeature: { type: 'object', properties: { type: { const: 'Feature' }, geometry: { $ref: '#/components/schemas/PointGeometry' }, properties: { $ref: '#/components/schemas/AirportProperties' }, }, required: ['type', 'geometry', 'properties'], }, PointGeometry: { type: 'object', properties: { type: { const: 'Point' }, coordinates: { type: 'array', description: '[longitude, latitude]', items: { type: 'number' }, minItems: 2, maxItems: 2, }, }, required: ['type', 'coordinates'], }, AirportProperties: { type: 'object', properties: { name: { type: 'string' }, icao: { type: 'string' }, lat: { type: 'number' }, lon: { type: 'number' }, }, required: ['name', 'icao', 'lat', 'lon'], }, StateSnapshot: { type: 'object', properties: { account: { $ref: '#/components/schemas/AccountState' }, simState: { $ref: '#/components/schemas/SimState' }, planes: { type: 'array', items: { $ref: '#/components/schemas/PlaneState' }, }, contracts: { type: 'array', items: { $ref: '#/components/schemas/ContractState' }, }, }, required: ['account', 'simState', 'planes', 'contracts'], }, AccountState: { type: 'object', properties: { cash: { type: 'number' }, reputation: { type: 'number' }, }, required: ['cash', 'reputation'], }, SimState: { type: 'object', properties: { clockMs: { type: 'integer', description: 'Sim-clock epoch ms (game time)' }, timeScale: { type: 'number', minimum: 1, maximum: 60 }, paused: { type: 'boolean' }, }, required: ['clockMs', 'timeScale', 'paused'], }, SimControlsRequest: { type: 'object', description: 'Partial update — omit a field to leave it unchanged.', properties: { timeScale: { type: 'number', minimum: 1, maximum: 60, description: 'Game-time multiplier; values outside [1, 60] are clamped.', }, paused: { type: 'boolean' }, }, }, Error: { type: 'object', properties: { error: { type: 'string' }, }, required: ['error'], }, PlaneState: { type: 'object', properties: { id: { type: 'string' }, model: { type: 'string' }, status: { enum: ['available', 'flying'] }, fuel: { type: 'number' }, maxFuel: { type: 'number' }, locationIcao: { type: 'string' }, locationName: { type: 'string' }, contractId: { type: ['string', 'null'] }, lat: { type: 'number' }, lon: { type: 'number' }, }, required: [ 'id', 'model', 'status', 'fuel', 'maxFuel', 'locationIcao', 'locationName', 'contractId', 'lat', 'lon', ], }, ContractState: { type: 'object', properties: { id: { type: 'string' }, originIcao: { type: 'string' }, originName: { type: 'string' }, destIcao: { type: 'string' }, destName: { type: 'string' }, reward: { type: 'number' }, cargoType: { type: 'string' }, cargoCount: { type: 'integer' }, deadlineSimTime: { type: 'integer', description: 'Sim-clock epoch ms (game time)' }, // 'expired' = an open contract whose deadline passed; accepted/in-flight contracts never expire. status: { enum: ['open', 'accepted', 'completed', 'expired'] }, planeId: { type: ['string', 'null'] }, createdSimTime: { type: 'integer' }, }, required: [ 'id', 'originIcao', 'originName', 'destIcao', 'destName', 'reward', 'cargoType', 'cargoCount', 'deadlineSimTime', 'status', 'planeId', 'createdSimTime', ], }, }, }, };