chore: init backend and ignore local artifacts
This commit is contained in:
729
swagger.js
Normal file
729
swagger.js
Normal file
@@ -0,0 +1,729 @@
|
||||
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" },
|
||||
},
|
||||
},
|
||||
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"],
|
||||
},
|
||||
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/{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" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
openApiSpec,
|
||||
};
|
||||
Reference in New Issue
Block a user