Files
MusicServer/cmd/docs/swagger.yaml
T
Sansan 090e794460 Restructure backend into services and fix security, crash and sync issues
Security
- Serve character images through os.Root so names like ../ can't escape
  the characters folder
- Sync start and /sync/reset are POST only (legacy and /api/v1)
- /api/v1/token/cleanup requires a token, password compared in constant
  time, tokens no longer logged
- Echo's rate limiter replaces the custom one; TRUSTED_PROXIES enables
  X-Forwarded-For for reverse proxies

Crashes and races
- Sync state lives in SyncService with atomics and a mutex instead of
  unsynchronised globals (fixes concurrent map writes during full sync)
- Only one sync can run; /api/v1 sync and music routes are locked during
  sync like the legacy ones
- No log.Fatal outside startup: errors become 4xx/5xx responses
- Search uses an in-order character match instead of unescaped regexes
- Empty queue / no songs return 404 instead of panicking or looping

Behaviour
- Sync result is computed once and stays the same between polls
- Swagger and /openapi serve the embedded spec
- Shutdown stops HTTP before closing the database
- Expired tokens are deleted with a context that outlives the request
- Files are served with Range support and correct content types; audio
  has no write deadline

Performance
- Migration 000006 adds soundtrack.fingerprint (names, sizes, mtimes) so
  unchanged folders are skipped without reading any files
- Songs are reconciled per soundtrack with one query plus needed writes;
  renames keep play counts
- Moved MUSIC_PATH updates soundtrack and song paths
- .id cleanup only runs for changed soundtracks or a full sync
- Classic random uses one SQL query; Gitea release lookups are cached
  with a timeout

Structure
- internal/config reads all environment variables once
- sqlc emits a Querier interface; backend globals become services
  injected into handlers
- One route registration shared by legacy and /api/v1
- Removed dead code (dbHelper.go, unused middleware, duplicate Gitea
  functions, embedded Swagger UI copy) and lib/pq
- templ and swag pinned as go tools
2026-10-06 15:20:14 +02:00

998 lines
24 KiB
YAML

basePath: /
definitions:
backend.SongInfo:
properties:
CurrentlyPlaying:
type: boolean
Song:
type: string
SongNo:
type: integer
SongPlayed:
type: integer
Soundtrack:
type: string
SoundtrackPlayed:
type: integer
type: object
backend.SongInfoForStats:
properties:
file_name:
type: string
path:
type: string
song_name:
type: string
soundtrack_id:
type: integer
soundtrack_name:
type: string
times_played:
type: integer
type: object
backend.SoundtrackWithSongs:
properties:
songs:
items:
$ref: '#/definitions/backend.SongInfoForStats'
type: array
soundtrack_id:
type: integer
soundtrack_last_played:
type: string
soundtrack_name:
type: string
soundtrack_played:
type: integer
type: object
backend.StatisticsSummary:
properties:
avg_soundtrack_plays:
type: number
max_soundtrack_plays:
type: integer
min_soundtrack_plays:
type: integer
never_played_soundtracks:
type: integer
played_soundtracks:
type: integer
total_soundtrack_plays:
type: integer
total_soundtracks:
type: integer
type: object
backend.SyncResponse:
properties:
catched_errors:
items:
type: string
type: array
soundtracks_added:
items:
type: string
type: array
soundtracks_changed_content:
items:
type: string
type: array
soundtracks_changed_title:
additionalProperties:
type: string
type: object
soundtracks_re_added:
items:
type: string
type: array
soundtracks_removed:
items:
type: string
type: array
total_time:
type: string
type: object
backend.VersionData:
properties:
changelog:
example:
- '["Initial release"'
- '"Bug fixes"]'
items:
type: string
type: array
version:
example: 1.0.0
type: string
type: object
server.TokenRequest:
properties:
client_type:
description: 'Optional: "web", "mobile", "api"'
type: string
password:
description: API password, required when API_PASSWORD is set
type: string
type: object
server.TokenResponse:
properties:
client_type:
type: string
expires_at:
type: string
token:
type: string
type: object
info:
contact:
email: zarnor91@gmail.com
name: Sebastian Olsson
description: API for the MusicServer application
license:
name: MIT
url: http://opensource.org/licenses/MIT
termsOfService: http://sanplex.xyz/terms/
title: MusicServer API
version: "1.0"
paths:
/api/v1/statistics/songs/least-played:
get:
description: Returns the top N least played songs with their soundtrack info
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SongInfoForStats'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get least played songs
tags:
- statistics
/api/v1/statistics/songs/most-played:
get:
description: Returns the top N most played songs with their soundtrack info
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SongInfoForStats'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get most played songs
tags:
- statistics
/api/v1/statistics/soundtracks/last-played:
get:
description: Returns the most recently played soundtracks with their songs
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SoundtrackWithSongs'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get last played soundtracks
tags:
- statistics
/api/v1/statistics/soundtracks/least-played:
get:
description: Returns the top N least played soundtracks with their songs
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SoundtrackWithSongs'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get least played soundtracks
tags:
- statistics
/api/v1/statistics/soundtracks/most-played:
get:
description: Returns the top N most played soundtracks with their songs
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SoundtrackWithSongs'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get most played soundtracks
tags:
- statistics
/api/v1/statistics/soundtracks/never-played:
get:
description: Returns all soundtracks that have never been played
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SoundtrackWithSongs'
type: array
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get never played soundtracks
tags:
- statistics
/api/v1/statistics/soundtracks/oldest-played:
get:
description: Returns the least recently played soundtracks with their songs
parameters:
- description: 'Number of results (default: 10, max: 100)'
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SoundtrackWithSongs'
type: array
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get oldest played soundtracks
tags:
- statistics
/api/v1/statistics/summary:
get:
description: Returns overall play statistics
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.StatisticsSummary'
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Get statistics summary
tags:
- statistics
/api/v1/token:
delete:
description: Deletes the current session token
parameters:
- description: Bearer token
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Invalidate session token
tags:
- auth
post:
consumes:
- application/json
description: Returns a new session token for API access. Requires the API password
when one is configured.
parameters:
- description: Password and client type
in: body
name: request
required: true
schema:
$ref: '#/definitions/server.TokenRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/server.TokenResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
"429":
description: Too Many Requests
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Create session token
tags:
- auth
/api/v1/token/cleanup:
post:
description: Removes all expired session tokens from the database
parameters:
- description: Bearer token
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
"401":
description: Unauthorized
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Cleanup expired sessions
tags:
- auth
/character:
get:
description: Returns the image for a specific character
parameters:
- description: Character file name
in: query
name: name
required: true
type: string
produces:
- image/png
- image/jpeg
responses:
"200":
description: OK
schema:
type: file
"400":
description: invalid name
schema:
type: string
"404":
description: not found
schema:
type: string
summary: Get character image
tags:
- characters
/characters:
get:
description: Returns a list of all available characters
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
summary: Get list of characters
tags:
- characters
/download:
get:
description: Returns the latest version of the client application
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Check for latest version
tags:
- download
/download/linux:
get:
description: Redirects to download the latest Linux version
responses:
"302":
description: Found
schema:
type: string
"404":
description: not found
schema:
type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Download latest Linux version
tags:
- download
/download/list:
get:
description: Lists all assets available for the latest version
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"502":
description: upstream service unavailable
schema:
type: string
summary: List assets of latest version
tags:
- download
/download/windows:
get:
description: Redirects to download the latest Windows version
responses:
"302":
description: Found
schema:
type: string
"404":
description: not found
schema:
type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Download latest Windows version
tags:
- download
/health:
get:
description: Returns the health status of the server
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
summary: Check server health
tags:
- health
/music:
get:
description: Returns the song at the given position in the queue
parameters:
- description: Song number in the queue
in: query
name: song
required: true
type: integer
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"206":
description: Partial Content
schema:
type: file
"400":
description: song must be a number
schema:
type: string
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get a specific song
tags:
- music
/music/addPlayed:
get:
description: Marks the current song as played
responses:
"200":
description: OK
"423":
description: Syncing is in progress
schema:
type: string
summary: Add latest to played
tags:
- music
/music/addQue:
get:
description: Adds the latest song to the queue
responses:
"200":
description: OK
"423":
description: Syncing is in progress
schema:
type: string
summary: Add latest to queue
tags:
- music
/music/all/order:
get:
description: Returns a list of all soundtracks in order
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"423":
description: Syncing is in progress
schema:
type: string
summary: Get all soundtracks
tags:
- music
/music/all/random:
get:
description: Returns a list of all soundtracks in random order
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
type: string
type: array
"423":
description: Syncing is in progress
schema:
type: string
summary: Get all soundtracks random
tags:
- music
/music/info:
get:
description: Returns information about the current song
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.SongInfo'
summary: Get current song info
tags:
- music
/music/list:
get:
description: Returns a list of played songs
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.SongInfo'
type: array
summary: Get played songs list
tags:
- music
/music/next:
get:
description: Returns the next song in the queue
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get next song
tags:
- music
/music/played:
put:
description: Marks the song at the given queue position as played
parameters:
- description: Song number in the queue
in: query
name: song
required: true
type: integer
responses:
"200":
description: OK
"400":
description: Bad Request
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Mark song as played
tags:
- music
/music/previous:
get:
description: Returns the previous song in the queue
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get previous song
tags:
- music
/music/rand:
get:
description: Returns a random song
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get random song
tags:
- music
/music/rand/classic:
get:
description: Returns a random song where every song has the same chance
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get random classic song
tags:
- music
/music/rand/low:
get:
description: Returns a random song, favouring soundtracks that have been played
less
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get random song with low chance
tags:
- music
/music/reset:
get:
description: Resets the music state
responses:
"200":
description: OK
"423":
description: Syncing is in progress
schema:
type: string
summary: Reset music state
tags:
- music
/music/soundTest:
get:
description: Returns the sound check song
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"404":
description: Not Found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Get sound check song
tags:
- music
/sync:
post:
description: Starts syncing soundtracks that have changed
produces:
- application/json
responses:
"200":
description: Start syncing soundtracks
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Sync soundtracks with only changes
tags:
- sync
/sync/full:
post:
description: Starts a full sync of all soundtracks
produces:
- application/json
responses:
"200":
description: Start syncing soundtracks full
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Sync all soundtracks fully
tags:
- sync
/sync/progress:
get:
description: Returns the progress of the running sync, or the result of the
last sync
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.SyncResponse'
summary: Get sync progress
tags:
- sync
/sync/reset:
post:
description: Resets the soundtracks database by deleting all soundtracks and
songs
produces:
- application/json
responses:
"200":
description: Soundtracks and songs are deleted from the database
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Reset soundtracks database
tags:
- sync
/version:
get:
description: get latest version info
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.VersionData'
summary: Getting the latest version of the backend
tags:
- version
/version/history:
get:
description: get version history
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.VersionData'
type: array
summary: Getting the version history of the backend
tags:
- version
swagger: "2.0"