Files
temp-history-api/swagger.js
2026-04-17 20:55:33 +07:00

913 lines
35 KiB
JavaScript

const openApiSpec = {
openapi: "3.0.3",
info: {
title: "Ultimate History Map API",
version: "1.0.0",
description: "API docs for tiles, geometries, and entities.",
},
servers: [
{
url: "http://localhost:3000",
description: "Local",
},
],
tags: [
{ name: "System", description: "Health and meta endpoints" },
{ name: "Tiles", description: "Vector and raster tile endpoints" },
{ name: "Geometries", description: "Geometry CRUD and batch save" },
{ name: "Entities", description: "Entity CRUD" },
],
components: {
schemas: {
ErrorResponse: {
type: "object",
properties: {
error: { type: "string" },
},
required: ["error"],
},
SuccessResponse: {
type: "object",
properties: {
success: { type: "boolean" },
},
required: ["success"],
},
Entity: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
slug: { type: "string", nullable: true },
description: { type: "string", nullable: true },
type_id: { type: "string" },
status: { type: "number" },
created_at: { type: "string", format: "date-time", nullable: true },
updated_at: { type: "string", format: "date-time", nullable: true },
geometry_count: { type: "number" },
},
required: ["id", "name", "type_id", "geometry_count"],
},
EntityCreateInput: {
type: "object",
properties: {
name: { type: "string" },
slug: { type: "string", nullable: true },
description: { type: "string", nullable: true },
type_id: { type: "string", nullable: true },
status: { type: "number", nullable: true },
},
required: ["name"],
},
EntityUpdateInput: {
type: "object",
properties: {
name: { type: "string" },
slug: { type: "string", nullable: true },
description: { type: "string", nullable: true },
type_id: { type: "string" },
status: { type: "number" },
},
},
EntityBatchCreateChange: {
type: "object",
properties: {
action: { type: "string", enum: ["create"] },
entity: { $ref: "#/components/schemas/EntityCreateInput" },
},
required: ["action"],
},
EntityBatchUpdateChange: {
type: "object",
properties: {
action: { type: "string", enum: ["update"] },
id: { type: "string" },
entity: { $ref: "#/components/schemas/EntityUpdateInput" },
name: { type: "string" },
slug: { type: "string", nullable: true },
description: { type: "string", nullable: true },
type_id: { type: "string" },
status: { type: "number" },
},
required: ["action", "id"],
},
EntityBatchDeleteChange: {
type: "object",
properties: {
action: { type: "string", enum: ["delete"] },
id: { type: "string" },
},
required: ["action", "id"],
},
EntityBatchPayload: {
type: "object",
properties: {
changes: {
type: "array",
items: {
oneOf: [
{ $ref: "#/components/schemas/EntityBatchCreateChange" },
{ $ref: "#/components/schemas/EntityBatchUpdateChange" },
{ $ref: "#/components/schemas/EntityBatchDeleteChange" },
],
},
},
},
required: ["changes"],
},
EntityBatchResponse: {
type: "object",
properties: {
success: { type: "boolean" },
applied: { type: "number" },
created_entity_ids: {
type: "array",
items: { type: "string" },
},
},
required: ["success", "applied", "created_entity_ids"],
},
GeoJSONGeometry: {
type: "object",
properties: {
type: {
type: "string",
enum: [
"Point",
"MultiPoint",
"LineString",
"MultiLineString",
"Polygon",
"MultiPolygon",
],
},
coordinates: {
type: "array",
items: {},
},
},
required: ["type", "coordinates"],
},
GeometryFeature: {
type: "object",
properties: {
type: { type: "string", enum: ["Feature"] },
properties: {
type: "object",
properties: {
id: { type: "string" },
type: { type: "string", nullable: true },
time_start: { type: "number", nullable: true },
time_end: { type: "number", nullable: true },
binding: {
type: "array",
items: { type: "string" },
},
entity_id: { type: "string", nullable: true },
entity_ids: {
type: "array",
items: { type: "string" },
},
entity_name: { type: "string", nullable: true },
entity_names: {
type: "array",
items: { type: "string" },
},
entity_type_id: { type: "string", nullable: true },
},
required: ["id"],
},
geometry: { $ref: "#/components/schemas/GeoJSONGeometry" },
},
required: ["type", "properties", "geometry"],
},
GeometryFeatureCollection: {
type: "object",
properties: {
type: { type: "string", enum: ["FeatureCollection"] },
features: {
type: "array",
items: { $ref: "#/components/schemas/GeometryFeature" },
},
},
required: ["type", "features"],
},
GeometryUpsertInput: {
type: "object",
properties: {
geometry: { $ref: "#/components/schemas/GeoJSONGeometry" },
type: { type: "string", nullable: true },
time_start: { type: "number", nullable: true },
time_end: { type: "number", nullable: true },
binding: {
type: "array",
items: { type: "string" },
},
entity_id: { type: "string", nullable: true },
entity_ids: {
type: "array",
items: { type: "string" },
},
},
required: ["geometry"],
},
GeometryCreateResponse: {
type: "object",
properties: {
id: { type: "string" },
},
required: ["id"],
},
BatchCreateChange: {
type: "object",
properties: {
action: { type: "string", enum: ["create"] },
feature: { $ref: "#/components/schemas/GeometryFeature" },
},
required: ["action", "feature"],
},
BatchUpdateChange: {
type: "object",
properties: {
action: { type: "string", enum: ["update"] },
id: { type: "string" },
geometry: { $ref: "#/components/schemas/GeoJSONGeometry" },
type: { type: "string", nullable: true },
time_start: { type: "number", nullable: true },
time_end: { type: "number", nullable: true },
binding: {
type: "array",
items: { type: "string" },
},
entity_id: { type: "string", nullable: true },
entity_ids: {
type: "array",
items: { type: "string" },
},
},
required: ["action", "id", "geometry"],
},
BatchDeleteChange: {
type: "object",
properties: {
action: { type: "string", enum: ["delete"] },
id: { type: "string" },
},
required: ["action", "id"],
},
GeometryBatchPayload: {
type: "object",
properties: {
changes: {
type: "array",
items: {
oneOf: [
{ $ref: "#/components/schemas/BatchCreateChange" },
{ $ref: "#/components/schemas/BatchUpdateChange" },
{ $ref: "#/components/schemas/BatchDeleteChange" },
],
},
},
},
required: ["changes"],
},
GeometryBatchResponse: {
type: "object",
properties: {
success: { type: "boolean" },
applied: { type: "number" },
},
required: ["success", "applied"],
},
CombinedBatchPayload: {
type: "object",
properties: {
entity_changes: {
type: "array",
items: {
oneOf: [
{ $ref: "#/components/schemas/EntityBatchCreateChange" },
{ $ref: "#/components/schemas/EntityBatchUpdateChange" },
{ $ref: "#/components/schemas/EntityBatchDeleteChange" },
],
},
},
geometry_changes: {
type: "array",
items: {
oneOf: [
{ $ref: "#/components/schemas/BatchCreateChange" },
{ $ref: "#/components/schemas/BatchUpdateChange" },
{ $ref: "#/components/schemas/BatchDeleteChange" },
],
},
},
},
},
CombinedBatchResponse: {
type: "object",
properties: {
success: { type: "boolean" },
applied: { type: "number" },
entity_applied: { type: "number" },
geometry_applied: { type: "number" },
created_entity_ids: {
type: "array",
items: { type: "string" },
},
},
required: [
"success",
"applied",
"entity_applied",
"geometry_applied",
"created_entity_ids",
],
},
MetadataResponse: {
type: "object",
additionalProperties: {
oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }],
},
},
},
},
paths: {
"/": {
get: {
tags: ["System"],
summary: "Health check",
responses: {
200: {
description: "Server status text",
content: {
"text/plain": {
schema: { type: "string" },
},
},
},
},
},
},
"/tiles/metadata/info": {
get: {
tags: ["Tiles"],
summary: "Get vector tiles metadata",
responses: {
200: {
description: "MBTiles metadata",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/MetadataResponse" },
},
},
},
},
},
},
"/tiles/{z}/{x}/{y}": {
get: {
tags: ["Tiles"],
summary: "Get vector tile by XYZ",
parameters: [
{ name: "z", in: "path", required: true, schema: { type: "integer" } },
{ name: "x", in: "path", required: true, schema: { type: "integer" } },
{ name: "y", in: "path", required: true, schema: { type: "integer" } },
],
responses: {
200: {
description: "Tile binary",
content: {
"application/x-protobuf": {
schema: { type: "string", format: "binary" },
},
"image/png": {
schema: { type: "string", format: "binary" },
},
"image/jpeg": {
schema: { type: "string", format: "binary" },
},
"application/octet-stream": {
schema: { type: "string", format: "binary" },
},
},
},
400: {
description: "Invalid tile coordinates",
},
404: {
description: "Tile not found",
},
},
},
},
"/raster-tiles/metadata/info": {
get: {
tags: ["Tiles"],
summary: "Get raster tiles metadata",
responses: {
200: {
description: "MBTiles metadata",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/MetadataResponse" },
},
},
},
},
},
},
"/raster-tiles/{z}/{x}/{y}": {
get: {
tags: ["Tiles"],
summary: "Get raster tile by XYZ",
parameters: [
{ name: "z", in: "path", required: true, schema: { type: "integer" } },
{ name: "x", in: "path", required: true, schema: { type: "integer" } },
{ name: "y", in: "path", required: true, schema: { type: "integer" } },
],
responses: {
200: {
description: "Tile binary",
content: {
"image/png": { schema: { type: "string", format: "binary" } },
"image/jpeg": { schema: { type: "string", format: "binary" } },
"image/webp": { schema: { type: "string", format: "binary" } },
"application/octet-stream": { schema: { type: "string", format: "binary" } },
},
},
400: {
description: "Invalid tile coordinates",
},
404: {
description: "Tile not found",
},
},
},
},
"/entities": {
get: {
tags: ["Entities"],
summary: "List entities",
parameters: [
{
name: "q",
in: "query",
required: false,
schema: { type: "string" },
description: "Search by name or slug",
},
],
responses: {
200: {
description: "Entity list",
content: {
"application/json": {
schema: {
type: "array",
items: { $ref: "#/components/schemas/Entity" },
},
},
},
},
},
},
post: {
tags: ["Entities"],
summary: "Create entity",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EntityCreateInput" },
},
},
},
responses: {
201: {
description: "Entity created",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Entity" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
409: {
description: "Unique conflict",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/entities/search": {
get: {
tags: ["Entities"],
summary: "Search entities by name",
parameters: [
{
name: "name",
in: "query",
required: true,
schema: { type: "string" },
description: "Entity name keyword",
},
{
name: "limit",
in: "query",
required: false,
schema: { type: "integer", minimum: 1, maximum: 100 },
},
],
responses: {
200: {
description: "Matched entity list",
content: {
"application/json": {
schema: {
type: "array",
items: { $ref: "#/components/schemas/Entity" },
},
},
},
},
},
},
},
"/entities/batch": {
post: {
tags: ["Entities"],
summary: "Apply batch create/update/delete entities",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EntityBatchPayload" },
},
},
},
responses: {
200: {
description: "Batch applied",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EntityBatchResponse" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
409: {
description: "Unique conflict or cannot delete due to orphaned geometries",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/entities/{id}": {
get: {
tags: ["Entities"],
summary: "Get entity by id",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
responses: {
200: {
description: "Entity",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Entity" },
},
},
},
404: {
description: "Not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
put: {
tags: ["Entities"],
summary: "Update entity",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EntityUpdateInput" },
},
},
},
responses: {
200: {
description: "Entity updated",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Entity" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
404: {
description: "Not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
409: {
description: "Unique conflict",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
delete: {
tags: ["Entities"],
summary: "Soft-delete entity",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
responses: {
200: {
description: "Soft-delete success",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/SuccessResponse" },
},
},
},
409: {
description: "Entity is still the last active link of one or more geometries",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
404: {
description: "Not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/geometries": {
get: {
tags: ["Geometries"],
summary: "Query geometries by bbox, time, and entity",
parameters: [
{ name: "minLng", in: "query", required: true, schema: { type: "number" } },
{ name: "minLat", in: "query", required: true, schema: { type: "number" } },
{ name: "maxLng", in: "query", required: true, schema: { type: "number" } },
{ name: "maxLat", in: "query", required: true, schema: { type: "number" } },
{ name: "time", in: "query", required: false, schema: { type: "integer" } },
{ name: "entity_id", in: "query", required: false, schema: { type: "string" } },
],
responses: {
200: {
description: "Feature collection",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryFeatureCollection" },
},
},
},
400: {
description: "Invalid query",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
post: {
tags: ["Geometries"],
summary: "Create geometry",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryUpsertInput" },
},
},
},
responses: {
200: {
description: "Created geometry id",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryCreateResponse" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
404: {
description: "Entity not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/geometries/{id}": {
put: {
tags: ["Geometries"],
summary: "Update geometry",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryUpsertInput" },
},
},
},
responses: {
200: {
description: "Updated",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/SuccessResponse" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
404: {
description: "Not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
delete: {
tags: ["Geometries"],
summary: "Soft-delete geometry",
parameters: [
{ name: "id", in: "path", required: true, schema: { type: "string" } },
],
responses: {
200: {
description: "Soft-delete success",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/SuccessResponse" },
},
},
},
404: {
description: "Not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/geometries/batch": {
post: {
tags: ["Geometries"],
summary: "Apply batch create/update/delete geometries",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryBatchPayload" },
},
},
},
responses: {
200: {
description: "Batch applied",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/GeometryBatchResponse" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
"/geometries/batch/combined": {
post: {
tags: ["Geometries", "Entities"],
summary: "Apply entity batch and geometry batch in one transaction",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CombinedBatchPayload" },
},
},
},
responses: {
200: {
description: "Combined batch applied",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CombinedBatchResponse" },
},
},
},
400: {
description: "Invalid payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
409: {
description: "Conflict in entity changes (unique or orphan guard)",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
},
},
},
},
},
},
},
};
module.exports = {
openApiSpec,
};