Renumber the 15 reference docs into pillar groups (recipe -> anatomy): - 01 Build Recipe (entry), 02 Composition Root - domains: 10 Layering Rules - contract: 20 Spec-First Routing, 21 CRUD Template, 22 Auth Integration - interfaces/infra: 30 Infra Adapters - UI: 40 UI: Auto-Rendered Admin & Custom Pages (new; @aetoskia packages, client template, x- catalog via react-openapi MCP module) - pipelines: 50 Pipeline Scaffold, 51 Worker & Progress, 52 Human-in-the-Loop - delivery/meta: 90 Drone Pipelines, 91 Versioning & Changelog, 92 Genericity Matrix, 93 Library Registry, 94 Repository Inventory MCP module names are unchanged (NN_ prefix stripped); all cross-references (wiki, templates, hooks, registry) updated; lib index rewritten pillar-first; build recipe bootstraps the client template. Bundle + HTML sites regenerated.
452 lines
12 KiB
YAML
452 lines
12 KiB
YAML
openapi: 3.1.0
|
||
|
||
info:
|
||
title: App Template API
|
||
version: 0.1.0
|
||
description: |
|
||
App template API — OpenAPI-first backend pairing with the client template
|
||
and the @aetoskia/react-openapi admin panel renderer. Resources are driven
|
||
by OpenAPI x- extension fields; auth is bearer JWT introspected against
|
||
the auth-server. Item and Job are example domains — replace them with
|
||
your own, keeping the x- conventions documented in the react-openapi MCP
|
||
bundle (module react-openapi, object specExtensions).
|
||
|
||
servers:
|
||
- url: http://localhost:8000
|
||
|
||
security:
|
||
- bearerAuth: []
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
x-server-url: "{AUTH_SERVER}"
|
||
x-introspect-path: "/introspect"
|
||
x-login-path: "/login"
|
||
x-register-path: "/register"
|
||
x-logout-path: "/logout"
|
||
x-me-path: "/me"
|
||
|
||
schemas:
|
||
Item:
|
||
type: object
|
||
x-resource: items
|
||
x-primary-key: id
|
||
x-display-format: "{name} × {quantity}"
|
||
x-list-columns: [name, quantity]
|
||
properties:
|
||
id:
|
||
type: integer
|
||
readOnly: true
|
||
x-order: 0
|
||
x-hidden: { form: true, list: true }
|
||
x-label: "ID"
|
||
name:
|
||
type: string
|
||
x-order: 1
|
||
x-label: "Name"
|
||
x-description: "Item name"
|
||
x-filterable: true
|
||
x-sortable: true
|
||
x-autocomplete: token
|
||
quantity:
|
||
type: integer
|
||
default: 1
|
||
x-order: 2
|
||
x-label: "Quantity"
|
||
x-description: "How many units"
|
||
x-sortable: true
|
||
required: [id, name]
|
||
|
||
Job:
|
||
type: object
|
||
x-resource: jobs
|
||
x-primary-key: id
|
||
x-display-format: "{name} [{status}]"
|
||
x-list-columns: [name, status, retry_count]
|
||
properties:
|
||
id:
|
||
type: integer
|
||
readOnly: true
|
||
x-order: 0
|
||
x-hidden: { form: true, list: true }
|
||
x-label: "ID"
|
||
name:
|
||
type: string
|
||
x-order: 1
|
||
x-label: "Name"
|
||
x-description: "Job name"
|
||
x-filterable: true
|
||
x-autocomplete: token
|
||
status:
|
||
type: string
|
||
enum: [pending, processing, done, failed]
|
||
x-order: 2
|
||
x-label: "Status"
|
||
x-description: "Lifecycle status"
|
||
x-filterable: true
|
||
retry_count:
|
||
type: integer
|
||
readOnly: true
|
||
x-order: 3
|
||
x-label: "Retries"
|
||
error_message:
|
||
type: string
|
||
readOnly: true
|
||
x-order: 4
|
||
x-label: "Last Error"
|
||
payload:
|
||
type: object
|
||
x-order: 5
|
||
x-label: "Payload"
|
||
x-description: "Arbitrary job payload"
|
||
required: [id, name, status]
|
||
|
||
ErrorBody:
|
||
type: object
|
||
properties:
|
||
detail:
|
||
type: string
|
||
required: [detail]
|
||
|
||
HTTPValidationError:
|
||
type: object
|
||
properties:
|
||
detail:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/ValidationError'
|
||
|
||
ValidationError:
|
||
type: object
|
||
properties:
|
||
loc:
|
||
type: array
|
||
items:
|
||
type: string
|
||
msg:
|
||
type: string
|
||
type:
|
||
type: string
|
||
required: [loc, msg, type]
|
||
|
||
responses:
|
||
Unauthorized:
|
||
description: Not authenticated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorBody'
|
||
Forbidden:
|
||
description: Insufficient permissions
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorBody'
|
||
NotFound:
|
||
description: Resource not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorBody'
|
||
ValidationError:
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
InternalServerError:
|
||
description: Internal server error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorBody'
|
||
|
||
paths:
|
||
/health:
|
||
get:
|
||
summary: Liveness probe (public, unauthenticated)
|
||
operationId: health
|
||
security: []
|
||
responses:
|
||
'200':
|
||
description: Service is alive
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
status:
|
||
type: string
|
||
service:
|
||
type: string
|
||
|
||
/items:
|
||
get:
|
||
summary: List items (paginated)
|
||
operationId: list_items
|
||
parameters:
|
||
- in: query
|
||
name: limit
|
||
schema: {type: integer, default: 20}
|
||
- in: query
|
||
name: offset
|
||
schema: {type: integer, default: 0}
|
||
responses:
|
||
'200':
|
||
description: Paginated list of items
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
total:
|
||
type: integer
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/Item'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
post:
|
||
summary: Create an item
|
||
operationId: create_item
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Item'
|
||
responses:
|
||
'201':
|
||
description: Item created
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Item'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
/items/{id}:
|
||
get:
|
||
operationId: get_item
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
responses:
|
||
'200':
|
||
description: Single item
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Item'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
put:
|
||
operationId: update_item
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Item'
|
||
responses:
|
||
'200':
|
||
description: Item updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Item'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
delete:
|
||
operationId: delete_item
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
responses:
|
||
'204':
|
||
description: Item deleted
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
|
||
/jobs:
|
||
get:
|
||
summary: List jobs (paginated)
|
||
operationId: list_jobs
|
||
parameters:
|
||
- in: query
|
||
name: limit
|
||
schema: {type: integer, default: 20}
|
||
- in: query
|
||
name: offset
|
||
schema: {type: integer, default: 0}
|
||
responses:
|
||
'200':
|
||
description: Paginated list of jobs
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
total:
|
||
type: integer
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/Job'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
post:
|
||
summary: Create a job
|
||
operationId: create_job
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Job'
|
||
responses:
|
||
'201':
|
||
description: Job created
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Job'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
/jobs/{id}:
|
||
get:
|
||
operationId: get_job
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
responses:
|
||
'200':
|
||
description: Single job
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Job'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
put:
|
||
operationId: update_job
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Job'
|
||
responses:
|
||
'200':
|
||
description: Job updated
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Job'
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|
||
delete:
|
||
operationId: delete_job
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema: {type: integer}
|
||
responses:
|
||
'204':
|
||
description: Job deleted
|
||
'401':
|
||
$ref: '#/components/responses/Unauthorized'
|
||
'403':
|
||
$ref: '#/components/responses/Forbidden'
|
||
'404':
|
||
$ref: '#/components/responses/NotFound'
|
||
'422':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'500':
|
||
$ref: '#/components/responses/InternalServerError'
|