497 lines
22 KiB
JSON
497 lines
22 KiB
JSON
{
|
|
"openapi": "3.0.3",
|
|
"info": {
|
|
"title": "Bookings API",
|
|
"version": "1.0.0",
|
|
"description": "OpenAPI specification for booking-related endpoints. This spec is standalone and not related to XLVask."
|
|
},
|
|
"servers": [
|
|
{ "url": "https://api.truckwash.dk:4433" }
|
|
],
|
|
"tags": [
|
|
{ "name": "Bookings" },
|
|
{ "name": "User Bookings" },
|
|
{ "name": "Admin Bookings" },
|
|
{ "name": "Public Time Bookings" }
|
|
],
|
|
"paths": {
|
|
"/bookings": {
|
|
"get": {
|
|
"tags": ["Bookings"],
|
|
"summary": "List bookings or fetch a single booking by id",
|
|
"description": "Requires permission `list_bookings`. If query parameter `id` is set, returns a single booking; otherwise returns a paginated list. If the caller has permission `issue_wash_certificates`, the meta will include `wash_certificate_token`.",
|
|
"parameters": [
|
|
{ "$ref": "#/components/parameters/id" },
|
|
{ "$ref": "#/components/parameters/page" },
|
|
{ "$ref": "#/components/parameters/limit" },
|
|
{ "$ref": "#/components/parameters/search" },
|
|
{ "$ref": "#/components/parameters/filters" },
|
|
{ "$ref": "#/components/parameters/order" }
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Success",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": { "$ref": "#/components/schemas/EnvelopeBookingsOrBooking" }
|
|
}
|
|
}
|
|
},
|
|
"400": { "description": "Invalid request or session" },
|
|
"403": { "description": "Forbidden" },
|
|
"404": { "description": "Booking not found" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
},
|
|
"put": {
|
|
"tags": ["Bookings"],
|
|
"summary": "Update a booking (reference number)",
|
|
"description": "Requires permission `list_own_bookings`. Updates only the `reference_number` for a booking the user has access to.",
|
|
"parameters": [
|
|
{ "$ref": "#/components/parameters/id" },
|
|
{
|
|
"name": "reference_number",
|
|
"in": "query",
|
|
"required": false,
|
|
"schema": { "type": "string", "maxLength": 255 }
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Updated booking",
|
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBooking" } } }
|
|
},
|
|
"400": { "description": "Invalid request or session" },
|
|
"403": { "description": "Forbidden" },
|
|
"404": { "description": "Booking not found" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/user/bookings": {
|
|
"get": {
|
|
"tags": ["User Bookings"],
|
|
"summary": "List bookings for the authenticated user",
|
|
"description": "Requires permission `list_own_bookings`. Returns a paginated list of the user's own bookings.",
|
|
"parameters": [
|
|
{ "$ref": "#/components/parameters/page" },
|
|
{ "$ref": "#/components/parameters/limit" },
|
|
{ "$ref": "#/components/parameters/search" },
|
|
{ "$ref": "#/components/parameters/filters" },
|
|
{ "$ref": "#/components/parameters/order" }
|
|
],
|
|
"responses": {
|
|
"200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBookingsList" } } } },
|
|
"400": { "description": "Invalid request or session" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/admin/bookings/sync": {
|
|
"post": {
|
|
"tags": ["Admin Bookings"],
|
|
"summary": "Sync or upsert a booking from external system",
|
|
"description": "Requires permission `sync_bookings` unless `auth_key` equals the configured value. Accepts all booking fields as query/body form parameters and upserts the booking.",
|
|
"requestBody": {
|
|
"required": false,
|
|
"content": {
|
|
"application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/SyncBookingRequest" } },
|
|
"application/json": { "schema": { "$ref": "#/components/schemas/SyncBookingRequest" } }
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": { "description": "Success", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } }
|
|
}
|
|
}
|
|
},
|
|
|
|
"/admin/bookings/department/count": {
|
|
"get": {
|
|
"tags": ["Admin Bookings"],
|
|
"summary": "Get unfulfilled bookings count for a department (today)",
|
|
"description": "Requires permission `list_department_bookings_count`. Returns an integer count. Response meta may include `cached: true` when the value was served from cache.",
|
|
"parameters": [
|
|
{ "$ref": "#/components/parameters/department_id" }
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Count",
|
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeInteger" } } }
|
|
},
|
|
"400": { "description": "Invalid request or session" },
|
|
"404": { "description": "Department not found" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/user/bookings/washcertificate/download": {
|
|
"post": {
|
|
"tags": ["User Bookings"],
|
|
"summary": "Get download link for a booking's wash certificate",
|
|
"description": "Requires permission `download_own_wash_certificate`, except authenticated customer accounts may download certificates for their own bookings. Returns a presigned download link if certificate exists and user has access.",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Link", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeDownloadLink" } } } },
|
|
"400": { "description": "Not allowed or invalid status" },
|
|
"404": { "description": "Booking or certificate not found" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/bookings/download_pdf": {
|
|
"get": {
|
|
"tags": ["User Bookings"],
|
|
"summary": "Get download link for a booking's wash certificate PDF",
|
|
"description": "Requires permission `download_own_wash_certificate`, except authenticated customer accounts may download certificates for their own bookings. Checks both legacy and current storage buckets.",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Link", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeDownloadLink" } } } },
|
|
"400": { "description": "Not allowed or invalid session" },
|
|
"404": { "description": "Booking or certificate not found" },
|
|
"500": { "description": "Storage inconsistency" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/admin/bookings/delete": {
|
|
"post": {
|
|
"tags": ["Admin Bookings"],
|
|
"summary": "Delete a booking",
|
|
"description": "Requires permission `delete_booking`. Deletes the booking if the user has access.",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } },
|
|
"400": { "description": "Not allowed or invalid session" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/superuser/bookings/sync/all": {
|
|
"post": {
|
|
"tags": ["Admin Bookings"],
|
|
"summary": "Sync all bookings from external system",
|
|
"description": "Requires permission `sync_all_bookings`.",
|
|
"responses": {
|
|
"200": { "description": "Triggered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } },
|
|
"400": { "description": "Invalid session" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/admin/bookings/completeWashWithoutWashCertificate": {
|
|
"post": {
|
|
"tags": ["Admin Bookings"],
|
|
"summary": "Complete wash without generating a certificate",
|
|
"description": "Requires permission `complete_wash_without_wash_certificate`.",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Completed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } },
|
|
"400": { "description": "Not allowed or invalid session" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/user/bookings/delete": {
|
|
"post": {
|
|
"tags": ["User Bookings"],
|
|
"summary": "Delete own booking",
|
|
"description": "Requires permission `delete_own_booking`. Deletes the user's own booking if accessible.",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeMessage" } } } },
|
|
"400": { "description": "Not allowed or invalid session" }
|
|
},
|
|
"security": [{ "BearerAuth": [] }]
|
|
}
|
|
},
|
|
|
|
"/department/timebookings/opening-hours/public": {
|
|
"get": {
|
|
"tags": ["Public Time Bookings"],
|
|
"summary": "Get public opening hours for a department",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Opening hours", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeOpeningHours" } } } },
|
|
"400": { "description": "Missing or invalid id" },
|
|
"404": { "description": "Department or opening hours not found, or module disabled" }
|
|
}
|
|
}
|
|
},
|
|
|
|
"/department/timebookings/types/public": {
|
|
"get": {
|
|
"tags": ["Public Time Bookings"],
|
|
"summary": "List public booking types for a department",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" } ],
|
|
"responses": {
|
|
"200": { "description": "Types", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeBookingTypesList" } } } },
|
|
"400": { "description": "Missing or invalid id" },
|
|
"404": { "description": "Department or types not found, or module disabled" }
|
|
}
|
|
}
|
|
},
|
|
|
|
"/department/timebookings/entries/public": {
|
|
"get": {
|
|
"tags": ["Public Time Bookings"],
|
|
"summary": "List public booking entries for a department",
|
|
"parameters": [ { "$ref": "#/components/parameters/id" }, { "$ref": "#/components/parameters/page" }, { "$ref": "#/components/parameters/limit" }, { "$ref": "#/components/parameters/search" }, { "$ref": "#/components/parameters/filters" }, { "$ref": "#/components/parameters/order" } ],
|
|
"responses": {
|
|
"200": { "description": "Entries", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeTimeBookingEntriesList" } } } },
|
|
"400": { "description": "Missing or invalid id" },
|
|
"404": { "description": "Department not found or module disabled" }
|
|
}
|
|
},
|
|
"post": {
|
|
"tags": ["Public Time Bookings"],
|
|
"summary": "Create a public booking entry",
|
|
"description": "Creates a time booking entry for a department and type at a specific start time. The duration is derived from the type.",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/PublicEntryCreate" } },
|
|
"application/json": { "schema": { "$ref": "#/components/schemas/PublicEntryCreate" } }
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvelopeTimeBookingEntry" } } } },
|
|
"400": { "description": "Validation error" },
|
|
"404": { "description": "Department/type not found or module disabled" }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"securitySchemes": {
|
|
"BearerAuth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT"
|
|
}
|
|
},
|
|
"parameters": {
|
|
"id": { "name": "id", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } },
|
|
"department_id": { "name": "department_id", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } },
|
|
"page": { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } },
|
|
"limit": { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1 } },
|
|
"search": { "name": "search", "in": "query", "required": false, "schema": { "type": "string" } },
|
|
"filters": { "name": "filters", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Comma-separated filters: key:value pairs (supports multiple keys)." },
|
|
"order": { "name": "order", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Sort order, e.g. `id:ASC` or `created_at:DESC`." }
|
|
},
|
|
"schemas": {
|
|
"Envelope": {
|
|
"type": "object",
|
|
"properties": {
|
|
"success": { "type": "boolean" },
|
|
"data": {},
|
|
"meta": { "type": "object", "additionalProperties": true },
|
|
"includes": { "type": "object", "additionalProperties": true }
|
|
},
|
|
"required": ["success", "data", "meta", "includes"]
|
|
},
|
|
"EnvelopeMessage": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "type": "object", "properties": { "message": { "type": "string" } }, "required": ["message"] } } }
|
|
]
|
|
},
|
|
"EnvelopeInteger": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "type": "integer" } } }
|
|
]
|
|
},
|
|
"EnvelopeDownloadLink": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "$ref": "#/components/schemas/DownloadLink" } } }
|
|
]
|
|
},
|
|
"EnvelopeBookingsList": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Booking" } } } }
|
|
]
|
|
},
|
|
"EnvelopeBooking": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Booking" } } }
|
|
]
|
|
},
|
|
"EnvelopeBookingsOrBooking": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "oneOf": [ { "$ref": "#/components/schemas/Booking" }, { "type": "array", "items": { "$ref": "#/components/schemas/Booking" } } ] } } }
|
|
]
|
|
},
|
|
"EnvelopeOpeningHours": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "$ref": "#/components/schemas/OpeningHours" } } }
|
|
]
|
|
},
|
|
"EnvelopeBookingTypesList": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/BookingType" } } } }
|
|
]
|
|
},
|
|
"EnvelopeTimeBookingEntriesList": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/TimeBookingPublicEntry" } } } }
|
|
]
|
|
},
|
|
"EnvelopeTimeBookingEntry": {
|
|
"allOf": [
|
|
{ "$ref": "#/components/schemas/Envelope" },
|
|
{ "type": "object", "properties": { "data": { "$ref": "#/components/schemas/TimeBookingEntry" } } }
|
|
]
|
|
},
|
|
|
|
"Booking": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "integer" },
|
|
"customer_number": { "type": "integer" },
|
|
"wash_type": { "type": "string" },
|
|
"contact_email": { "type": "string" },
|
|
"reference_number": { "type": "string", "nullable": true },
|
|
"regNrTraekker": { "type": "string", "nullable": true },
|
|
"regNrTrailer": { "type": "string", "nullable": true },
|
|
"washCertificateEmail": { "type": "string", "nullable": true },
|
|
"date": { "type": "string" },
|
|
"department": { "type": "integer" },
|
|
"pickup_bool": { "type": "boolean" },
|
|
"notes": { "type": "string", "nullable": true },
|
|
"washCertificateStatus": { "type": "string", "enum": ["pending", "completed", "cancelled"], "nullable": true },
|
|
"washCertificateUrl": { "type": "string", "nullable": true },
|
|
"wash_certificate_pdf": { "type": "string", "nullable": true },
|
|
"status": { "type": "string", "enum": ["pending", "completed", "cancelled"] },
|
|
"data": { "type": "object", "nullable": true, "additionalProperties": true },
|
|
"parsed_services": {
|
|
"type": "object",
|
|
"nullable": true,
|
|
"properties": {
|
|
"string": { "type": "string" },
|
|
"array": { "type": "array", "items": { "type": "integer" } }
|
|
}
|
|
}
|
|
},
|
|
"required": ["id", "customer_number", "wash_type", "date", "department", "pickup_bool", "status"]
|
|
},
|
|
|
|
"DownloadLink": {
|
|
"type": "object",
|
|
"properties": { "link": { "type": "string", "format": "uri" } },
|
|
"required": ["link"]
|
|
},
|
|
|
|
"OpeningHours": {
|
|
"type": "object",
|
|
"properties": {
|
|
"monday_start": { "type": ["string", "null"] },
|
|
"monday_end": { "type": ["string", "null"] },
|
|
"tuesday_start": { "type": ["string", "null"] },
|
|
"tuesday_end": { "type": ["string", "null"] },
|
|
"wednesday_start": { "type": ["string", "null"] },
|
|
"wednesday_end": { "type": ["string", "null"] },
|
|
"thursday_start": { "type": ["string", "null"] },
|
|
"thursday_end": { "type": ["string", "null"] },
|
|
"friday_start": { "type": ["string", "null"] },
|
|
"friday_end": { "type": ["string", "null"] },
|
|
"saturday_start": { "type": ["string", "null"] },
|
|
"saturday_end": { "type": ["string", "null"] },
|
|
"sunday_start": { "type": ["string", "null"] },
|
|
"sunday_end": { "type": ["string", "null"] }
|
|
}
|
|
},
|
|
|
|
"BookingType": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "integer" },
|
|
"department": { "type": "integer" },
|
|
"product": { "type": "integer" },
|
|
"name": { "type": "string" },
|
|
"description": { "type": "string" },
|
|
"duration": { "type": "integer", "description": "Duration in minutes" }
|
|
},
|
|
"required": ["id", "department", "product", "name", "description", "duration"]
|
|
},
|
|
|
|
"TimeBookingPublicEntry": {
|
|
"type": "object",
|
|
"properties": {
|
|
"start": { "type": "string" },
|
|
"duration": { "type": "integer" }
|
|
},
|
|
"required": ["start", "duration"]
|
|
},
|
|
|
|
"TimeBookingEntry": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "integer" },
|
|
"department": { "type": "integer" },
|
|
"type": { "type": "integer" },
|
|
"start": { "type": "string" },
|
|
"end": { "type": "string" },
|
|
"note": { "type": ["string", "null"] },
|
|
"reg": { "type": ["string", "null"] },
|
|
"phone": { "type": "integer" },
|
|
"phone_country_code": { "type": "integer" }
|
|
},
|
|
"required": ["id", "department", "type", "start", "end", "phone", "phone_country_code"]
|
|
},
|
|
|
|
"PublicEntryCreate": {
|
|
"type": "object",
|
|
"properties": {
|
|
"department": { "type": "integer" },
|
|
"type": { "type": "integer" },
|
|
"start": { "type": "string", "example": "2025-05-01 10:00:00" },
|
|
"phone_country_code": { "type": "integer", "nullable": true },
|
|
"phone": { "type": "integer", "nullable": true },
|
|
"note": { "type": "string", "nullable": true },
|
|
"reg": { "type": "string", "nullable": true },
|
|
"addons": { "type": "array", "nullable": true, "items": { "type": "integer" } }
|
|
},
|
|
"required": ["department", "type", "start"]
|
|
},
|
|
|
|
"SyncBookingRequest": {
|
|
"type": "object",
|
|
"properties": {
|
|
"auth_key": { "type": "string", "description": "Special key which allows syncing without the `sync_bookings` permission when valid" },
|
|
"id": { "type": "integer" },
|
|
"customer_number": { "type": "integer" },
|
|
"wash_type": { "type": "string" },
|
|
"contact_email": { "type": "string" },
|
|
"reference_number": { "type": "string" },
|
|
"regNrTraekker": { "type": "string" },
|
|
"regNrTrailer": { "type": "string" },
|
|
"washCertificateEmail": { "type": "string" },
|
|
"date": { "type": "string" },
|
|
"department": { "type": "integer" },
|
|
"pickup_bool": { "type": "integer" },
|
|
"notes": { "type": "string" },
|
|
"washCertificateStatus": { "type": "string" },
|
|
"washCertificateUrl": { "type": "string" },
|
|
"status": { "type": "string" }
|
|
},
|
|
"required": ["id", "customer_number", "wash_type", "contact_email", "reference_number", "regNrTraekker", "regNrTrailer", "washCertificateEmail", "date", "department", "pickup_bool", "notes", "washCertificateStatus", "washCertificateUrl", "status"]
|
|
}
|
|
}
|
|
}
|
|
}
|