mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 07:43:13 +00:00
666 lines
16 KiB
YAML
666 lines
16 KiB
YAML
|
|
openapi: 3.1.0
|
||
|
|
info:
|
||
|
|
title: Meal Order Manager
|
||
|
|
version: 0.1.0
|
||
|
|
description: >
|
||
|
|
Flask HTTP API on ECS Fargate behind orders.seahaven.com. The internal
|
||
|
|
portal SPA calls menu, submit, and admin with a Cognito ID token from
|
||
|
|
GET /api/auth/meals-token. Weekly-menu GitHub Actions uses HMAC publish
|
||
|
|
routes that CloudFront does not expose. JSON errors are currently
|
||
|
|
`{ error: string }`. Health matches the portal BFF `{ stage, sha }`.
|
||
|
|
Lint with the same Redocly `extends: recommended` config as
|
||
|
|
internal-portal and afterhours-shift-manager.
|
||
|
|
contact:
|
||
|
|
name: Sea Haven Engineering
|
||
|
|
license:
|
||
|
|
name: Proprietary
|
||
|
|
identifier: LicenseRef-SeaHaven
|
||
|
|
|
||
|
|
servers:
|
||
|
|
- url: /
|
||
|
|
description: orders.seahaven.com CloudFront / local Flask :5050
|
||
|
|
|
||
|
|
tags:
|
||
|
|
- name: Runtime
|
||
|
|
description: Unauthenticated health
|
||
|
|
- name: Menu
|
||
|
|
description: Public weekly menu and form status
|
||
|
|
- name: Orders
|
||
|
|
description: Employee submit and own-order lookup
|
||
|
|
- name: Admin
|
||
|
|
description: Admin order edit and summary PDF
|
||
|
|
- name: Publish
|
||
|
|
description: Weekly-menu HMAC publish (not on CloudFront)
|
||
|
|
|
||
|
|
paths:
|
||
|
|
/api/health:
|
||
|
|
get:
|
||
|
|
operationId: getHealth
|
||
|
|
tags: [Runtime]
|
||
|
|
summary: Runtime health
|
||
|
|
security: []
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Process is up
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/Health"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/menu/{week}:
|
||
|
|
get:
|
||
|
|
operationId: getMenu
|
||
|
|
tags: [Menu]
|
||
|
|
summary: Published menu for a week
|
||
|
|
security: []
|
||
|
|
parameters:
|
||
|
|
- $ref: "#/components/parameters/MenuWeekPath"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Menu payload
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/MenuPayload"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"404":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/form-status/{week}:
|
||
|
|
get:
|
||
|
|
operationId: getFormStatus
|
||
|
|
tags: [Menu]
|
||
|
|
summary: Open or closed for a week
|
||
|
|
security: []
|
||
|
|
parameters:
|
||
|
|
- $ref: "#/components/parameters/MenuWeekPath"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Form status
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/FormStatus"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/submit-order:
|
||
|
|
post:
|
||
|
|
operationId: submitOrder
|
||
|
|
tags: [Orders]
|
||
|
|
summary: Place or replace this week's order
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/SubmitBody"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Order saved
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/SubmitResult"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"404":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"410":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"429":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"503":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/orders/{week}:
|
||
|
|
get:
|
||
|
|
operationId: getMyOrder
|
||
|
|
tags: [Orders]
|
||
|
|
summary: Caller's order only
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
parameters:
|
||
|
|
- $ref: "#/components/parameters/OrderWeekPath"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Empty list when the caller has no order
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/MyOrders"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"503":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/admin/orders:
|
||
|
|
get:
|
||
|
|
operationId: adminListOrders
|
||
|
|
tags: [Admin]
|
||
|
|
summary: List weeks or one week's orders
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
parameters:
|
||
|
|
- name: week
|
||
|
|
in: query
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Weeks list or week detail
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/AdminOrdersResponse"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
put:
|
||
|
|
operationId: adminUpdateOrder
|
||
|
|
tags: [Admin]
|
||
|
|
summary: Recalculate and replace an order
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/AdminUpdateBody"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/AdminMutation"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"404":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"503":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
delete:
|
||
|
|
operationId: adminDeleteOrder
|
||
|
|
tags: [Admin]
|
||
|
|
summary: Delete an order
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
parameters:
|
||
|
|
- name: week
|
||
|
|
in: query
|
||
|
|
required: true
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
- name: email
|
||
|
|
in: query
|
||
|
|
required: true
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Deleted
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/AdminMutation"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"404":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/admin/summary-pdf:
|
||
|
|
get:
|
||
|
|
operationId: adminSummaryPdf
|
||
|
|
tags: [Admin]
|
||
|
|
summary: Presigned summary PDF URL
|
||
|
|
security:
|
||
|
|
- portalCognito: []
|
||
|
|
parameters:
|
||
|
|
- name: week
|
||
|
|
in: query
|
||
|
|
required: true
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Short-lived URL
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/SummaryPdf"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"404":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"500":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/roster:
|
||
|
|
get:
|
||
|
|
operationId: getRoster
|
||
|
|
tags: [Orders]
|
||
|
|
summary: Name and email list used by the static form
|
||
|
|
security: []
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Roster
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/FormRoster"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/publish/settings:
|
||
|
|
get:
|
||
|
|
operationId: publishSettings
|
||
|
|
tags: [Publish]
|
||
|
|
summary: Bulk discount and subsidy for scrape
|
||
|
|
security:
|
||
|
|
- publishKey: []
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Pricing fields only
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/PublishSettings"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
/api/publish/menu:
|
||
|
|
post:
|
||
|
|
operationId: publishMenu
|
||
|
|
tags: [Publish]
|
||
|
|
summary: Write this week's scraped menu
|
||
|
|
security:
|
||
|
|
- publishKey: []
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/PublishMenuBody"
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Published
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/PublishMenuResult"
|
||
|
|
"400":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
"403":
|
||
|
|
$ref: "#/components/responses/StringError"
|
||
|
|
|
||
|
|
components:
|
||
|
|
securitySchemes:
|
||
|
|
portalCognito:
|
||
|
|
type: http
|
||
|
|
scheme: bearer
|
||
|
|
bearerFormat: JWT
|
||
|
|
description: Portal Cognito ID token. GIS google_id_token is also accepted on submit until cutover.
|
||
|
|
publishKey:
|
||
|
|
type: apiKey
|
||
|
|
in: header
|
||
|
|
name: X-Meals-Publish-Key
|
||
|
|
|
||
|
|
parameters:
|
||
|
|
MenuWeekPath:
|
||
|
|
name: week
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: "`current` or `YYYY-WNN`. Other values are 400."
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
minLength: 1
|
||
|
|
OrderWeekPath:
|
||
|
|
name: week
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: "`YYYY-WNN` or `YYYY-MM-DD`. `current` is 400."
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
minLength: 1
|
||
|
|
|
||
|
|
responses:
|
||
|
|
StringError:
|
||
|
|
description: Current meals JSON error
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
$ref: "#/components/schemas/StringErrorBody"
|
||
|
|
|
||
|
|
schemas:
|
||
|
|
Health:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [stage, sha]
|
||
|
|
properties:
|
||
|
|
stage:
|
||
|
|
type: string
|
||
|
|
minLength: 1
|
||
|
|
description: Workspace stage (`dev`, `prod`, or `local`)
|
||
|
|
sha:
|
||
|
|
type: string
|
||
|
|
minLength: 1
|
||
|
|
description: Git SHA or `unknown` locally
|
||
|
|
|
||
|
|
StringErrorBody:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error:
|
||
|
|
type: string
|
||
|
|
minLength: 1
|
||
|
|
|
||
|
|
Meal:
|
||
|
|
type: object
|
||
|
|
additionalProperties: true
|
||
|
|
required: [name, price]
|
||
|
|
properties:
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
price:
|
||
|
|
type: number
|
||
|
|
calories:
|
||
|
|
type: [string, number, "null"]
|
||
|
|
protein:
|
||
|
|
type: [string, number, "null"]
|
||
|
|
description:
|
||
|
|
type: [string, "null"]
|
||
|
|
dietary_tags:
|
||
|
|
type: [array, "null"]
|
||
|
|
items:
|
||
|
|
type: string
|
||
|
|
image_url:
|
||
|
|
type: [string, "null"]
|
||
|
|
is_new:
|
||
|
|
type: boolean
|
||
|
|
|
||
|
|
MenuPayload:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required:
|
||
|
|
- week
|
||
|
|
- form_status
|
||
|
|
- meal_count
|
||
|
|
- meals
|
||
|
|
- bulk_discount_percent
|
||
|
|
- company_subsidy_percent
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
form_status:
|
||
|
|
type: string
|
||
|
|
scraped_at:
|
||
|
|
type: [string, "null"]
|
||
|
|
menu_url:
|
||
|
|
type: [string, "null"]
|
||
|
|
meal_count:
|
||
|
|
type: integer
|
||
|
|
meals:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/Meal"
|
||
|
|
bulk_discount_percent:
|
||
|
|
type: number
|
||
|
|
company_subsidy_percent:
|
||
|
|
type: number
|
||
|
|
order_deadline:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
FormStatus:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [week, status]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
reopen_at:
|
||
|
|
type: integer
|
||
|
|
|
||
|
|
SubmitItem:
|
||
|
|
type: object
|
||
|
|
additionalProperties: true
|
||
|
|
required: [name, quantity]
|
||
|
|
properties:
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
quantity:
|
||
|
|
type: number
|
||
|
|
retail_price:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
SubmitBody:
|
||
|
|
type: object
|
||
|
|
additionalProperties: true
|
||
|
|
required: [items]
|
||
|
|
properties:
|
||
|
|
items:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/SubmitItem"
|
||
|
|
google_id_token:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
SubmitResult:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [status]
|
||
|
|
properties:
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
message:
|
||
|
|
type: string
|
||
|
|
total:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
MyOrders:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [week, orders]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
orders:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [employee_email]
|
||
|
|
properties:
|
||
|
|
employee_email:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
AdminWeek:
|
||
|
|
type: object
|
||
|
|
additionalProperties: true
|
||
|
|
required: [week]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
form_status:
|
||
|
|
type: string
|
||
|
|
meal_count:
|
||
|
|
type: integer
|
||
|
|
order_count:
|
||
|
|
type: integer
|
||
|
|
|
||
|
|
AdminOrderItem:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [name, quantity, retail_price, price, subtotal]
|
||
|
|
properties:
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
quantity:
|
||
|
|
type: integer
|
||
|
|
retail_price:
|
||
|
|
type: number
|
||
|
|
price:
|
||
|
|
type: number
|
||
|
|
subtotal:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
AdminOrder:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [employee_name, employee_email, items, total, submitted_at]
|
||
|
|
properties:
|
||
|
|
employee_name:
|
||
|
|
type: string
|
||
|
|
employee_email:
|
||
|
|
type: string
|
||
|
|
items:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/AdminOrderItem"
|
||
|
|
total:
|
||
|
|
type: number
|
||
|
|
submitted_at:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
AdminWeeks:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [weeks]
|
||
|
|
properties:
|
||
|
|
weeks:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/AdminWeek"
|
||
|
|
|
||
|
|
AdminWeekOrders:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [week, orders, total_employees, grand_total]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
orders:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/AdminOrder"
|
||
|
|
total_employees:
|
||
|
|
type: integer
|
||
|
|
grand_total:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
AdminOrdersResponse:
|
||
|
|
oneOf:
|
||
|
|
- $ref: "#/components/schemas/AdminWeeks"
|
||
|
|
- $ref: "#/components/schemas/AdminWeekOrders"
|
||
|
|
|
||
|
|
AdminUpdateBody:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [week, email, items]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
email:
|
||
|
|
type: string
|
||
|
|
items:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/SubmitItem"
|
||
|
|
|
||
|
|
AdminMutation:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [status, week]
|
||
|
|
properties:
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
email:
|
||
|
|
type: string
|
||
|
|
total:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
SummaryPdf:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [week, url]
|
||
|
|
properties:
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
url:
|
||
|
|
type: string
|
||
|
|
format: uri
|
||
|
|
|
||
|
|
FormRoster:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [employees]
|
||
|
|
properties:
|
||
|
|
employees:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [name, email]
|
||
|
|
properties:
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
email:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
PublishSettings:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [bulk_discount_percent, company_subsidy_percent]
|
||
|
|
properties:
|
||
|
|
bulk_discount_percent:
|
||
|
|
type: number
|
||
|
|
company_subsidy_percent:
|
||
|
|
type: number
|
||
|
|
|
||
|
|
PublishMenuBody:
|
||
|
|
type: object
|
||
|
|
additionalProperties: true
|
||
|
|
required: [meals]
|
||
|
|
properties:
|
||
|
|
meals:
|
||
|
|
type: array
|
||
|
|
minItems: 1
|
||
|
|
items:
|
||
|
|
$ref: "#/components/schemas/Meal"
|
||
|
|
scraped_at:
|
||
|
|
type: string
|
||
|
|
menu_url:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
PublishMenuResult:
|
||
|
|
type: object
|
||
|
|
additionalProperties: false
|
||
|
|
required: [status, week, meal_count]
|
||
|
|
properties:
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
week:
|
||
|
|
type: string
|
||
|
|
meal_count:
|
||
|
|
type: integer
|