api / src /openapi.js
Tataaicoltd's picture
deploy: add /chefsys routes (foods, drinks, add-on, order)
fb78baf verified
Raw History Blame Contribute Delete
18.7 kB
// OpenAPI 3.0 specification for the Taboard API, served via Swagger UI at /api-docs.
const STATUSES = ['open', 'queued', 'processing', 'done', 'failed', 'close'];
const TYPES = ['backend', 'api', 'frontend', 'ui'];
export const openapiSpec = {
openapi: '3.0.3',
info: {
title: 'Taboard API',
version: '1.0.0',
description: 'Jira-style admin board + API backed by Supabase.'
},
tags: [
{ name: 'Health', description: 'Service health' },
{ name: 'Projects', description: 'Project management' },
{ name: 'Tickets', description: 'Ticket management (board)' },
{ name: 'Flow', description: 'Lightweight ticket flow API for agents' },
{ name: 'ChefSys', description: 'Chef system: menu items (foods/drinks/add-ons) & orders' }
],
components: {
schemas: {
Error: {
type: 'object',
properties: { error: { type: 'string' } }
},
Project: {
type: 'object',
properties: {
id: { type: 'integer', readOnly: true },
name: { type: 'string' },
note: { type: 'string', nullable: true },
struc: { type: 'object', additionalProperties: true }
}
},
ProjectInput: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string' },
note: { type: 'string', nullable: true },
struc: {
oneOf: [{ type: 'object' }, { type: 'string' }],
description: 'Object, or a JSON string (parsed server-side).'
}
}
},
Ticket: {
type: 'object',
properties: {
id: { type: 'integer', readOnly: true },
title: { type: 'string' },
detail: { type: 'string', nullable: true },
type: { type: 'string', enum: TYPES },
status: { type: 'string', enum: STATUSES },
message: { type: 'string', nullable: true },
logs: { type: 'string', nullable: true },
sort: { type: 'integer' },
subtic: { type: 'integer', nullable: true },
projectid: { type: 'integer', nullable: true },
project_name: { type: 'string', nullable: true, readOnly: true },
images: { type: 'array', items: { type: 'string', format: 'uri' } }
}
},
TicketInput: {
type: 'object',
required: ['title'],
properties: {
title: { type: 'string' },
detail: { type: 'string', nullable: true },
type: { type: 'string', enum: TYPES, default: 'backend' },
status: { type: 'string', enum: STATUSES, default: 'open' },
message: { type: 'string', nullable: true },
logs: { type: 'string', nullable: true },
sort: { type: 'integer', default: 0 },
subtic: { type: 'integer', nullable: true },
projectid: { type: 'integer', nullable: true }
}
},
FlowTicket: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
detail: { type: 'string', nullable: true },
status: { type: 'string', enum: STATUSES },
message: { type: 'string', nullable: true }
}
},
FlowUpdate: {
type: 'object',
required: ['id'],
properties: {
id: { type: 'integer' },
status: { type: 'string', enum: STATUSES },
message: { type: 'string' },
detail: { type: 'string' }
}
},
ChefMenuItem: {
type: 'object',
properties: {
id: { type: 'integer', readOnly: true },
name: { type: 'string' },
price: { type: 'number' },
description: { type: 'string', nullable: true },
creatid: { type: 'string', format: 'date-time', readOnly: true }
}
},
ChefAddon: {
type: 'object',
properties: {
id: { type: 'integer', readOnly: true },
name: { type: 'string' },
price: { type: 'number' },
creatid: { type: 'string', format: 'date-time', readOnly: true }
}
},
ChefOrderItem: {
type: 'object',
properties: {
type: { type: 'string', enum: ['food', 'drink', 'addon'] },
id: { type: 'integer' },
name: { type: 'string', readOnly: true },
price: { type: 'number', readOnly: true },
qty: { type: 'integer' }
}
},
ChefOrder: {
type: 'object',
properties: {
id: { type: 'integer', readOnly: true },
client: { type: 'integer' },
total_item: { type: 'integer', readOnly: true },
total_price: { type: 'integer', readOnly: true },
detail: { type: 'array', items: { $ref: '#/components/schemas/ChefOrderItem' } },
created_at: { type: 'string', format: 'date-time', readOnly: true }
}
},
ChefOrderInput: {
type: 'object',
required: ['client', 'items'],
properties: {
client: { type: 'integer' },
items: {
type: 'array',
items: {
type: 'object',
required: ['type', 'id', 'qty'],
properties: {
type: { type: 'string', enum: ['food', 'drink', 'addon'] },
id: { type: 'integer' },
qty: { type: 'integer', minimum: 1 }
}
}
}
}
}
},
responses: {
NotFound: {
description: 'Not found',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
},
BadRequest: {
description: 'Invalid request',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
},
ServerError: {
description: 'Server error',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
}
}
},
paths: {
'/api/health': {
get: {
tags: ['Health'],
summary: 'Health check',
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: { type: 'object', properties: { ok: { type: 'boolean' } } }
}
}
}
}
}
},
'/api/projects': {
get: {
tags: ['Projects'],
summary: 'List projects',
responses: {
200: {
description: 'Array of projects',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/Project' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
},
post: {
tags: ['Projects'],
summary: 'Create project',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/ProjectInput' } } }
},
responses: {
201: {
description: 'Created',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Project' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/api/projects/{id}': {
parameters: [
{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }
],
get: {
tags: ['Projects'],
summary: 'Get project',
responses: {
200: {
description: 'Project',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Project' } } }
},
404: { $ref: '#/components/responses/NotFound' }
}
},
put: {
tags: ['Projects'],
summary: 'Update project',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/ProjectInput' } } }
},
responses: {
200: {
description: 'Updated',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Project' } } }
},
500: { $ref: '#/components/responses/ServerError' }
}
},
delete: {
tags: ['Projects'],
summary: 'Delete project',
responses: {
204: { description: 'Deleted' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/api/tickets': {
get: {
tags: ['Tickets'],
summary: 'List tickets',
parameters: [
{
name: 'subtic', in: 'query',
schema: { type: 'string' },
description: 'Filter by parent ticket id, or "null" for top-level tickets.'
},
{
name: 'projectid', in: 'query',
schema: { type: 'integer' },
description: 'Filter by project id.'
},
{
name: 'project_name', in: 'query',
schema: { type: 'string' },
description: 'Filter by project name; resolved to the matching project id.'
}
],
responses: {
200: {
description: 'Array of tickets',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/Ticket' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
},
post: {
tags: ['Tickets'],
summary: 'Create ticket',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/TicketInput' } } }
},
responses: {
201: {
description: 'Created',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/api/tickets/{id}': {
parameters: [
{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }
],
get: {
tags: ['Tickets'],
summary: 'Get ticket',
responses: {
200: {
description: 'Ticket',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
404: { $ref: '#/components/responses/NotFound' }
}
},
put: {
tags: ['Tickets'],
summary: 'Update ticket',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/TicketInput' } } }
},
responses: {
200: {
description: 'Updated',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
500: { $ref: '#/components/responses/ServerError' }
}
},
delete: {
tags: ['Tickets'],
summary: 'Delete ticket (sub-tickets cascade)',
responses: {
204: { description: 'Deleted' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/api/tickets/{id}/images': {
parameters: [
{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }
],
post: {
tags: ['Tickets'],
summary: 'Upload ticket images',
requestBody: {
required: true,
content: {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
images: {
type: 'array',
items: { type: 'string', format: 'binary' }
}
}
}
}
}
},
responses: {
201: {
description: 'Ticket with appended image URLs',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
404: { $ref: '#/components/responses/NotFound' },
500: { $ref: '#/components/responses/ServerError' }
}
},
delete: {
tags: ['Tickets'],
summary: 'Remove one image URL from a ticket',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['url'],
properties: { url: { type: 'string', format: 'uri' } }
}
}
}
},
responses: {
200: {
description: 'Updated ticket',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
404: { $ref: '#/components/responses/NotFound' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/flow/tickets': {
get: {
tags: ['Flow'],
summary: 'List tickets (id, title, status)',
responses: {
200: {
description: 'Array of flow tickets',
content: {
'application/json': {
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
status: { type: 'string', enum: STATUSES }
}
}
}
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
},
put: {
tags: ['Flow'],
summary: 'Update ticket status/message/detail',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/FlowUpdate' } } }
},
responses: {
200: {
description: 'Updated ticket',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/flow/tickets/queue': {
get: {
tags: ['Flow'],
summary: 'Next queued ticket for a project',
description: 'Returns the first ticket for the given project with status "queued", ordered by the smallest sort.',
parameters: [
{
name: 'project_name', in: 'query', required: true,
schema: { type: 'string' },
description: 'Project name; resolved to the matching project id.'
}
],
responses: {
200: {
description: 'The next queued ticket',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Ticket' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
404: { $ref: '#/components/responses/NotFound' },
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/flow/tickets/{id}': {
parameters: [
{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }
],
get: {
tags: ['Flow'],
summary: 'Get ticket (id, title, detail, status, message)',
responses: {
200: {
description: 'Flow ticket',
content: { 'application/json': { schema: { $ref: '#/components/schemas/FlowTicket' } } }
},
404: { $ref: '#/components/responses/NotFound' }
}
}
},
'/chefsys/foods': {
get: {
tags: ['ChefSys'],
summary: 'List foods',
responses: {
200: {
description: 'Array of foods',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/ChefMenuItem' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/chefsys/drinks': {
get: {
tags: ['ChefSys'],
summary: 'List drinks',
responses: {
200: {
description: 'Array of drinks',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/ChefMenuItem' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/chefsys/add-on': {
get: {
tags: ['ChefSys'],
summary: 'List add-ons',
responses: {
200: {
description: 'Array of add-ons',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/ChefAddon' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
}
},
'/chefsys/order': {
get: {
tags: ['ChefSys'],
summary: 'List all orders',
responses: {
200: {
description: 'Array of orders',
content: {
'application/json': {
schema: { type: 'array', items: { $ref: '#/components/schemas/ChefOrder' } }
}
}
},
500: { $ref: '#/components/responses/ServerError' }
}
},
post: {
tags: ['ChefSys'],
summary: 'Create an order',
description: 'Item name/price are re-resolved server-side from foods/drinks/add-ons; total is computed from them.',
requestBody: {
required: true,
content: { 'application/json': { schema: { $ref: '#/components/schemas/ChefOrderInput' } } }
},
responses: {
201: {
description: 'Created',
content: { 'application/json': { schema: { $ref: '#/components/schemas/ChefOrder' } } }
},
400: { $ref: '#/components/responses/BadRequest' },
500: { $ref: '#/components/responses/ServerError' }
}
}
}
}
};