feat(openapi): annotate track CRUD handlers + regen spec (v1.0.8 B-annot)
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s

First batch of the backend OpenAPI annotation campaign. Adds full
swaggo annotations to the 8 handlers in internal/core/track/track_crud_handler.go
so the resulting openapi.yaml exposes the track CRUD surface to
orval-generated frontend clients.

Handlers annotated (all under @Tags Track):
- ListTracks     — GET    /tracks
- GetTrack       — GET    /tracks/{id}
- UpdateTrack    — PUT    /tracks/{id}                  (Auth, ownership)
- GetLyrics      — GET    /tracks/{id}/lyrics
- UpdateLyrics   — PUT    /tracks/{id}/lyrics           (Auth, ownership)
- DeleteTrack    — DELETE /tracks/{id}                  (Auth, ownership)
- BatchDeleteTracks — POST /tracks/batch/delete         (Auth)
- BatchUpdateTracks — POST /tracks/batch/update         (Auth)

Each block follows the established pattern (auth.go + marketplace.go):
Summary / Description / Tags / Accept / Produce / Security when auth-required /
Param (path/query/body) with concrete types / Success envelope typed via
response.APIResponse{data=...} / Failure 400/401/403/404/500 / Router.

make openapi:  valid (Swagger 2.0)
go build ./...: 
openapi.yaml: +490 LOC, 8 new paths exposed under /tracks.

Part of the Option B campaign tracked in
/home/senke/.claude/plans/audit-fonctionnel-wild-hickey.md.
~364 handlers total remain unannotated across 16 files in /internal/core/
and ~55 files in /internal/handlers/. Subsequent commits will annotate
one handler file at a time so each regenerated spec stays bisectable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
senke 2026-04-24 00:45:10 +02:00
parent 7fd43ab609
commit 2aa2e6cd51
5 changed files with 2692 additions and 34 deletions

View file

@ -2349,6 +2349,106 @@ const docTemplate = `{
}
},
"/tracks": {
"get": {
"description": "List tracks with pagination, filters, sort. Cursor-based when ?cursor provided, otherwise page/limit offset.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "List tracks",
"parameters": [
{
"type": "string",
"description": "Opaque pagination cursor (overrides page)",
"name": "cursor",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "Page number, 1-based (ignored if cursor set)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 20,
"description": "Items per page (max 100)",
"name": "limit",
"in": "query"
},
{
"type": "string",
"description": "Filter by creator UUID",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"description": "Filter by genre",
"name": "genre",
"in": "query"
},
{
"type": "string",
"description": "Filter by audio format (mp3, flac, wav, ogg, m4a, aac)",
"name": "format",
"in": "query"
},
{
"type": "string",
"default": "created_at",
"description": "Sort column (created_at, play_count, title)",
"name": "sort_by",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"pagination": {
"type": "object"
},
"tracks": {
"type": "array",
"items": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid query params",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"post": {
"security": [
{
@ -2426,6 +2526,172 @@ const docTemplate = `{
}
}
},
"/tracks/batch/delete": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Soft-delete up to N tracks in one request. Per-track ownership checked (admin can delete others).",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Batch delete tracks",
"parameters": [
{
"description": "List of track UUIDs",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.BatchDeleteRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"deleted": {
"type": "array",
"items": {
"type": "string"
}
},
"failed": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / batch size exceeded",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/batch/update": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Apply a partial metadata update to up to N tracks in one request. Per-track ownership checked.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Batch update tracks",
"parameters": [
{
"description": "Track UUIDs + shared updates map",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.BatchUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"failed": {
"type": "array",
"items": {
"type": "object"
}
},
"updated": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / batch size exceeded",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/chunk": {
"post": {
"security": [
@ -2812,6 +3078,240 @@ const docTemplate = `{
}
}
},
"/tracks/{id}": {
"get": {
"description": "Retrieve a single track. Private play_count / like_count are omitted for non-owners (v0.10.3 F202).",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Get track by ID",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"track": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "Update the metadata of an existing track. Caller must own the track or be admin.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Update track metadata",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Updated metadata",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.UpdateTrackRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"track": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / invalid id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "Soft-delete a track (sets deleted_at). Caller must own the track or be admin.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Delete track",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/{id}/comments": {
"get": {
"description": "Get paginated list of comments for a track",
@ -3066,6 +3566,161 @@ const docTemplate = `{
}
}
},
"/tracks/{id}/lyrics": {
"get": {
"description": "Returns the current lyrics for a track, or null if no lyrics exist.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Get track lyrics",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "lyrics may be null",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"lyrics": {
"type": "object"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "Replace the lyrics of a track. Caller must own the track or be admin.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Create or update track lyrics",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Lyrics payload",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.UpdateLyricsRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"lyrics": {
"type": "object"
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / invalid id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/{id}/status": {
"get": {
"security": [
@ -4108,6 +4763,41 @@ const docTemplate = `{
}
},
"definitions": {
"internal_core_track.BatchDeleteRequest": {
"type": "object",
"required": [
"track_ids"
],
"properties": {
"track_ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
},
"internal_core_track.BatchUpdateRequest": {
"type": "object",
"required": [
"track_ids",
"updates"
],
"properties": {
"track_ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"updates": {
"type": "object",
"additionalProperties": true
}
}
},
"internal_core_track.CompleteChunkedUploadRequest": {
"type": "object",
"required": [
@ -4140,6 +4830,68 @@ const docTemplate = `{
}
}
},
"internal_core_track.UpdateLyricsRequest": {
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
},
"internal_core_track.UpdateTrackRequest": {
"type": "object",
"properties": {
"album": {
"type": "string",
"maxLength": 255
},
"artist": {
"type": "string",
"maxLength": 255
},
"bpm": {
"type": "integer",
"maximum": 300,
"minimum": 0
},
"genre": {
"description": "legacy, single",
"type": "string",
"maxLength": 100
},
"genres": {
"description": "v0.10.1: max 3, taxonomy slugs",
"type": "array",
"items": {
"type": "string"
}
},
"is_public": {
"type": "boolean"
},
"musical_key": {
"type": "string",
"maxLength": 10
},
"tags": {
"description": "v0.10.1: max 10, 30 chars each",
"type": "array",
"items": {
"type": "string"
}
},
"title": {
"type": "string",
"maxLength": 255,
"minLength": 1
},
"year": {
"type": "integer",
"maximum": 2100,
"minimum": 1900
}
}
},
"internal_handlers.APIResponse": {
"type": "object",
"properties": {
@ -4162,8 +4914,11 @@ const docTemplate = `{
"minLength": 1
},
"parent_id": {
"description": "Changed to *uuid.UUID",
"type": "string"
},
"timestamp": {
"description": "Position in seconds (0 = top-level, no specific time)",
"type": "number"
}
}
},
@ -4364,6 +5119,10 @@ const docTemplate = `{
"confirm_text": {
"type": "string"
},
"keep_public_tracks": {
"description": "If true, public tracks remain (attributed to deleted account)",
"type": "boolean"
},
"password": {
"type": "string"
},
@ -4777,6 +5536,10 @@ const docTemplate = `{
"promo_code_id": {
"type": "string"
},
"refund_deadline": {
"description": "v0.12.0: 14-day refund deadline",
"type": "string"
},
"status": {
"description": "pending, completed, failed, refunded",
"type": "string"
@ -5120,6 +5883,14 @@ const docTemplate = `{
"id": {
"type": "string"
},
"is_default_favorites": {
"description": "v0.10.4 F136",
"type": "boolean"
},
"is_editorial": {
"description": "v0.10.4 F141",
"type": "boolean"
},
"is_public": {
"type": "boolean"
},
@ -5261,15 +6032,9 @@ const docTemplate = `{
"is_public": {
"type": "boolean"
},
"like_count": {
"type": "integer"
},
"musical_key": {
"type": "string"
},
"play_count": {
"type": "integer"
},
"sample_rate": {
"description": "Hz",
"type": "integer"
@ -5386,6 +6151,14 @@ const docTemplate = `{
"description": "Virtual field for input",
"type": "string"
},
"password_changed_at": {
"description": "F016: Password expiration tracking",
"type": "string"
},
"promoted_to_creator_at": {
"description": "v1.0.6: set the first time a user self-promotes to ` + "`" + `role='creator'` + "`" + `\nvia POST /api/v1/users/me/upgrade-creator. NULL for users who never\ntook that path (still 'user', or promoted by an admin out-of-band).",
"type": "string"
},
"role": {
"type": "string"
},
@ -5421,6 +6194,12 @@ const docTemplate = `{
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"description": "Developer API key (obtain from Developer Portal). Format: vza_xxxxx",
"type": "apiKey",
"name": "X-API-Key",
"in": "header"
},
"BearerAuth": {
"type": "apiKey",
"name": "Authorization",
@ -5432,7 +6211,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.2.0",
Host: "localhost:8080",
Host: "localhost:18080",
BasePath: "/api/v1",
Schemes: []string{},
Title: "Veza Backend API",

View file

@ -15,7 +15,7 @@
},
"version": "1.2.0"
},
"host": "localhost:8080",
"host": "localhost:18080",
"basePath": "/api/v1",
"paths": {
"/api/v1/dashboard": {
@ -2343,6 +2343,106 @@
}
},
"/tracks": {
"get": {
"description": "List tracks with pagination, filters, sort. Cursor-based when ?cursor provided, otherwise page/limit offset.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "List tracks",
"parameters": [
{
"type": "string",
"description": "Opaque pagination cursor (overrides page)",
"name": "cursor",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "Page number, 1-based (ignored if cursor set)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 20,
"description": "Items per page (max 100)",
"name": "limit",
"in": "query"
},
{
"type": "string",
"description": "Filter by creator UUID",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"description": "Filter by genre",
"name": "genre",
"in": "query"
},
{
"type": "string",
"description": "Filter by audio format (mp3, flac, wav, ogg, m4a, aac)",
"name": "format",
"in": "query"
},
{
"type": "string",
"default": "created_at",
"description": "Sort column (created_at, play_count, title)",
"name": "sort_by",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"pagination": {
"type": "object"
},
"tracks": {
"type": "array",
"items": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid query params",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"post": {
"security": [
{
@ -2420,6 +2520,172 @@
}
}
},
"/tracks/batch/delete": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Soft-delete up to N tracks in one request. Per-track ownership checked (admin can delete others).",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Batch delete tracks",
"parameters": [
{
"description": "List of track UUIDs",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.BatchDeleteRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"deleted": {
"type": "array",
"items": {
"type": "string"
}
},
"failed": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / batch size exceeded",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/batch/update": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Apply a partial metadata update to up to N tracks in one request. Per-track ownership checked.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Batch update tracks",
"parameters": [
{
"description": "Track UUIDs + shared updates map",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.BatchUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"failed": {
"type": "array",
"items": {
"type": "object"
}
},
"updated": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / batch size exceeded",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/chunk": {
"post": {
"security": [
@ -2806,6 +3072,240 @@
}
}
},
"/tracks/{id}": {
"get": {
"description": "Retrieve a single track. Private play_count / like_count are omitted for non-owners (v0.10.3 F202).",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Get track by ID",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"track": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "Update the metadata of an existing track. Caller must own the track or be admin.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Update track metadata",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Updated metadata",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.UpdateTrackRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"track": {
"$ref": "#/definitions/veza-backend-api_internal_models.Track"
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / invalid id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "Soft-delete a track (sets deleted_at). Caller must own the track or be admin.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Delete track",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/{id}/comments": {
"get": {
"description": "Get paginated list of comments for a track",
@ -3060,6 +3560,161 @@
}
}
},
"/tracks/{id}/lyrics": {
"get": {
"description": "Returns the current lyrics for a track, or null if no lyrics exist.",
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Get track lyrics",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "lyrics may be null",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"lyrics": {
"type": "object"
}
}
}
}
}
]
}
},
"400": {
"description": "Invalid track id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "Replace the lyrics of a track. Caller must own the track or be admin.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Track"
],
"summary": "Create or update track lyrics",
"parameters": [
{
"type": "string",
"description": "Track UUID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Lyrics payload",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_core_track.UpdateLyricsRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"lyrics": {
"type": "object"
}
}
}
}
}
]
}
},
"400": {
"description": "Validation / invalid id",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"403": {
"description": "Not owner / no admin",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"404": {
"description": "Track not found",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
},
"500": {
"description": "Internal Error",
"schema": {
"$ref": "#/definitions/veza-backend-api_internal_response.APIResponse"
}
}
}
}
},
"/tracks/{id}/status": {
"get": {
"security": [
@ -4102,6 +4757,41 @@
}
},
"definitions": {
"internal_core_track.BatchDeleteRequest": {
"type": "object",
"required": [
"track_ids"
],
"properties": {
"track_ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
},
"internal_core_track.BatchUpdateRequest": {
"type": "object",
"required": [
"track_ids",
"updates"
],
"properties": {
"track_ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"updates": {
"type": "object",
"additionalProperties": true
}
}
},
"internal_core_track.CompleteChunkedUploadRequest": {
"type": "object",
"required": [
@ -4134,6 +4824,68 @@
}
}
},
"internal_core_track.UpdateLyricsRequest": {
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
},
"internal_core_track.UpdateTrackRequest": {
"type": "object",
"properties": {
"album": {
"type": "string",
"maxLength": 255
},
"artist": {
"type": "string",
"maxLength": 255
},
"bpm": {
"type": "integer",
"maximum": 300,
"minimum": 0
},
"genre": {
"description": "legacy, single",
"type": "string",
"maxLength": 100
},
"genres": {
"description": "v0.10.1: max 3, taxonomy slugs",
"type": "array",
"items": {
"type": "string"
}
},
"is_public": {
"type": "boolean"
},
"musical_key": {
"type": "string",
"maxLength": 10
},
"tags": {
"description": "v0.10.1: max 10, 30 chars each",
"type": "array",
"items": {
"type": "string"
}
},
"title": {
"type": "string",
"maxLength": 255,
"minLength": 1
},
"year": {
"type": "integer",
"maximum": 2100,
"minimum": 1900
}
}
},
"internal_handlers.APIResponse": {
"type": "object",
"properties": {
@ -4156,8 +4908,11 @@
"minLength": 1
},
"parent_id": {
"description": "Changed to *uuid.UUID",
"type": "string"
},
"timestamp": {
"description": "Position in seconds (0 = top-level, no specific time)",
"type": "number"
}
}
},
@ -4358,6 +5113,10 @@
"confirm_text": {
"type": "string"
},
"keep_public_tracks": {
"description": "If true, public tracks remain (attributed to deleted account)",
"type": "boolean"
},
"password": {
"type": "string"
},
@ -4771,6 +5530,10 @@
"promo_code_id": {
"type": "string"
},
"refund_deadline": {
"description": "v0.12.0: 14-day refund deadline",
"type": "string"
},
"status": {
"description": "pending, completed, failed, refunded",
"type": "string"
@ -5114,6 +5877,14 @@
"id": {
"type": "string"
},
"is_default_favorites": {
"description": "v0.10.4 F136",
"type": "boolean"
},
"is_editorial": {
"description": "v0.10.4 F141",
"type": "boolean"
},
"is_public": {
"type": "boolean"
},
@ -5255,15 +6026,9 @@
"is_public": {
"type": "boolean"
},
"like_count": {
"type": "integer"
},
"musical_key": {
"type": "string"
},
"play_count": {
"type": "integer"
},
"sample_rate": {
"description": "Hz",
"type": "integer"
@ -5380,6 +6145,14 @@
"description": "Virtual field for input",
"type": "string"
},
"password_changed_at": {
"description": "F016: Password expiration tracking",
"type": "string"
},
"promoted_to_creator_at": {
"description": "v1.0.6: set the first time a user self-promotes to `role='creator'`\nvia POST /api/v1/users/me/upgrade-creator. NULL for users who never\ntook that path (still 'user', or promoted by an admin out-of-band).",
"type": "string"
},
"role": {
"type": "string"
},
@ -5415,6 +6188,12 @@
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"description": "Developer API key (obtain from Developer Portal). Format: vza_xxxxx",
"type": "apiKey",
"name": "X-API-Key",
"in": "header"
},
"BearerAuth": {
"type": "apiKey",
"name": "Authorization",

View file

@ -1,5 +1,29 @@
basePath: /api/v1
definitions:
internal_core_track.BatchDeleteRequest:
properties:
track_ids:
items:
type: string
minItems: 1
type: array
required:
- track_ids
type: object
internal_core_track.BatchUpdateRequest:
properties:
track_ids:
items:
type: string
minItems: 1
type: array
updates:
additionalProperties: true
type: object
required:
- track_ids
- updates
type: object
internal_core_track.CompleteChunkedUploadRequest:
properties:
upload_id:
@ -22,6 +46,51 @@ definitions:
- total_chunks
- total_size
type: object
internal_core_track.UpdateLyricsRequest:
properties:
content:
type: string
type: object
internal_core_track.UpdateTrackRequest:
properties:
album:
maxLength: 255
type: string
artist:
maxLength: 255
type: string
bpm:
maximum: 300
minimum: 0
type: integer
genre:
description: legacy, single
maxLength: 100
type: string
genres:
description: 'v0.10.1: max 3, taxonomy slugs'
items:
type: string
type: array
is_public:
type: boolean
musical_key:
maxLength: 10
type: string
tags:
description: 'v0.10.1: max 10, 30 chars each'
items:
type: string
type: array
title:
maxLength: 255
minLength: 1
type: string
year:
maximum: 2100
minimum: 1900
type: integer
type: object
internal_handlers.APIResponse:
properties:
data: {}
@ -36,8 +105,10 @@ definitions:
minLength: 1
type: string
parent_id:
description: Changed to *uuid.UUID
type: string
timestamp:
description: Position in seconds (0 = top-level, no specific time)
type: number
required:
- content
type: object
@ -178,6 +249,9 @@ definitions:
properties:
confirm_text:
type: string
keep_public_tracks:
description: If true, public tracks remain (attributed to deleted account)
type: boolean
password:
type: string
reason:
@ -468,6 +542,9 @@ definitions:
type: string
promo_code_id:
type: string
refund_deadline:
description: 'v0.12.0: 14-day refund deadline'
type: string
status:
description: pending, completed, failed, refunded
type: string
@ -698,6 +775,12 @@ definitions:
type: integer
id:
type: string
is_default_favorites:
description: v0.10.4 F136
type: boolean
is_editorial:
description: v0.10.4 F141
type: boolean
is_public:
type: boolean
title:
@ -794,12 +877,8 @@ definitions:
type: string
is_public:
type: boolean
like_count:
type: integer
musical_key:
type: string
play_count:
type: integer
sample_rate:
description: Hz
type: integer
@ -880,6 +959,15 @@ definitions:
password:
description: Virtual field for input
type: string
password_changed_at:
description: 'F016: Password expiration tracking'
type: string
promoted_to_creator_at:
description: |-
v1.0.6: set the first time a user self-promotes to `role='creator'`
via POST /api/v1/users/me/upgrade-creator. NULL for users who never
took that path (still 'user', or promoted by an admin out-of-band).
type: string
role:
type: string
slug:
@ -902,7 +990,7 @@ definitions:
success:
type: boolean
type: object
host: localhost:8080
host: localhost:18080
info:
contact:
email: support@veza.app
@ -2350,6 +2438,71 @@ paths:
tags:
- Playlist
/tracks:
get:
description: List tracks with pagination, filters, sort. Cursor-based when ?cursor
provided, otherwise page/limit offset.
parameters:
- description: Opaque pagination cursor (overrides page)
in: query
name: cursor
type: string
- default: 1
description: Page number, 1-based (ignored if cursor set)
in: query
name: page
type: integer
- default: 20
description: Items per page (max 100)
in: query
name: limit
type: integer
- description: Filter by creator UUID
in: query
name: user_id
type: string
- description: Filter by genre
in: query
name: genre
type: string
- description: Filter by audio format (mp3, flac, wav, ogg, m4a, aac)
in: query
name: format
type: string
- default: created_at
description: Sort column (created_at, play_count, title)
in: query
name: sort_by
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
pagination:
type: object
tracks:
items:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: array
type: object
type: object
"400":
description: Invalid query params
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: List tracks
tags:
- Track
post:
consumes:
- multipart/form-data
@ -2396,6 +2549,152 @@ paths:
summary: Upload Track
tags:
- Track
/tracks/{id}:
delete:
description: Soft-delete a track (sets deleted_at). Caller must own the track
or be admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
message:
type: string
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Delete track
tags:
- Track
get:
description: Retrieve a single track. Private play_count / like_count are omitted
for non-owners (v0.10.3 F202).
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
track:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: Get track by ID
tags:
- Track
put:
consumes:
- application/json
description: Update the metadata of an existing track. Caller must own the track
or be admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
- description: Updated metadata
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.UpdateTrackRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
track:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: object
type: object
"400":
description: Validation / invalid id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Update track metadata
tags:
- Track
/tracks/{id}/comments:
get:
consumes:
@ -2554,6 +2853,102 @@ paths:
summary: Delete comment
tags:
- Comment
/tracks/{id}/lyrics:
get:
description: Returns the current lyrics for a track, or null if no lyrics exist.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: lyrics may be null
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
lyrics:
type: object
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: Get track lyrics
tags:
- Track
put:
consumes:
- application/json
description: Replace the lyrics of a track. Caller must own the track or be
admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
- description: Lyrics payload
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.UpdateLyricsRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
lyrics:
type: object
type: object
type: object
"400":
description: Validation / invalid id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Create or update track lyrics
tags:
- Track
/tracks/{id}/status:
get:
consumes:
@ -2597,6 +2992,108 @@ paths:
summary: Get Upload Status
tags:
- Track
/tracks/batch/delete:
post:
consumes:
- application/json
description: Soft-delete up to N tracks in one request. Per-track ownership
checked (admin can delete others).
parameters:
- description: List of track UUIDs
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.BatchDeleteRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
deleted:
items:
type: string
type: array
failed:
items:
type: object
type: array
type: object
type: object
"400":
description: Validation / batch size exceeded
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Batch delete tracks
tags:
- Track
/tracks/batch/update:
post:
consumes:
- application/json
description: Apply a partial metadata update to up to N tracks in one request.
Per-track ownership checked.
parameters:
- description: Track UUIDs + shared updates map
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.BatchUpdateRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
failed:
items:
type: object
type: array
updated:
items:
type: string
type: array
type: object
type: object
"400":
description: Validation / batch size exceeded
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Batch update tracks
tags:
- Track
/tracks/chunk:
post:
consumes:
@ -3428,6 +3925,11 @@ paths:
tags:
- Webhook
securityDefinitions:
ApiKeyAuth:
description: 'Developer API key (obtain from Developer Portal). Format: vza_xxxxx'
in: header
name: X-API-Key
type: apiKey
BearerAuth:
in: header
name: Authorization

View file

@ -54,6 +54,21 @@ type BatchUpdateRequest struct {
// ListTracks gère la liste des tracks avec pagination, filtres et tri.
// v0.931: Supports cursor-based pagination via ?cursor=xxx&limit=20 for consistent performance.
// Falls back to page/limit (offset) when cursor is not provided.
// @Summary List tracks
// @Description List tracks with pagination, filters, sort. Cursor-based when ?cursor provided, otherwise page/limit offset.
// @Tags Track
// @Produce json
// @Param cursor query string false "Opaque pagination cursor (overrides page)"
// @Param page query int false "Page number, 1-based (ignored if cursor set)" default(1)
// @Param limit query int false "Items per page (max 100)" default(20)
// @Param user_id query string false "Filter by creator UUID"
// @Param genre query string false "Filter by genre"
// @Param format query string false "Filter by audio format (mp3, flac, wav, ogg, m4a, aac)"
// @Param sort_by query string false "Sort column (created_at, play_count, title)" default(created_at)
// @Success 200 {object} response.APIResponse{data=object{tracks=[]models.Track,pagination=object}}
// @Failure 400 {object} response.APIResponse "Invalid query params"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks [get]
func (h *TrackHandler) ListTracks(c *gin.Context) {
cursor := c.Query("cursor")
page := c.DefaultQuery("page", "1")
@ -138,6 +153,16 @@ func (h *TrackHandler) ListTracks(c *gin.Context) {
}
// GetTrack gère la récupération d'un track par ID
// @Summary Get track by ID
// @Description Retrieve a single track. Private play_count / like_count are omitted for non-owners (v0.10.3 F202).
// @Tags Track
// @Produce json
// @Param id path string true "Track UUID"
// @Success 200 {object} response.APIResponse{data=object{track=models.Track}}
// @Failure 400 {object} response.APIResponse "Invalid track id"
// @Failure 404 {object} response.APIResponse "Track not found"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/{id} [get]
func (h *TrackHandler) GetTrack(c *gin.Context) {
trackIDStr := c.Param("id")
if trackIDStr == "" {
@ -196,6 +221,21 @@ func hideLikeCountFromTrack(track *models.Track) map[string]interface{} {
}
// UpdateTrack gère la mise à jour d'un track
// @Summary Update track metadata
// @Description Update the metadata of an existing track. Caller must own the track or be admin.
// @Tags Track
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Track UUID"
// @Param request body UpdateTrackRequest true "Updated metadata"
// @Success 200 {object} response.APIResponse{data=object{track=models.Track}}
// @Failure 400 {object} response.APIResponse "Validation / invalid id"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Failure 403 {object} response.APIResponse "Not owner / no admin"
// @Failure 404 {object} response.APIResponse "Track not found"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/{id} [put]
func (h *TrackHandler) UpdateTrack(c *gin.Context) {
userID, ok := h.getUserID(c)
if !ok {
@ -279,6 +319,16 @@ func (h *TrackHandler) UpdateTrack(c *gin.Context) {
}
// GetLyrics gère la récupération des paroles d'un track
// @Summary Get track lyrics
// @Description Returns the current lyrics for a track, or null if no lyrics exist.
// @Tags Track
// @Produce json
// @Param id path string true "Track UUID"
// @Success 200 {object} response.APIResponse{data=object{lyrics=object}} "lyrics may be null"
// @Failure 400 {object} response.APIResponse "Invalid track id"
// @Failure 404 {object} response.APIResponse "Track not found"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/{id}/lyrics [get]
func (h *TrackHandler) GetLyrics(c *gin.Context) {
trackIDStr := c.Param("id")
if trackIDStr == "" {
@ -311,6 +361,21 @@ func (h *TrackHandler) GetLyrics(c *gin.Context) {
}
// UpdateLyrics gère la création/mise à jour des paroles
// @Summary Create or update track lyrics
// @Description Replace the lyrics of a track. Caller must own the track or be admin.
// @Tags Track
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Track UUID"
// @Param request body UpdateLyricsRequest true "Lyrics payload"
// @Success 200 {object} response.APIResponse{data=object{lyrics=object}}
// @Failure 400 {object} response.APIResponse "Validation / invalid id"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Failure 403 {object} response.APIResponse "Not owner / no admin"
// @Failure 404 {object} response.APIResponse "Track not found"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/{id}/lyrics [put]
func (h *TrackHandler) UpdateLyrics(c *gin.Context) {
userID, ok := h.getUserID(c)
if !ok {
@ -348,6 +413,19 @@ func (h *TrackHandler) UpdateLyrics(c *gin.Context) {
}
// DeleteTrack gère la suppression d'un track
// @Summary Delete track
// @Description Soft-delete a track (sets deleted_at). Caller must own the track or be admin.
// @Tags Track
// @Produce json
// @Security BearerAuth
// @Param id path string true "Track UUID"
// @Success 200 {object} response.APIResponse{data=object{message=string}}
// @Failure 400 {object} response.APIResponse "Invalid track id"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Failure 403 {object} response.APIResponse "Not owner / no admin"
// @Failure 404 {object} response.APIResponse "Track not found"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/{id} [delete]
func (h *TrackHandler) DeleteTrack(c *gin.Context) {
userID, ok := h.getUserID(c)
if !ok {
@ -392,6 +470,18 @@ func (h *TrackHandler) DeleteTrack(c *gin.Context) {
}
// BatchDeleteTracks gère la suppression en lot de plusieurs tracks
// @Summary Batch delete tracks
// @Description Soft-delete up to N tracks in one request. Per-track ownership checked (admin can delete others).
// @Tags Track
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body BatchDeleteRequest true "List of track UUIDs"
// @Success 200 {object} response.APIResponse{data=object{deleted=[]string,failed=[]object}}
// @Failure 400 {object} response.APIResponse "Validation / batch size exceeded"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/batch/delete [post]
func (h *TrackHandler) BatchDeleteTracks(c *gin.Context) {
userID, ok := h.getUserID(c)
if !ok {
@ -436,6 +526,18 @@ func (h *TrackHandler) BatchDeleteTracks(c *gin.Context) {
}
// BatchUpdateTracks gère la mise à jour en lot de plusieurs tracks
// @Summary Batch update tracks
// @Description Apply a partial metadata update to up to N tracks in one request. Per-track ownership checked.
// @Tags Track
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body BatchUpdateRequest true "Track UUIDs + shared updates map"
// @Success 200 {object} response.APIResponse{data=object{updated=[]string,failed=[]object}}
// @Failure 400 {object} response.APIResponse "Validation / batch size exceeded"
// @Failure 401 {object} response.APIResponse "Unauthorized"
// @Failure 500 {object} response.APIResponse "Internal Error"
// @Router /tracks/batch/update [post]
func (h *TrackHandler) BatchUpdateTracks(c *gin.Context) {
userID, ok := h.getUserID(c)
if !ok {

View file

@ -1,5 +1,29 @@
basePath: /api/v1
definitions:
internal_core_track.BatchDeleteRequest:
properties:
track_ids:
items:
type: string
minItems: 1
type: array
required:
- track_ids
type: object
internal_core_track.BatchUpdateRequest:
properties:
track_ids:
items:
type: string
minItems: 1
type: array
updates:
additionalProperties: true
type: object
required:
- track_ids
- updates
type: object
internal_core_track.CompleteChunkedUploadRequest:
properties:
upload_id:
@ -22,6 +46,51 @@ definitions:
- total_chunks
- total_size
type: object
internal_core_track.UpdateLyricsRequest:
properties:
content:
type: string
type: object
internal_core_track.UpdateTrackRequest:
properties:
album:
maxLength: 255
type: string
artist:
maxLength: 255
type: string
bpm:
maximum: 300
minimum: 0
type: integer
genre:
description: legacy, single
maxLength: 100
type: string
genres:
description: 'v0.10.1: max 3, taxonomy slugs'
items:
type: string
type: array
is_public:
type: boolean
musical_key:
maxLength: 10
type: string
tags:
description: 'v0.10.1: max 10, 30 chars each'
items:
type: string
type: array
title:
maxLength: 255
minLength: 1
type: string
year:
maximum: 2100
minimum: 1900
type: integer
type: object
internal_handlers.APIResponse:
properties:
data: {}
@ -36,8 +105,10 @@ definitions:
minLength: 1
type: string
parent_id:
description: Changed to *uuid.UUID
type: string
timestamp:
description: Position in seconds (0 = top-level, no specific time)
type: number
required:
- content
type: object
@ -178,6 +249,9 @@ definitions:
properties:
confirm_text:
type: string
keep_public_tracks:
description: If true, public tracks remain (attributed to deleted account)
type: boolean
password:
type: string
reason:
@ -468,6 +542,9 @@ definitions:
type: string
promo_code_id:
type: string
refund_deadline:
description: 'v0.12.0: 14-day refund deadline'
type: string
status:
description: pending, completed, failed, refunded
type: string
@ -698,6 +775,12 @@ definitions:
type: integer
id:
type: string
is_default_favorites:
description: v0.10.4 F136
type: boolean
is_editorial:
description: v0.10.4 F141
type: boolean
is_public:
type: boolean
title:
@ -794,12 +877,8 @@ definitions:
type: string
is_public:
type: boolean
like_count:
type: integer
musical_key:
type: string
play_count:
type: integer
sample_rate:
description: Hz
type: integer
@ -880,6 +959,15 @@ definitions:
password:
description: Virtual field for input
type: string
password_changed_at:
description: 'F016: Password expiration tracking'
type: string
promoted_to_creator_at:
description: |-
v1.0.6: set the first time a user self-promotes to `role='creator'`
via POST /api/v1/users/me/upgrade-creator. NULL for users who never
took that path (still 'user', or promoted by an admin out-of-band).
type: string
role:
type: string
slug:
@ -902,7 +990,7 @@ definitions:
success:
type: boolean
type: object
host: localhost:8080
host: localhost:18080
info:
contact:
email: support@veza.app
@ -2350,6 +2438,71 @@ paths:
tags:
- Playlist
/tracks:
get:
description: List tracks with pagination, filters, sort. Cursor-based when ?cursor
provided, otherwise page/limit offset.
parameters:
- description: Opaque pagination cursor (overrides page)
in: query
name: cursor
type: string
- default: 1
description: Page number, 1-based (ignored if cursor set)
in: query
name: page
type: integer
- default: 20
description: Items per page (max 100)
in: query
name: limit
type: integer
- description: Filter by creator UUID
in: query
name: user_id
type: string
- description: Filter by genre
in: query
name: genre
type: string
- description: Filter by audio format (mp3, flac, wav, ogg, m4a, aac)
in: query
name: format
type: string
- default: created_at
description: Sort column (created_at, play_count, title)
in: query
name: sort_by
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
pagination:
type: object
tracks:
items:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: array
type: object
type: object
"400":
description: Invalid query params
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: List tracks
tags:
- Track
post:
consumes:
- multipart/form-data
@ -2396,6 +2549,152 @@ paths:
summary: Upload Track
tags:
- Track
/tracks/{id}:
delete:
description: Soft-delete a track (sets deleted_at). Caller must own the track
or be admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
message:
type: string
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Delete track
tags:
- Track
get:
description: Retrieve a single track. Private play_count / like_count are omitted
for non-owners (v0.10.3 F202).
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
track:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: Get track by ID
tags:
- Track
put:
consumes:
- application/json
description: Update the metadata of an existing track. Caller must own the track
or be admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
- description: Updated metadata
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.UpdateTrackRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
track:
$ref: '#/definitions/veza-backend-api_internal_models.Track'
type: object
type: object
"400":
description: Validation / invalid id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Update track metadata
tags:
- Track
/tracks/{id}/comments:
get:
consumes:
@ -2554,6 +2853,102 @@ paths:
summary: Delete comment
tags:
- Comment
/tracks/{id}/lyrics:
get:
description: Returns the current lyrics for a track, or null if no lyrics exist.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: lyrics may be null
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
lyrics:
type: object
type: object
type: object
"400":
description: Invalid track id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
summary: Get track lyrics
tags:
- Track
put:
consumes:
- application/json
description: Replace the lyrics of a track. Caller must own the track or be
admin.
parameters:
- description: Track UUID
in: path
name: id
required: true
type: string
- description: Lyrics payload
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.UpdateLyricsRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
lyrics:
type: object
type: object
type: object
"400":
description: Validation / invalid id
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"403":
description: Not owner / no admin
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"404":
description: Track not found
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Create or update track lyrics
tags:
- Track
/tracks/{id}/status:
get:
consumes:
@ -2597,6 +2992,108 @@ paths:
summary: Get Upload Status
tags:
- Track
/tracks/batch/delete:
post:
consumes:
- application/json
description: Soft-delete up to N tracks in one request. Per-track ownership
checked (admin can delete others).
parameters:
- description: List of track UUIDs
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.BatchDeleteRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
deleted:
items:
type: string
type: array
failed:
items:
type: object
type: array
type: object
type: object
"400":
description: Validation / batch size exceeded
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Batch delete tracks
tags:
- Track
/tracks/batch/update:
post:
consumes:
- application/json
description: Apply a partial metadata update to up to N tracks in one request.
Per-track ownership checked.
parameters:
- description: Track UUIDs + shared updates map
in: body
name: request
required: true
schema:
$ref: '#/definitions/internal_core_track.BatchUpdateRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
- properties:
data:
properties:
failed:
items:
type: object
type: array
updated:
items:
type: string
type: array
type: object
type: object
"400":
description: Validation / batch size exceeded
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
"500":
description: Internal Error
schema:
$ref: '#/definitions/veza-backend-api_internal_response.APIResponse'
security:
- BearerAuth: []
summary: Batch update tracks
tags:
- Track
/tracks/chunk:
post:
consumes:
@ -3428,14 +3925,13 @@ paths:
tags:
- Webhook
securityDefinitions:
BearerAuth:
description: "JWT Bearer token. Format: Bearer {token}"
in: header
name: Authorization
type: apiKey
ApiKeyAuth:
description: "Developer API key. Format: vza_xxxxx (obtain from Developer Portal)"
description: 'Developer API key (obtain from Developer Portal). Format: vza_xxxxx'
in: header
name: X-API-Key
type: apiKey
BearerAuth:
in: header
name: Authorization
type: apiKey
swagger: "2.0"