|
|
openapi: 3.0.3
|
|
|
info:
|
|
|
title: Polotsk Transit API
|
|
|
version: 1.0.0-draft
|
|
|
description: |
|
|
|
Черновик OpenAPI 3.0, восстановленный по реальному коду проекта.
|
|
|
В проекте нет встроенного swagger/openapi генератора, поэтому часть схем описана
|
|
|
по фактическим SQL-запросам и runtime-поведению контроллеров.
|
|
|
servers:
|
|
|
- url: http://localhost:3000
|
|
|
description: Local backend
|
|
|
tags:
|
|
|
- name: System
|
|
|
- name: Auth
|
|
|
- name: Upload
|
|
|
- name: Routes
|
|
|
- name: Route Stops
|
|
|
- name: Schedules
|
|
|
- name: Holidays
|
|
|
- name: Stops
|
|
|
- name: ETA
|
|
|
- name: Sync
|
|
|
- name: Alerts
|
|
|
- name: Users
|
|
|
- name: Telemetry
|
|
|
- name: Realtime
|
|
|
paths:
|
|
|
/health:
|
|
|
get:
|
|
|
tags: [System]
|
|
|
summary: Health check
|
|
|
security: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Service status
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
required: [status, timestamp, version]
|
|
|
properties:
|
|
|
status:
|
|
|
type: string
|
|
|
example: healthy
|
|
|
timestamp:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
version:
|
|
|
type: string
|
|
|
example: 1.0.0
|
|
|
/api/v1/auth/login:
|
|
|
post:
|
|
|
tags: [Auth]
|
|
|
summary: Login
|
|
|
security: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Authenticated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/LoginResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/auth/refresh:
|
|
|
post:
|
|
|
tags: [Auth]
|
|
|
summary: Refresh access token
|
|
|
security: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/RefreshRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: New access token
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/RefreshResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/auth/logout:
|
|
|
post:
|
|
|
tags: [Auth]
|
|
|
summary: Logout by refresh token
|
|
|
security: []
|
|
|
requestBody:
|
|
|
required: false
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/LogoutRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Logged out
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/auth/me:
|
|
|
get:
|
|
|
tags: [Auth]
|
|
|
summary: Current user
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Current authenticated user
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UserMe'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/auth/change-password:
|
|
|
post:
|
|
|
tags: [Auth]
|
|
|
summary: Change password
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ChangePasswordRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Password changed
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/upload/avatar:
|
|
|
post:
|
|
|
tags: [Upload]
|
|
|
summary: Upload or replace current user avatar
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
multipart/form-data:
|
|
|
schema:
|
|
|
type: object
|
|
|
required: [avatar]
|
|
|
properties:
|
|
|
avatar:
|
|
|
type: string
|
|
|
format: binary
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Avatar uploaded
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/AvatarUploadResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Upload]
|
|
|
summary: Delete current user avatar
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Avatar deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/upload/avatar/{userId}:
|
|
|
delete:
|
|
|
tags: [Upload]
|
|
|
summary: Admin deletes any user avatar
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/UserId'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Avatar deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes:
|
|
|
get:
|
|
|
tags: [Routes]
|
|
|
summary: List active routes
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/RouteSummary'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Routes]
|
|
|
summary: Create route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateRouteRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Route created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Route'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{id}:
|
|
|
get:
|
|
|
tags: [Routes]
|
|
|
summary: Get route detail with stops
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route detail
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/RouteDetail'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
put:
|
|
|
tags: [Routes]
|
|
|
summary: Update route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateRouteRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Updated route
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Route'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Routes]
|
|
|
summary: Delete route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/stops:
|
|
|
get:
|
|
|
tags: [Route Stops]
|
|
|
summary: Get ordered stops for route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route stop list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/RouteStopView'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Route Stops]
|
|
|
summary: Add stop to route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/AddRouteStopRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Route stop created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/RouteStopRecord'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/available-stops:
|
|
|
get:
|
|
|
tags: [Route Stops]
|
|
|
summary: Stops not yet assigned to route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Available stops
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/AvailableStop'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/stops/{routeStopId}:
|
|
|
put:
|
|
|
tags: [Route Stops]
|
|
|
summary: Update route stop position or offset
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
- $ref: '#/components/parameters/RouteStopId'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateRouteStopRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Updated route stop
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/RouteStopRecord'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Route Stops]
|
|
|
summary: Remove stop from route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
- $ref: '#/components/parameters/RouteStopId'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route stop deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/stops-reorder:
|
|
|
put:
|
|
|
tags: [Route Stops]
|
|
|
summary: Bulk reorder route stops
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ReorderRouteStopsRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route stops reordered
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
required: [message, count]
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
count:
|
|
|
type: integer
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/schedules:
|
|
|
get:
|
|
|
tags: [Schedules]
|
|
|
summary: List route schedules
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Schedules for route
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/ScheduleWithRoute'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/schedule/today:
|
|
|
get:
|
|
|
tags: [Schedules]
|
|
|
summary: Get active schedule for date
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
- in: query
|
|
|
name: date
|
|
|
schema:
|
|
|
type: string
|
|
|
format: date
|
|
|
required: false
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Schedule for date
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/TodayScheduleResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/routes/{routeId}/schedules/copy:
|
|
|
post:
|
|
|
tags: [Schedules]
|
|
|
summary: Copy schedule from one day type to another
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/RouteId'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CopyScheduleRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Existing schedule updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
'201':
|
|
|
description: New schedule created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/schedules:
|
|
|
post:
|
|
|
tags: [Schedules]
|
|
|
summary: Create schedule
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateScheduleRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Schedule created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'409':
|
|
|
description: Schedule already exists
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
properties:
|
|
|
error:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
existingId:
|
|
|
type: integer
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/schedules/{id}:
|
|
|
get:
|
|
|
tags: [Schedules]
|
|
|
summary: Get schedule by id
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Schedule detail
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ScheduleWithRoute'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
put:
|
|
|
tags: [Schedules]
|
|
|
summary: Update schedule
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateScheduleRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Schedule updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Schedules]
|
|
|
summary: Delete schedule
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Schedule deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/holidays:
|
|
|
get:
|
|
|
tags: [Holidays]
|
|
|
summary: List holidays
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: year
|
|
|
schema:
|
|
|
type: integer
|
|
|
required: false
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Holiday list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Holiday'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Holidays]
|
|
|
summary: Create holiday
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateHolidayRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Holiday created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Holiday'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/holidays/upcoming:
|
|
|
get:
|
|
|
tags: [Holidays]
|
|
|
summary: Get upcoming holidays
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: limit
|
|
|
schema:
|
|
|
type: integer
|
|
|
required: false
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Upcoming holidays
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/UpcomingHoliday'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/holidays/year/{year}:
|
|
|
get:
|
|
|
tags: [Holidays]
|
|
|
summary: Get holidays for year with resolved recurring dates
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: path
|
|
|
name: year
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Holidays for year
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/HolidayForYear'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/holidays/check/{date}:
|
|
|
get:
|
|
|
tags: [Holidays]
|
|
|
summary: Check if date is holiday
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: path
|
|
|
name: date
|
|
|
required: true
|
|
|
schema:
|
|
|
type: string
|
|
|
format: date
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Holiday check
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
required: [date, isHoliday, holidayName]
|
|
|
properties:
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
isHoliday:
|
|
|
type: boolean
|
|
|
holidayName:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/holidays/{id}:
|
|
|
put:
|
|
|
tags: [Holidays]
|
|
|
summary: Update holiday
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateHolidayRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Holiday updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Holiday'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Holidays]
|
|
|
summary: Delete holiday
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Holiday deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/stops:
|
|
|
get:
|
|
|
tags: [Stops]
|
|
|
summary: List active stops
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Stop list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/StopSummary'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Stops]
|
|
|
summary: Create stop
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateStopRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Stop created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Stop'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/stops/nearby:
|
|
|
get:
|
|
|
tags: [Stops]
|
|
|
summary: Find nearby stops
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: lat
|
|
|
required: true
|
|
|
schema:
|
|
|
type: number
|
|
|
- in: query
|
|
|
name: lon
|
|
|
required: true
|
|
|
schema:
|
|
|
type: number
|
|
|
- in: query
|
|
|
name: radius
|
|
|
required: false
|
|
|
schema:
|
|
|
type: number
|
|
|
default: 500
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Nearby stops
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Stop'
|
|
|
- type: object
|
|
|
properties:
|
|
|
distance_meters:
|
|
|
type: number
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/stops/{id}:
|
|
|
get:
|
|
|
tags: [Stops]
|
|
|
summary: Get stop detail
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Stop detail
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/StopDetail'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
put:
|
|
|
tags: [Stops]
|
|
|
summary: Update stop
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateStopRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Stop updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Stop'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Stops]
|
|
|
summary: Delete stop
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Stop deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/eta/calculate:
|
|
|
post:
|
|
|
tags: [ETA]
|
|
|
summary: Calculate ETA for route+stop
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CalculateEtaRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: ETA calculated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
oneOf:
|
|
|
- $ref: '#/components/schemas/CalculateEtaResponse'
|
|
|
- type: object
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
nextDay:
|
|
|
type: string
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/eta/stop/{stopId}:
|
|
|
get:
|
|
|
tags: [ETA]
|
|
|
summary: ETA for all routes at stop
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: path
|
|
|
name: stopId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
responses:
|
|
|
'200':
|
|
|
description: ETA list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/StopEtasResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/sync:
|
|
|
get:
|
|
|
tags: [Sync]
|
|
|
summary: Full or incremental sync
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: lastSync
|
|
|
required: false
|
|
|
schema:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Sync payload
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
oneOf:
|
|
|
- $ref: '#/components/schemas/FullSyncResponse'
|
|
|
- $ref: '#/components/schemas/IncrementalSyncResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/sync/status:
|
|
|
get:
|
|
|
tags: [Sync]
|
|
|
summary: Sync status
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Status payload
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/SyncStatusResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/alerts:
|
|
|
get:
|
|
|
tags: [Alerts]
|
|
|
summary: List alerts
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: route_id
|
|
|
required: false
|
|
|
schema:
|
|
|
type: integer
|
|
|
- in: query
|
|
|
name: is_active
|
|
|
required: false
|
|
|
schema:
|
|
|
type: boolean
|
|
|
- in: query
|
|
|
name: alert_type
|
|
|
required: false
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/AlertType'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Alert list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/AlertRead'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Alerts]
|
|
|
summary: Create alert
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateAlertRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Alert created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Alert'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/alerts/active:
|
|
|
get:
|
|
|
tags: [Alerts]
|
|
|
summary: List currently active alerts
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: query
|
|
|
name: route_id
|
|
|
required: false
|
|
|
schema:
|
|
|
type: integer
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Active alerts
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/AlertRead'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/alerts/cleanup:
|
|
|
post:
|
|
|
tags: [Alerts]
|
|
|
summary: Deactivate expired alerts
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Cleanup stats
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
required: [message, deactivated_count]
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
deactivated_count:
|
|
|
type: integer
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/alerts/{id}:
|
|
|
get:
|
|
|
tags: [Alerts]
|
|
|
summary: Get alert by id
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Alert detail
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/AlertRead'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
put:
|
|
|
tags: [Alerts]
|
|
|
summary: Update alert
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateAlertRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Alert updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/Alert'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Alerts]
|
|
|
summary: Delete alert
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Alert deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/users:
|
|
|
get:
|
|
|
tags: [Users]
|
|
|
summary: List users
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
responses:
|
|
|
'200':
|
|
|
description: User list
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/UserAdminView'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
post:
|
|
|
tags: [Users]
|
|
|
summary: Create user
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/CreateUserRequest'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: User created
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UserCreated'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/users/{id}:
|
|
|
get:
|
|
|
tags: [Users]
|
|
|
summary: Get user by id
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: User detail
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UserAdminView'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
put:
|
|
|
tags: [Users]
|
|
|
summary: Update user
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UpdateUserRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: User updated
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/UserUpdated'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
delete:
|
|
|
tags: [Users]
|
|
|
summary: Delete user
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: User deleted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/users/{id}/reset-password:
|
|
|
post:
|
|
|
tags: [Users]
|
|
|
summary: Reset user password
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
parameters:
|
|
|
- $ref: '#/components/parameters/Id'
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ResetPasswordRequest'
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Password reset
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/telemetry/ingest:
|
|
|
post:
|
|
|
tags: [Telemetry]
|
|
|
summary: Ingest GPS telemetry
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
requestBody:
|
|
|
required: true
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/TelemetryIngestRequest'
|
|
|
example:
|
|
|
routeId: 7
|
|
|
vehicleNumber: DEMO-7A
|
|
|
vehicleType: bus
|
|
|
lat: 55.4869
|
|
|
lon: 28.7856
|
|
|
speedKmh: 24
|
|
|
heading: 90
|
|
|
accuracyMeters: 5
|
|
|
recordedAt: '2026-04-16T10:30:00.000Z'
|
|
|
responses:
|
|
|
'201':
|
|
|
description: Telemetry accepted
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/TelemetryIngestResponse'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/realtime/routes/{routeId}/vehicles:
|
|
|
get:
|
|
|
tags: [Realtime]
|
|
|
summary: Live vehicles snapshot for route
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: path
|
|
|
name: routeId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
- in: query
|
|
|
name: includeStale
|
|
|
schema:
|
|
|
type: boolean
|
|
|
- in: query
|
|
|
name: staleAfterSeconds
|
|
|
schema:
|
|
|
type: integer
|
|
|
default: 180
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Route live vehicles
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/LiveVehicleState'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
/api/v1/realtime/vehicles/{vehicleId}:
|
|
|
get:
|
|
|
tags: [Realtime]
|
|
|
summary: Live vehicle state
|
|
|
security:
|
|
|
- BearerAuth: []
|
|
|
- ApiKeyAuth: []
|
|
|
parameters:
|
|
|
- in: path
|
|
|
name: vehicleId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
- in: query
|
|
|
name: staleAfterSeconds
|
|
|
schema:
|
|
|
type: integer
|
|
|
default: 180
|
|
|
responses:
|
|
|
'200':
|
|
|
description: Vehicle live state
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/LiveVehicleState'
|
|
|
'400':
|
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
'401':
|
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
'403':
|
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
'404':
|
|
|
$ref: '#/components/responses/NotFound'
|
|
|
'429':
|
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
'500':
|
|
|
$ref: '#/components/responses/InternalError'
|
|
|
components:
|
|
|
securitySchemes:
|
|
|
ApiKeyAuth:
|
|
|
type: apiKey
|
|
|
in: header
|
|
|
name: X-API-Key
|
|
|
BearerAuth:
|
|
|
type: http
|
|
|
scheme: bearer
|
|
|
bearerFormat: JWT
|
|
|
parameters:
|
|
|
Id:
|
|
|
in: path
|
|
|
name: id
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
RouteId:
|
|
|
in: path
|
|
|
name: routeId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
RouteStopId:
|
|
|
in: path
|
|
|
name: routeStopId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
UserId:
|
|
|
in: path
|
|
|
name: userId
|
|
|
required: true
|
|
|
schema:
|
|
|
type: integer
|
|
|
responses:
|
|
|
BadRequest:
|
|
|
description: Bad request
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
Unauthorized:
|
|
|
description: Authentication required or invalid credentials
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
Forbidden:
|
|
|
description: Forbidden
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
NotFound:
|
|
|
description: Not found
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
TooManyRequests:
|
|
|
description: Too many requests
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
type: object
|
|
|
properties:
|
|
|
error:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
retryAfter:
|
|
|
type: integer
|
|
|
InternalError:
|
|
|
description: Internal server error
|
|
|
content:
|
|
|
application/json:
|
|
|
schema:
|
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
schemas:
|
|
|
ErrorResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
error:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
MessageResponse:
|
|
|
type: object
|
|
|
required: [message]
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
RouteType:
|
|
|
type: string
|
|
|
enum: [bus, minibus, trolleybus, tram]
|
|
|
ScheduleDayType:
|
|
|
type: string
|
|
|
enum: [weekday, saturday, sunday, holiday]
|
|
|
AlertType:
|
|
|
type: string
|
|
|
enum: [delay, cancellation, detour, info]
|
|
|
UserRole:
|
|
|
type: string
|
|
|
enum: [admin, user]
|
|
|
Route:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
route_number:
|
|
|
type: string
|
|
|
name:
|
|
|
type: string
|
|
|
type:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
color:
|
|
|
type: string
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
description:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
updated_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
RouteSummary:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Route'
|
|
|
- type: object
|
|
|
properties:
|
|
|
stops_count:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
Stop:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
name:
|
|
|
type: string
|
|
|
location:
|
|
|
description: Raw PostGIS geography serialization; exact shape is not guaranteed
|
|
|
nullable: true
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
address:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
description:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
updated_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
StopSummary:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Stop'
|
|
|
- type: object
|
|
|
properties:
|
|
|
routes_count:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
StopOnRoute:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Stop'
|
|
|
- type: object
|
|
|
properties:
|
|
|
sequence:
|
|
|
type: integer
|
|
|
time_offset_minutes:
|
|
|
type: integer
|
|
|
RouteDetail:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Route'
|
|
|
- type: object
|
|
|
properties:
|
|
|
stops:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/StopOnRoute'
|
|
|
RouteAtStop:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Route'
|
|
|
- type: object
|
|
|
properties:
|
|
|
sequence:
|
|
|
type: integer
|
|
|
time_offset_minutes:
|
|
|
type: integer
|
|
|
StopDetail:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Stop'
|
|
|
- type: object
|
|
|
properties:
|
|
|
routes:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/RouteAtStop'
|
|
|
RouteStopRecord:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
route_id:
|
|
|
type: integer
|
|
|
stop_id:
|
|
|
type: integer
|
|
|
sequence:
|
|
|
type: integer
|
|
|
time_offset_minutes:
|
|
|
type: integer
|
|
|
RouteStopView:
|
|
|
type: object
|
|
|
properties:
|
|
|
route_stop_id:
|
|
|
type: integer
|
|
|
sequence:
|
|
|
type: integer
|
|
|
time_offset_minutes:
|
|
|
type: integer
|
|
|
stop_id:
|
|
|
type: integer
|
|
|
name:
|
|
|
type: string
|
|
|
address:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
AvailableStop:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
name:
|
|
|
type: string
|
|
|
address:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
Schedule:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
route_id:
|
|
|
type: integer
|
|
|
day_type:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
departure_times:
|
|
|
type: array
|
|
|
items:
|
|
|
type: string
|
|
|
valid_from:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
valid_until:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
updated_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
ScheduleWithRoute:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Schedule'
|
|
|
- type: object
|
|
|
properties:
|
|
|
route_number:
|
|
|
type: string
|
|
|
route_name:
|
|
|
type: string
|
|
|
TodayScheduleResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
scheduleType:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
isHoliday:
|
|
|
type: boolean
|
|
|
holidayName:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
schedule:
|
|
|
$ref: '#/components/schemas/ScheduleWithRoute'
|
|
|
Holiday:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
name:
|
|
|
type: string
|
|
|
is_recurring:
|
|
|
type: boolean
|
|
|
recurring_month:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
recurring_day:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
HolidayForYear:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
name:
|
|
|
type: string
|
|
|
is_recurring:
|
|
|
type: boolean
|
|
|
UpcomingHoliday:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
name:
|
|
|
type: string
|
|
|
next_date:
|
|
|
type: string
|
|
|
format: date
|
|
|
is_recurring:
|
|
|
type: boolean
|
|
|
Alert:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
route_id:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
alert_type:
|
|
|
$ref: '#/components/schemas/AlertType'
|
|
|
title:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
start_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
end_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
AlertRead:
|
|
|
allOf:
|
|
|
- $ref: '#/components/schemas/Alert'
|
|
|
- type: object
|
|
|
properties:
|
|
|
route_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
route_number:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
color:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
UserLoginPayload:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
full_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
email:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
last_login:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
updated_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
avatar_url:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
UserMe:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
full_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
email:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
avatar_url:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
last_login:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
UserAdminView:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
full_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
email:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
last_login:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
UserCreated:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
full_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
email:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
created_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
UserUpdated:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
full_name:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
email:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
Arrival:
|
|
|
type: object
|
|
|
properties:
|
|
|
scheduledTime:
|
|
|
type: string
|
|
|
estimatedTime:
|
|
|
type: string
|
|
|
minutesUntil:
|
|
|
type: integer
|
|
|
delay:
|
|
|
type: integer
|
|
|
CalculateEtaResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
routeId:
|
|
|
type: integer
|
|
|
stopId:
|
|
|
type: integer
|
|
|
currentTime:
|
|
|
type: string
|
|
|
dayType:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
arrivals:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Arrival'
|
|
|
StopEtasRoute:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
route_number:
|
|
|
type: string
|
|
|
name:
|
|
|
type: string
|
|
|
type:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
color:
|
|
|
type: string
|
|
|
arrivals:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Arrival'
|
|
|
error:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
StopEtasResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
stopId:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
routes:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/StopEtasRoute'
|
|
|
FullSyncResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
syncType:
|
|
|
type: string
|
|
|
enum: [full]
|
|
|
timestamp:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
version:
|
|
|
type: string
|
|
|
data:
|
|
|
type: object
|
|
|
properties:
|
|
|
routes:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Route'
|
|
|
stops:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Stop'
|
|
|
routeStops:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/RouteStopRecord'
|
|
|
schedules:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
alerts:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Alert'
|
|
|
holidays:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Holiday'
|
|
|
metadata:
|
|
|
type: object
|
|
|
properties:
|
|
|
routesCount:
|
|
|
type: integer
|
|
|
stopsCount:
|
|
|
type: integer
|
|
|
schedulesCount:
|
|
|
type: integer
|
|
|
holidaysCount:
|
|
|
type: integer
|
|
|
IncrementalSyncResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
syncType:
|
|
|
type: string
|
|
|
enum: [incremental]
|
|
|
timestamp:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
lastSync:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
changes:
|
|
|
type: object
|
|
|
properties:
|
|
|
routes:
|
|
|
$ref: '#/components/schemas/SyncChangeBucketRoutes'
|
|
|
stops:
|
|
|
$ref: '#/components/schemas/SyncChangeBucketStops'
|
|
|
routeStops:
|
|
|
$ref: '#/components/schemas/SyncChangeBucketRouteStops'
|
|
|
schedules:
|
|
|
$ref: '#/components/schemas/SyncChangeBucketSchedules'
|
|
|
alerts:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Alert'
|
|
|
metadata:
|
|
|
type: object
|
|
|
properties:
|
|
|
changesCount:
|
|
|
type: integer
|
|
|
SyncChangeBucketRoutes:
|
|
|
type: object
|
|
|
properties:
|
|
|
updated:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Route'
|
|
|
deleted:
|
|
|
type: array
|
|
|
items:
|
|
|
type: integer
|
|
|
SyncChangeBucketStops:
|
|
|
type: object
|
|
|
properties:
|
|
|
updated:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Stop'
|
|
|
deleted:
|
|
|
type: array
|
|
|
items:
|
|
|
type: integer
|
|
|
SyncChangeBucketRouteStops:
|
|
|
type: object
|
|
|
properties:
|
|
|
updated:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/RouteStopRecord'
|
|
|
deleted:
|
|
|
type: array
|
|
|
items:
|
|
|
type: integer
|
|
|
SyncChangeBucketSchedules:
|
|
|
type: object
|
|
|
properties:
|
|
|
updated:
|
|
|
type: array
|
|
|
items:
|
|
|
$ref: '#/components/schemas/Schedule'
|
|
|
deleted:
|
|
|
type: array
|
|
|
items:
|
|
|
type: integer
|
|
|
SyncStatusResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
status:
|
|
|
type: string
|
|
|
timestamp:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
database:
|
|
|
type: object
|
|
|
properties:
|
|
|
routes_count:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
stops_count:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
schedules_count:
|
|
|
oneOf:
|
|
|
- type: integer
|
|
|
- type: string
|
|
|
last_change:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
LoginRequest:
|
|
|
type: object
|
|
|
required: [username, password]
|
|
|
properties:
|
|
|
username:
|
|
|
type: string
|
|
|
password:
|
|
|
type: string
|
|
|
LoginResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
user:
|
|
|
$ref: '#/components/schemas/UserLoginPayload'
|
|
|
accessToken:
|
|
|
type: string
|
|
|
refreshToken:
|
|
|
type: string
|
|
|
RefreshRequest:
|
|
|
type: object
|
|
|
required: [refreshToken]
|
|
|
properties:
|
|
|
refreshToken:
|
|
|
type: string
|
|
|
RefreshResponse:
|
|
|
type: object
|
|
|
required: [accessToken]
|
|
|
properties:
|
|
|
accessToken:
|
|
|
type: string
|
|
|
LogoutRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
refreshToken:
|
|
|
type: string
|
|
|
ChangePasswordRequest:
|
|
|
type: object
|
|
|
required: [currentPassword, newPassword]
|
|
|
properties:
|
|
|
currentPassword:
|
|
|
type: string
|
|
|
newPassword:
|
|
|
type: string
|
|
|
minLength: 6
|
|
|
refreshToken:
|
|
|
type: string
|
|
|
CreateRouteRequest:
|
|
|
type: object
|
|
|
required: [route_number, name, type]
|
|
|
properties:
|
|
|
route_number:
|
|
|
type: string
|
|
|
name:
|
|
|
type: string
|
|
|
type:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
color:
|
|
|
type: string
|
|
|
default: '#0066CC'
|
|
|
description:
|
|
|
type: string
|
|
|
UpdateRouteRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
route_number:
|
|
|
type: string
|
|
|
name:
|
|
|
type: string
|
|
|
type:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
color:
|
|
|
type: string
|
|
|
description:
|
|
|
type: string
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
CreateStopRequest:
|
|
|
type: object
|
|
|
required: [name, latitude, longitude]
|
|
|
properties:
|
|
|
name:
|
|
|
type: string
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
address:
|
|
|
type: string
|
|
|
description:
|
|
|
type: string
|
|
|
UpdateStopRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
name:
|
|
|
type: string
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
address:
|
|
|
type: string
|
|
|
description:
|
|
|
type: string
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
AddRouteStopRequest:
|
|
|
type: object
|
|
|
required: [stopId]
|
|
|
properties:
|
|
|
stopId:
|
|
|
type: integer
|
|
|
sequence:
|
|
|
type: integer
|
|
|
timeOffsetMinutes:
|
|
|
type: integer
|
|
|
default: 0
|
|
|
UpdateRouteStopRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
sequence:
|
|
|
type: integer
|
|
|
timeOffsetMinutes:
|
|
|
type: integer
|
|
|
ReorderRouteStopsRequest:
|
|
|
type: object
|
|
|
required: [stops]
|
|
|
properties:
|
|
|
stops:
|
|
|
type: array
|
|
|
items:
|
|
|
type: object
|
|
|
required: [stopId, sequence]
|
|
|
properties:
|
|
|
stopId:
|
|
|
type: integer
|
|
|
sequence:
|
|
|
type: integer
|
|
|
timeOffsetMinutes:
|
|
|
type: integer
|
|
|
CreateScheduleRequest:
|
|
|
type: object
|
|
|
required: [routeId, dayType, departureTimes]
|
|
|
properties:
|
|
|
routeId:
|
|
|
type: integer
|
|
|
dayType:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
departureTimes:
|
|
|
type: array
|
|
|
items:
|
|
|
type: string
|
|
|
validFrom:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
validUntil:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
UpdateScheduleRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
departureTimes:
|
|
|
type: array
|
|
|
items:
|
|
|
type: string
|
|
|
validFrom:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
validUntil:
|
|
|
type: string
|
|
|
format: date
|
|
|
nullable: true
|
|
|
CopyScheduleRequest:
|
|
|
type: object
|
|
|
required: [fromDayType, toDayType]
|
|
|
properties:
|
|
|
fromDayType:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
toDayType:
|
|
|
$ref: '#/components/schemas/ScheduleDayType'
|
|
|
CreateHolidayRequest:
|
|
|
type: object
|
|
|
required: [date, name]
|
|
|
properties:
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
name:
|
|
|
type: string
|
|
|
isRecurring:
|
|
|
type: boolean
|
|
|
UpdateHolidayRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
date:
|
|
|
type: string
|
|
|
format: date
|
|
|
name:
|
|
|
type: string
|
|
|
isRecurring:
|
|
|
type: boolean
|
|
|
CreateAlertRequest:
|
|
|
type: object
|
|
|
required: [alert_type, title, message, start_time]
|
|
|
properties:
|
|
|
route_id:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
alert_type:
|
|
|
$ref: '#/components/schemas/AlertType'
|
|
|
title:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
start_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
end_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
UpdateAlertRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
route_id:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
alert_type:
|
|
|
$ref: '#/components/schemas/AlertType'
|
|
|
title:
|
|
|
type: string
|
|
|
message:
|
|
|
type: string
|
|
|
start_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
end_time:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
CreateUserRequest:
|
|
|
type: object
|
|
|
required: [username, password]
|
|
|
properties:
|
|
|
username:
|
|
|
type: string
|
|
|
password:
|
|
|
type: string
|
|
|
minLength: 6
|
|
|
full_name:
|
|
|
type: string
|
|
|
email:
|
|
|
type: string
|
|
|
format: email
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
UpdateUserRequest:
|
|
|
type: object
|
|
|
properties:
|
|
|
full_name:
|
|
|
type: string
|
|
|
email:
|
|
|
type: string
|
|
|
format: email
|
|
|
role:
|
|
|
$ref: '#/components/schemas/UserRole'
|
|
|
is_active:
|
|
|
type: boolean
|
|
|
ResetPasswordRequest:
|
|
|
type: object
|
|
|
required: [newPassword]
|
|
|
properties:
|
|
|
newPassword:
|
|
|
type: string
|
|
|
minLength: 6
|
|
|
AvatarUploadResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
avatar_url:
|
|
|
type: string
|
|
|
user:
|
|
|
type: object
|
|
|
properties:
|
|
|
id:
|
|
|
type: integer
|
|
|
username:
|
|
|
type: string
|
|
|
avatar_url:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
CalculateEtaRequest:
|
|
|
type: object
|
|
|
required: [stopId, routeId]
|
|
|
properties:
|
|
|
stopId:
|
|
|
type: integer
|
|
|
routeId:
|
|
|
type: integer
|
|
|
TelemetryIngestRequest:
|
|
|
type: object
|
|
|
required: [lat, lon]
|
|
|
properties:
|
|
|
vehicleId:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
vehicleNumber:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
routeId:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
vehicleType:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
lat:
|
|
|
type: number
|
|
|
minimum: -90
|
|
|
maximum: 90
|
|
|
lon:
|
|
|
type: number
|
|
|
minimum: -180
|
|
|
maximum: 180
|
|
|
speedKmh:
|
|
|
type: number
|
|
|
nullable: true
|
|
|
heading:
|
|
|
type: number
|
|
|
minimum: 0
|
|
|
maximum: 360
|
|
|
nullable: true
|
|
|
accuracyMeters:
|
|
|
type: number
|
|
|
nullable: true
|
|
|
recordedAt:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
nullable: true
|
|
|
sourceType:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
sourceRef:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
LiveVehicleState:
|
|
|
type: object
|
|
|
properties:
|
|
|
vehicle_id:
|
|
|
type: integer
|
|
|
vehicle_number:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
registration:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
vehicle_type:
|
|
|
$ref: '#/components/schemas/RouteType'
|
|
|
capacity:
|
|
|
type: integer
|
|
|
nullable: true
|
|
|
route_id:
|
|
|
type: integer
|
|
|
route_number:
|
|
|
type: string
|
|
|
route_name:
|
|
|
type: string
|
|
|
route_color:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
latitude:
|
|
|
type: number
|
|
|
longitude:
|
|
|
type: number
|
|
|
speed_kmh:
|
|
|
type: number
|
|
|
nullable: true
|
|
|
heading:
|
|
|
type: number
|
|
|
nullable: true
|
|
|
accuracy_meters:
|
|
|
type: number
|
|
|
nullable: true
|
|
|
source_type:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
source_ref:
|
|
|
type: string
|
|
|
nullable: true
|
|
|
last_seen_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
updated_at:
|
|
|
type: string
|
|
|
format: date-time
|
|
|
is_online:
|
|
|
type: boolean
|
|
|
seconds_since_update:
|
|
|
type: integer
|
|
|
is_moving:
|
|
|
type: boolean
|
|
|
TelemetryIngestResponse:
|
|
|
type: object
|
|
|
properties:
|
|
|
message:
|
|
|
type: string
|
|
|
vehicle:
|
|
|
$ref: '#/components/schemas/LiveVehicleState'
|