Add OpenAPI documentation
This commit is contained in:
parent
86c40aee91
commit
768939c22f
19
README.md
19
README.md
@ -93,6 +93,25 @@ http://127.0.0.1:8000/login.html
|
|||||||
|
|
||||||
Keep both terminals open while using the app.
|
Keep both terminals open while using the app.
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
|
||||||
|
The backend API is documented with an OpenAPI/Swagger specification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend/openapi.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
To view the interactive documentation, open [Swagger Editor](https://editor.swagger.io/)
|
||||||
|
and import `backend/openapi.yaml`. To make requests, start the backend and use
|
||||||
|
`http://127.0.0.1:5000`. Browser cookie policies may make authenticated requests
|
||||||
|
from the hosted Swagger Editor unreliable, so the app or an API client may be
|
||||||
|
easier for testing authenticated endpoints.
|
||||||
|
|
||||||
|
The API uses a Flask session cookie for authentication. Register or log in first,
|
||||||
|
then include the returned `session` cookie in authenticated requests. The
|
||||||
|
specification documents all authentication, user, journey, comment, image upload,
|
||||||
|
uploaded-file, and health endpoints.
|
||||||
|
|
||||||
### Daily Development Workflow
|
### Daily Development Workflow
|
||||||
|
|
||||||
1. Start the backend in `backend/`.
|
1. Start the backend in `backend/`.
|
||||||
|
|||||||
942
backend/openapi.yaml
Normal file
942
backend/openapi.yaml
Normal file
@ -0,0 +1,942 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Journey Mapper API
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
Backend API for the Journey Mapper application.
|
||||||
|
|
||||||
|
Authentication uses a Flask session cookie. After registering or logging in,
|
||||||
|
clients must send the returned `session` cookie with authenticated requests.
|
||||||
|
Browser requests from the frontend must use credentials, for example
|
||||||
|
`fetch(url, { credentials: "include" })`.
|
||||||
|
|
||||||
|
Journey and marker descriptions support Markdown. Raw HTML is stored as text
|
||||||
|
and escaped by the frontend when rendered.
|
||||||
|
servers:
|
||||||
|
- url: http://127.0.0.1:5000
|
||||||
|
description: Local development backend
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: Authentication
|
||||||
|
- name: Users
|
||||||
|
- name: Journeys
|
||||||
|
- name: Comments
|
||||||
|
- name: Uploads
|
||||||
|
- name: System
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/register:
|
||||||
|
post:
|
||||||
|
tags: [Authentication]
|
||||||
|
summary: Register a user
|
||||||
|
description: Creates a user, starts a session, and returns the new public user data.
|
||||||
|
operationId: registerUser
|
||||||
|
security: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/AuthRequest"
|
||||||
|
example:
|
||||||
|
username: traveller
|
||||||
|
password: secret123
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Registration successful
|
||||||
|
headers:
|
||||||
|
Set-Cookie:
|
||||||
|
description: Flask session cookie used for authenticated requests.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/AuthResponse"
|
||||||
|
example:
|
||||||
|
id: 1
|
||||||
|
username: traveller
|
||||||
|
message: Registration successful
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"409":
|
||||||
|
description: Username is already taken
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Username already taken
|
||||||
|
|
||||||
|
/api/login:
|
||||||
|
post:
|
||||||
|
tags: [Authentication]
|
||||||
|
summary: Log in
|
||||||
|
description: Validates the credentials and starts a session.
|
||||||
|
operationId: loginUser
|
||||||
|
security: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/AuthRequest"
|
||||||
|
example:
|
||||||
|
username: traveller
|
||||||
|
password: secret123
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Login successful
|
||||||
|
headers:
|
||||||
|
Set-Cookie:
|
||||||
|
description: Flask session cookie used for authenticated requests.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/AuthResponse"
|
||||||
|
example:
|
||||||
|
id: 1
|
||||||
|
username: traveller
|
||||||
|
message: Login successful
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"401":
|
||||||
|
description: Invalid username or password
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Invalid username or password
|
||||||
|
|
||||||
|
/api/logout:
|
||||||
|
post:
|
||||||
|
tags: [Authentication]
|
||||||
|
summary: Log out
|
||||||
|
description: Removes the user ID from the current session. This operation also succeeds if no session exists.
|
||||||
|
operationId: logoutUser
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Logged out
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Message"
|
||||||
|
example:
|
||||||
|
message: Logged out
|
||||||
|
|
||||||
|
/api/me:
|
||||||
|
get:
|
||||||
|
tags: [Authentication]
|
||||||
|
summary: Get the current user
|
||||||
|
operationId: getCurrentUser
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Current public user data
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/NotLoggedIn"
|
||||||
|
|
||||||
|
/api/users:
|
||||||
|
get:
|
||||||
|
tags: [Users]
|
||||||
|
summary: List other users
|
||||||
|
description: Returns every public user except the currently logged-in user.
|
||||||
|
operationId: listUsers
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Public users
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
|
||||||
|
/api/journeys:
|
||||||
|
get:
|
||||||
|
tags: [Journeys]
|
||||||
|
summary: List visible journeys
|
||||||
|
description: |
|
||||||
|
Returns journeys owned by the current user, public journeys, and journeys
|
||||||
|
shared with the current user. Each returned journey contains a `can_edit`
|
||||||
|
flag calculated for the current user.
|
||||||
|
operationId: listJourneys
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Visible journeys
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/JourneyListItem"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
post:
|
||||||
|
tags: [Journeys]
|
||||||
|
summary: Create a journey
|
||||||
|
description: |
|
||||||
|
Creates a journey owned by the current user. Supplied comments and
|
||||||
|
server-managed fields such as IDs and timestamps are ignored.
|
||||||
|
operationId: createJourney
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/JourneyCreate"
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Journey created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Journey"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
|
||||||
|
/api/journeys/{journeyId}:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/JourneyId"
|
||||||
|
get:
|
||||||
|
tags: [Journeys]
|
||||||
|
summary: Get a journey
|
||||||
|
operationId: getJourney
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Journey data
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Journey"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: The current user cannot view this journey
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Access denied
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/JourneyNotFound"
|
||||||
|
put:
|
||||||
|
tags: [Journeys]
|
||||||
|
summary: Update a journey
|
||||||
|
description: |
|
||||||
|
The owner and users with shared edit access may update the title,
|
||||||
|
description, and markers. Only the owner may update visibility or sharing.
|
||||||
|
Omitted fields remain unchanged.
|
||||||
|
operationId: updateJourney
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/JourneyUpdate"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Journey updated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Journey"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: The current user cannot edit the journey or its sharing settings
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
examples:
|
||||||
|
cannotEdit:
|
||||||
|
value:
|
||||||
|
error: Not authorized to edit this journey
|
||||||
|
sharingOwnerOnly:
|
||||||
|
value:
|
||||||
|
error: Only the owner can update sharing settings
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/JourneyNotFound"
|
||||||
|
delete:
|
||||||
|
tags: [Journeys]
|
||||||
|
summary: Delete a journey
|
||||||
|
description: Only the journey owner may delete it.
|
||||||
|
operationId: deleteJourney
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Journey deleted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [message, journey]
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
journey:
|
||||||
|
$ref: "#/components/schemas/Journey"
|
||||||
|
example:
|
||||||
|
message: Journey deleted successfully
|
||||||
|
journey:
|
||||||
|
id: 1
|
||||||
|
owner_id: 1
|
||||||
|
title: Switzerland
|
||||||
|
description: My summer journey
|
||||||
|
markers: []
|
||||||
|
created_at: "2026-06-07T18:30:00"
|
||||||
|
visibility: private
|
||||||
|
shared_read: []
|
||||||
|
shared_edit: []
|
||||||
|
comments: []
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: Only the journey owner may delete it
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Only the owner can delete this journey
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/JourneyNotFound"
|
||||||
|
|
||||||
|
/api/journeys/{journeyId}/comments:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/JourneyId"
|
||||||
|
get:
|
||||||
|
tags: [Comments]
|
||||||
|
summary: List journey comments
|
||||||
|
description: The current user must be able to view the journey.
|
||||||
|
operationId: listJourneyComments
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Journey comments
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Comment"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: The current user cannot view the journey
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Access denied
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/JourneyNotFound"
|
||||||
|
post:
|
||||||
|
tags: [Comments]
|
||||||
|
summary: Add a journey comment
|
||||||
|
description: The current user must be able to view the journey.
|
||||||
|
operationId: addJourneyComment
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CommentCreate"
|
||||||
|
example:
|
||||||
|
text: This looks like a wonderful trip.
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Comment created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Comment"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: The current user cannot view the journey
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Access denied
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/JourneyNotFound"
|
||||||
|
|
||||||
|
/api/comments/{commentId}:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/CommentId"
|
||||||
|
delete:
|
||||||
|
tags: [Comments]
|
||||||
|
summary: Delete a comment
|
||||||
|
description: A comment may be deleted by its author or the journey owner.
|
||||||
|
operationId: deleteComment
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Comment deleted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Message"
|
||||||
|
example:
|
||||||
|
message: Comment deleted
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
"403":
|
||||||
|
description: The current user cannot delete the comment
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Not authorized
|
||||||
|
"404":
|
||||||
|
description: Comment not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Comment not found
|
||||||
|
|
||||||
|
/api/uploads/images:
|
||||||
|
post:
|
||||||
|
tags: [Uploads]
|
||||||
|
summary: Upload marker images
|
||||||
|
description: |
|
||||||
|
Uploads one or more images using repeated `images` form fields. Accepted
|
||||||
|
filename extensions are `.png`, `.jpg`, `.jpeg`, `.gif`, and `.webp`.
|
||||||
|
The returned image objects can be included in a marker's `images` array.
|
||||||
|
operationId: uploadImages
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [images]
|
||||||
|
properties:
|
||||||
|
images:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Images uploaded
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [images]
|
||||||
|
properties:
|
||||||
|
images:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Image"
|
||||||
|
"400":
|
||||||
|
description: No valid images were provided or an extension is unsupported
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
examples:
|
||||||
|
noImages:
|
||||||
|
value:
|
||||||
|
error: No images provided
|
||||||
|
unsupported:
|
||||||
|
value:
|
||||||
|
error: "Unsupported image type: notes.txt"
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/AuthenticationRequired"
|
||||||
|
|
||||||
|
/uploads/{filename}:
|
||||||
|
parameters:
|
||||||
|
- name: filename
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Server-generated image filename returned by the upload endpoint.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 79ce8a0727d846f4bffb1fbf94365191.jpg
|
||||||
|
get:
|
||||||
|
tags: [Uploads]
|
||||||
|
summary: Get an uploaded image
|
||||||
|
description: Uploaded images are publicly accessible.
|
||||||
|
operationId: getUploadedImage
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Image file
|
||||||
|
content:
|
||||||
|
image/png:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
image/jpeg:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
image/gif:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
image/webp:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
"404":
|
||||||
|
description: Image not found
|
||||||
|
|
||||||
|
/api/journeys/health:
|
||||||
|
get:
|
||||||
|
tags: [System]
|
||||||
|
summary: Check API health
|
||||||
|
operationId: getHealth
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Backend is healthy
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Health"
|
||||||
|
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
tags: [System]
|
||||||
|
summary: Get the API landing page
|
||||||
|
description: Returns a small HTML page confirming that the backend is running.
|
||||||
|
operationId: getApiLandingPage
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: API landing page
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
cookieAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: cookie
|
||||||
|
name: session
|
||||||
|
description: Flask session cookie returned after registration or login.
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
JourneyId:
|
||||||
|
name: journeyId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Journey ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
CommentId:
|
||||||
|
name: commentId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Millisecond timestamp used as the comment ID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
minimum: 1
|
||||||
|
|
||||||
|
responses:
|
||||||
|
ValidationError:
|
||||||
|
description: Request validation failed
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
examples:
|
||||||
|
missingTitle:
|
||||||
|
value:
|
||||||
|
error: Journey title is required
|
||||||
|
invalidCoordinates:
|
||||||
|
value:
|
||||||
|
error: Marker latitude must be between -90 and 90
|
||||||
|
AuthenticationRequired:
|
||||||
|
description: Authentication is required
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Authentication required
|
||||||
|
NotLoggedIn:
|
||||||
|
description: There is no valid current user session
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
examples:
|
||||||
|
noSession:
|
||||||
|
value:
|
||||||
|
error: Not logged in
|
||||||
|
missingUser:
|
||||||
|
value:
|
||||||
|
error: User not found
|
||||||
|
JourneyNotFound:
|
||||||
|
description: Journey not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
example:
|
||||||
|
error: Journey not found
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
required: [error]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Message:
|
||||||
|
type: object
|
||||||
|
required: [message]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
required: [id, username]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
maxLength: 50
|
||||||
|
|
||||||
|
AuthRequest:
|
||||||
|
type: object
|
||||||
|
required: [username, password]
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 50
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
minLength: 4
|
||||||
|
maxLength: 200
|
||||||
|
|
||||||
|
AuthResponse:
|
||||||
|
type: object
|
||||||
|
required: [id, username, message]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
maxLength: 50
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Image:
|
||||||
|
type: object
|
||||||
|
required: [filename, originalName, url]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
filename:
|
||||||
|
type: string
|
||||||
|
maxLength: 255
|
||||||
|
description: Server-generated filename.
|
||||||
|
originalName:
|
||||||
|
type: string
|
||||||
|
maxLength: 255
|
||||||
|
description: Sanitized original filename.
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
maxLength: 2048
|
||||||
|
description: Relative or absolute image URL.
|
||||||
|
example: /uploads/79ce8a0727d846f4bffb1fbf94365191.jpg
|
||||||
|
|
||||||
|
MarkerImageInput:
|
||||||
|
description: A marker image may be supplied as a URL string or a full image object.
|
||||||
|
oneOf:
|
||||||
|
- type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 2048
|
||||||
|
- $ref: "#/components/schemas/Image"
|
||||||
|
|
||||||
|
Marker:
|
||||||
|
type: object
|
||||||
|
required: [lat, lng, title, date, description, images]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
lat:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: -90
|
||||||
|
maximum: 90
|
||||||
|
lng:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: -180
|
||||||
|
maximum: 180
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
maxLength: 200
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
maxLength: 20
|
||||||
|
description: Date text, normally formatted as `YYYY-MM-DD`.
|
||||||
|
example: "2026-06-07"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 10000
|
||||||
|
description: Markdown-supported marker description.
|
||||||
|
images:
|
||||||
|
type: array
|
||||||
|
maxItems: 20
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarkerImageInput"
|
||||||
|
|
||||||
|
MarkerInput:
|
||||||
|
type: object
|
||||||
|
required: [lat, lng]
|
||||||
|
properties:
|
||||||
|
lat:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: -90
|
||||||
|
maximum: 90
|
||||||
|
lng:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: -180
|
||||||
|
maximum: 180
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
maxLength: 200
|
||||||
|
default: ""
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
maxLength: 20
|
||||||
|
default: ""
|
||||||
|
example: "2026-06-07"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 10000
|
||||||
|
default: ""
|
||||||
|
description: Markdown-supported marker description.
|
||||||
|
images:
|
||||||
|
type: array
|
||||||
|
maxItems: 20
|
||||||
|
default: []
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarkerImageInput"
|
||||||
|
|
||||||
|
Comment:
|
||||||
|
type: object
|
||||||
|
required: [id, author_id, author_name, text, created_at]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
minimum: 1
|
||||||
|
author_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
author_name:
|
||||||
|
type: string
|
||||||
|
maxLength: 50
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 2000
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
CommentCreate:
|
||||||
|
type: object
|
||||||
|
required: [text]
|
||||||
|
properties:
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 2000
|
||||||
|
|
||||||
|
Visibility:
|
||||||
|
type: string
|
||||||
|
enum: [private, public, shared]
|
||||||
|
default: private
|
||||||
|
|
||||||
|
Journey:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- owner_id
|
||||||
|
- title
|
||||||
|
- description
|
||||||
|
- markers
|
||||||
|
- created_at
|
||||||
|
- visibility
|
||||||
|
- shared_read
|
||||||
|
- shared_edit
|
||||||
|
- comments
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
owner_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 200
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 20000
|
||||||
|
description: Markdown-supported journey description.
|
||||||
|
markers:
|
||||||
|
type: array
|
||||||
|
maxItems: 500
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Marker"
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
visibility:
|
||||||
|
$ref: "#/components/schemas/Visibility"
|
||||||
|
shared_read:
|
||||||
|
type: array
|
||||||
|
uniqueItems: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
shared_edit:
|
||||||
|
type: array
|
||||||
|
uniqueItems: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
comments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Comment"
|
||||||
|
|
||||||
|
JourneyListItem:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/Journey"
|
||||||
|
- type: object
|
||||||
|
required: [can_edit]
|
||||||
|
properties:
|
||||||
|
can_edit:
|
||||||
|
type: boolean
|
||||||
|
description: Whether the current user may edit this journey.
|
||||||
|
|
||||||
|
JourneyCreate:
|
||||||
|
type: object
|
||||||
|
required: [title]
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 200
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 20000
|
||||||
|
default: ""
|
||||||
|
description: Markdown-supported journey description.
|
||||||
|
markers:
|
||||||
|
type: array
|
||||||
|
maxItems: 500
|
||||||
|
default: []
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarkerInput"
|
||||||
|
visibility:
|
||||||
|
$ref: "#/components/schemas/Visibility"
|
||||||
|
shared_read:
|
||||||
|
type: array
|
||||||
|
default: []
|
||||||
|
description: Invalid, duplicate, and unknown user IDs are silently removed.
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
shared_edit:
|
||||||
|
type: array
|
||||||
|
default: []
|
||||||
|
description: Invalid, duplicate, and unknown user IDs are silently removed.
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
|
||||||
|
JourneyUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 200
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 20000
|
||||||
|
description: Markdown-supported journey description.
|
||||||
|
markers:
|
||||||
|
type: array
|
||||||
|
maxItems: 500
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarkerInput"
|
||||||
|
visibility:
|
||||||
|
$ref: "#/components/schemas/Visibility"
|
||||||
|
shared_read:
|
||||||
|
type: array
|
||||||
|
description: Owner-only field. Invalid, duplicate, and unknown user IDs are silently removed.
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
shared_edit:
|
||||||
|
type: array
|
||||||
|
description: Owner-only field. Invalid, duplicate, and unknown user IDs are silently removed.
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
|
||||||
|
Health:
|
||||||
|
type: object
|
||||||
|
required: [status, timestamp]
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
enum: [healthy]
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
security:
|
||||||
|
- cookieAuth: []
|
||||||
Loading…
x
Reference in New Issue
Block a user