943 lines
26 KiB
YAML

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: []