diff --git a/veza-backend-api/docs/docs.go b/veza-backend-api/docs/docs.go index e8549fbf4..199cd8ce0 100644 --- a/veza-backend-api/docs/docs.go +++ b/veza-backend-api/docs/docs.go @@ -107,7 +107,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateOrderRequest" + "$ref": "#/definitions/internal_handlers.CreateOrderRequest" } } ], @@ -115,7 +115,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/marketplace.Order" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Order" } }, "400": { @@ -172,7 +172,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/marketplace.Product" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Product" } } } @@ -202,7 +202,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateProductRequest" + "$ref": "#/definitions/internal_handlers.CreateProductRequest" } } ], @@ -210,7 +210,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/marketplace.Product" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Product" } }, "400": { @@ -233,10 +233,2325 @@ const docTemplate = `{ } } } + }, + "/auth/check-username": { + "get": { + "description": "Check if a username is already taken", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Check Username Availability", + "parameters": [ + { + "type": "string", + "description": "Username to check", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "available": { + "type": "boolean" + }, + "username": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Missing Username", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/login": { + "post": { + "description": "Authenticate user and return access/refresh tokens", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "User Login", + "parameters": [ + { + "description": "Login Credentials", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.LoginResponse" + } + }, + "400": { + "description": "Validation or Bad Request", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Invalid credentials", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/logout": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Revoke refresh token and current session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Logout", + "parameters": [ + { + "description": "Refresh Token to revoke", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get profile information of the currently logged-in user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Get Current User", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/refresh": { + "post": { + "description": "Get a new access token using a refresh token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Refresh Token", + "parameters": [ + { + "description": "Refresh Token", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RefreshRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Invalid/Expired Refresh Token", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/register": { + "post": { + "description": "Register a new user account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "User Registration", + "parameters": [ + { + "description": "Registration Data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RegisterRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RegisterResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "409": { + "description": "User already exists", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/resend-verification": { + "post": { + "description": "Resend the email verification link", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Resend Verification Email", + "parameters": [ + { + "description": "Email", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.ResendVerificationRequest" + } + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/verify-email": { + "post": { + "description": "Verify user email address using a token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Verify Email", + "parameters": [ + { + "type": "string", + "description": "Verification Token", + "name": "token", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Invalid Token", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/chat/token": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Generate a short-lived token for chat authentication", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Get Chat Token", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a paginated list of playlists", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Get Playlists", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Filter by User ID", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "pagination": { + "type": "object" + }, + "playlists": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Create Playlist", + "parameters": [ + { + "description": "Playlist Metadata", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.CreatePlaylistRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get detailed information about a playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Get Playlist by ID", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update playlist metadata", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Update Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Playlist Metadata", + "name": "playlist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.UpdatePlaylistRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permanently delete a playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Delete Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a track to the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Add Track to Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Track ID (in body)", + "name": "trackId", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "track_id": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Track already present or invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist or Track not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks/reorder": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Reorder tracks in the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Reorder Tracks", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "New Track Order", + "name": "order", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.ReorderTracksRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks/{trackId}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Remove a track from the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Remove Track from Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Track ID", + "name": "trackId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "404": { + "description": "Playlist or Track not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/tracks": { + "get": { + "description": "Get a paginated list of tracks with filters", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "List Tracks", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Filter by User ID", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter by Genre", + "name": "genre", + "in": "query" + }, + { + "type": "string", + "description": "Filter by Format", + "name": "format", + "in": "query" + }, + { + "type": "string", + "default": "created_at", + "description": "Sort field", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "default": "desc", + "description": "Sort order (asc/desc)", + "name": "sort_order", + "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" + } + } + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Upload a new track (audio file)", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Upload Track", + "parameters": [ + { + "type": "file", + "description": "Audio File (MP3, WAV, FLAC, OGG)", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "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": "No file or validation error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Quota exceeded", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/batch/delete": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete multiple tracks at once", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Batch Delete Tracks", + "parameters": [ + { + "description": "List of Track IDs", + "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": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "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": [ + { + "BearerAuth": [] + } + ], + "description": "Upload a single chunk of a file", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Upload Chunk", + "parameters": [ + { + "type": "file", + "description": "Chunk Data", + "name": "chunk", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Upload ID", + "name": "upload_id", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Chunk Number", + "name": "chunk_number", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Total Chunks", + "name": "total_chunks", + "in": "formData", + "required": true + }, + { + "type": "integer", + "format": "int64", + "description": "Total Size", + "name": "total_size", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Filename", + "name": "filename", + "in": "formData", + "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" + }, + "progress": { + "type": "number", + "format": "float64" + }, + "received_chunks": { + "type": "integer" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/complete": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Finish upload session and assemble file", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Complete Chunked Upload", + "parameters": [ + { + "description": "Upload ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_core_track.CompleteChunkedUploadRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "md5": { + "type": "string" + }, + "message": { + "type": "string" + }, + "track": { + "$ref": "#/definitions/veza-backend-api_internal_models.Track" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation or Assemblage Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/initiate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Start a new chunked upload session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Initiate Chunked Upload", + "parameters": [ + { + "description": "Upload Metadata", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_core_track.InitiateChunkedUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/quota/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get remaining upload quota for the user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Upload Quota", + "parameters": [ + { + "type": "string", + "description": "User ID (optional, defaults to current user)", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "quota": { + "type": "object" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/resume/{uploadId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get state of an interrupted upload", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Resume Upload", + "parameters": [ + { + "type": "string", + "description": "Upload ID", + "name": "uploadId", + "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": { + "chunks_received": { + "type": "integer" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "404": { + "description": "Upload session not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/{id}": { + "get": { + "description": "Get detailed information about a track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Track by ID", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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 ID", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update track metadata", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Update Track", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Track Metadata", + "name": "track", + "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 Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permanently delete a track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Delete Track", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/{id}/status": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get the processing status of an uploaded track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Upload Status", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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": { + "progress": { + "type": "integer" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/users/by-username/{username}": { + "get": { + "description": "Get public profile information for a user by username", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile by Username", + "parameters": [ + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Missing username", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/{id}": { + "get": { + "description": "Get public profile information for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile by ID", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update user profile details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Update Profile", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Profile Data", + "name": "profile", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/{id}/completion": { + "get": { + "description": "Get profile completion percentage and missing fields", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile Completion", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } } }, "definitions": { - "handlers.CreateOrderRequest": { + "internal_core_track.BatchDeleteRequest": { + "type": "object", + "required": [ + "track_ids" + ], + "properties": { + "track_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "internal_core_track.CompleteChunkedUploadRequest": { + "type": "object", + "required": [ + "upload_id" + ], + "properties": { + "upload_id": { + "type": "string" + } + } + }, + "internal_core_track.InitiateChunkedUploadRequest": { + "type": "object", + "required": [ + "filename", + "total_chunks", + "total_size" + ], + "properties": { + "filename": { + "type": "string" + }, + "total_chunks": { + "type": "integer", + "minimum": 1 + }, + "total_size": { + "type": "integer", + "minimum": 1 + } + } + }, + "internal_core_track.UpdateTrackRequest": { + "type": "object", + "properties": { + "album": { + "type": "string" + }, + "artist": { + "type": "string" + }, + "genre": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "year": { + "type": "integer" + } + } + }, + "internal_handlers.APIResponse": { + "type": "object", + "properties": { + "data": {}, + "error": {}, + "success": { + "type": "boolean" + } + } + }, + "internal_handlers.CreateOrderRequest": { "type": "object", "required": [ "items" @@ -259,7 +2574,26 @@ const docTemplate = `{ } } }, - "handlers.CreateProductRequest": { + "internal_handlers.CreatePlaylistRequest": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 200, + "minLength": 1 + } + } + }, + "internal_handlers.CreateProductRequest": { "type": "object", "required": [ "price", @@ -302,7 +2636,77 @@ const docTemplate = `{ } } }, - "marketplace.LicenseType": { + "internal_handlers.ReorderTracksRequest": { + "type": "object", + "required": [ + "track_ids" + ], + "properties": { + "track_ids": { + "description": "Changed to []uuid.UUID", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } + }, + "internal_handlers.UpdatePlaylistRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 200, + "minLength": 1 + } + } + }, + "internal_handlers.UpdateProfileRequest": { + "type": "object", + "properties": { + "bio": { + "type": "string", + "maxLength": 500 + }, + "birthdate": { + "type": "string" + }, + "first_name": { + "type": "string", + "maxLength": 100 + }, + "gender": { + "type": "string", + "enum": [ + "Male", + "Female", + "Other", + "Prefer not to say" + ] + }, + "last_name": { + "type": "string", + "maxLength": 100 + }, + "location": { + "type": "string", + "maxLength": 100 + }, + "username": { + "type": "string", + "maxLength": 30, + "minLength": 3 + } + } + }, + "veza-backend-api_internal_core_marketplace.LicenseType": { "type": "string", "enum": [ "basic", @@ -315,7 +2719,7 @@ const docTemplate = `{ "LicenseExclusive" ] }, - "marketplace.Order": { + "veza-backend-api_internal_core_marketplace.Order": { "type": "object", "properties": { "buyer_id": { @@ -333,7 +2737,7 @@ const docTemplate = `{ "items": { "type": "array", "items": { - "$ref": "#/definitions/marketplace.OrderItem" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.OrderItem" } }, "payment_intent": { @@ -352,7 +2756,7 @@ const docTemplate = `{ } } }, - "marketplace.OrderItem": { + "veza-backend-api_internal_core_marketplace.OrderItem": { "type": "object", "properties": { "id": { @@ -369,7 +2773,7 @@ const docTemplate = `{ } } }, - "marketplace.Product": { + "veza-backend-api_internal_core_marketplace.Product": { "type": "object", "properties": { "created_at": { @@ -385,7 +2789,7 @@ const docTemplate = `{ "type": "string" }, "license_type": { - "$ref": "#/definitions/marketplace.LicenseType" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.LicenseType" }, "price": { "type": "number" @@ -398,7 +2802,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "$ref": "#/definitions/marketplace.ProductStatus" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.ProductStatus" }, "title": { "type": "string" @@ -412,7 +2816,7 @@ const docTemplate = `{ } } }, - "marketplace.ProductStatus": { + "veza-backend-api_internal_core_marketplace.ProductStatus": { "type": "string", "enum": [ "draft", @@ -424,6 +2828,410 @@ const docTemplate = `{ "ProductStatusActive", "ProductStatusArchived" ] + }, + "veza-backend-api_internal_dto.LoginRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "remember_me": { + "type": "boolean" + } + } + }, + "veza-backend-api_internal_dto.LoginResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_dto.UserResponse" + } + } + }, + "veza-backend-api_internal_dto.RefreshRequest": { + "type": "object", + "required": [ + "refresh_token" + ], + "properties": { + "refresh_token": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.RegisterRequest": { + "type": "object", + "required": [ + "email", + "password", + "password_confirm" + ], + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string", + "minLength": 12 + }, + "password_confirm": { + "type": "string" + }, + "username": { + "type": "string", + "maxLength": 50, + "minLength": 3 + } + } + }, + "veza-backend-api_internal_dto.RegisterResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_dto.UserResponse" + } + } + }, + "veza-backend-api_internal_dto.ResendVerificationRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.TokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "refresh_token": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.UserResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.Playlist": { + "type": "object", + "properties": { + "collaborators": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistCollaborator" + } + }, + "cover_url": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "follower_count": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "track_count": { + "type": "integer" + }, + "tracks": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistTrack" + } + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.PlaylistCollaborator": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "permission": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistPermission" + }, + "playlist_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + }, + "user_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.PlaylistPermission": { + "type": "string", + "enum": [ + "read", + "write", + "admin" + ], + "x-enum-varnames": [ + "PlaylistPermissionRead", + "PlaylistPermissionWrite", + "PlaylistPermissionAdmin" + ] + }, + "veza-backend-api_internal_models.PlaylistTrack": { + "type": "object", + "properties": { + "added_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "playlist_id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "track": { + "$ref": "#/definitions/veza-backend-api_internal_models.Track" + }, + "track_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.Track": { + "type": "object", + "properties": { + "album": { + "type": "string" + }, + "artist": { + "type": "string" + }, + "bitrate": { + "description": "kbps", + "type": "integer" + }, + "cover_art_path": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "duration": { + "description": "seconds", + "type": "integer" + }, + "file_path": { + "type": "string" + }, + "file_size": { + "description": "bytes", + "type": "integer" + }, + "format": { + "description": "mp3, flac, wav, etc.", + "type": "string" + }, + "genre": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "like_count": { + "type": "integer" + }, + "play_count": { + "type": "integer" + }, + "sample_rate": { + "description": "Hz", + "type": "integer" + }, + "status": { + "$ref": "#/definitions/veza-backend-api_internal_models.TrackStatus" + }, + "status_message": { + "type": "string" + }, + "stream_manifest_url": { + "type": "string" + }, + "stream_status": { + "description": "pending, processing, ready, error", + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "waveform_path": { + "type": "string" + }, + "year": { + "type": "integer" + } + } + }, + "veza-backend-api_internal_models.TrackStatus": { + "type": "string", + "enum": [ + "uploading", + "processing", + "completed", + "failed" + ], + "x-enum-varnames": [ + "TrackStatusUploading", + "TrackStatusProcessing", + "TrackStatusCompleted", + "TrackStatusFailed" + ] + }, + "veza-backend-api_internal_models.User": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "birthdate": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_admin": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "is_verified": { + "type": "boolean" + }, + "last_login_at": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "password": { + "description": "Virtual field for input", + "type": "string" + }, + "role": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "token_version": { + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "username_changed_at": { + "type": "string" + } + } + }, + "veza-backend-api_internal_response.APIResponse": { + "type": "object", + "properties": { + "data": {}, + "error": {}, + "success": { + "type": "boolean" + } + } } }, "securityDefinitions": { diff --git a/veza-backend-api/docs/swagger.json b/veza-backend-api/docs/swagger.json index 362ec265b..3f3ad5b8b 100644 --- a/veza-backend-api/docs/swagger.json +++ b/veza-backend-api/docs/swagger.json @@ -101,7 +101,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateOrderRequest" + "$ref": "#/definitions/internal_handlers.CreateOrderRequest" } } ], @@ -109,7 +109,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/marketplace.Order" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Order" } }, "400": { @@ -166,7 +166,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/marketplace.Product" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Product" } } } @@ -196,7 +196,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateProductRequest" + "$ref": "#/definitions/internal_handlers.CreateProductRequest" } } ], @@ -204,7 +204,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/marketplace.Product" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.Product" } }, "400": { @@ -227,10 +227,2325 @@ } } } + }, + "/auth/check-username": { + "get": { + "description": "Check if a username is already taken", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Check Username Availability", + "parameters": [ + { + "type": "string", + "description": "Username to check", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "available": { + "type": "boolean" + }, + "username": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Missing Username", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/login": { + "post": { + "description": "Authenticate user and return access/refresh tokens", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "User Login", + "parameters": [ + { + "description": "Login Credentials", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.LoginResponse" + } + }, + "400": { + "description": "Validation or Bad Request", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Invalid credentials", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/logout": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Revoke refresh token and current session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Logout", + "parameters": [ + { + "description": "Refresh Token to revoke", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get profile information of the currently logged-in user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Get Current User", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/refresh": { + "post": { + "description": "Get a new access token using a refresh token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Refresh Token", + "parameters": [ + { + "description": "Refresh Token", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RefreshRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Invalid/Expired Refresh Token", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/register": { + "post": { + "description": "Register a new user account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "User Registration", + "parameters": [ + { + "description": "Registration Data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RegisterRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.RegisterResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "409": { + "description": "User already exists", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/resend-verification": { + "post": { + "description": "Resend the email verification link", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Resend Verification Email", + "parameters": [ + { + "description": "Email", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_dto.ResendVerificationRequest" + } + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/auth/verify-email": { + "post": { + "description": "Verify user email address using a token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth" + ], + "summary": "Verify Email", + "parameters": [ + { + "type": "string", + "description": "Verification Token", + "name": "token", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success message", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "400": { + "description": "Invalid Token", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/chat/token": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Generate a short-lived token for chat authentication", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Chat" + ], + "summary": "Get Chat Token", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a paginated list of playlists", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Get Playlists", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Filter by User ID", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "pagination": { + "type": "object" + }, + "playlists": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Create Playlist", + "parameters": [ + { + "description": "Playlist Metadata", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.CreatePlaylistRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get detailed information about a playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Get Playlist by ID", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update playlist metadata", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Update Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Playlist Metadata", + "name": "playlist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.UpdatePlaylistRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "playlist": { + "$ref": "#/definitions/veza-backend-api_internal_models.Playlist" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permanently delete a playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Delete Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a track to the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Add Track to Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Track ID (in body)", + "name": "trackId", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "track_id": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Track already present or invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "Playlist or Track not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks/reorder": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Reorder tracks in the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Reorder Tracks", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "New Track Order", + "name": "order", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.ReorderTracksRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/playlists/{id}/tracks/{trackId}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Remove a track from the playlist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Playlist" + ], + "summary": "Remove Track from Playlist", + "parameters": [ + { + "type": "string", + "description": "Playlist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Track ID", + "name": "trackId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + ] + } + }, + "404": { + "description": "Playlist or Track not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/tracks": { + "get": { + "description": "Get a paginated list of tracks with filters", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "List Tracks", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "Filter by User ID", + "name": "user_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter by Genre", + "name": "genre", + "in": "query" + }, + { + "type": "string", + "description": "Filter by Format", + "name": "format", + "in": "query" + }, + { + "type": "string", + "default": "created_at", + "description": "Sort field", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "default": "desc", + "description": "Sort order (asc/desc)", + "name": "sort_order", + "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" + } + } + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Upload a new track (audio file)", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Upload Track", + "parameters": [ + { + "type": "file", + "description": "Audio File (MP3, WAV, FLAC, OGG)", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "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": "No file or validation error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Quota exceeded", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/batch/delete": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete multiple tracks at once", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Batch Delete Tracks", + "parameters": [ + { + "description": "List of Track IDs", + "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": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "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": [ + { + "BearerAuth": [] + } + ], + "description": "Upload a single chunk of a file", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Upload Chunk", + "parameters": [ + { + "type": "file", + "description": "Chunk Data", + "name": "chunk", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Upload ID", + "name": "upload_id", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Chunk Number", + "name": "chunk_number", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Total Chunks", + "name": "total_chunks", + "in": "formData", + "required": true + }, + { + "type": "integer", + "format": "int64", + "description": "Total Size", + "name": "total_size", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Filename", + "name": "filename", + "in": "formData", + "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" + }, + "progress": { + "type": "number", + "format": "float64" + }, + "received_chunks": { + "type": "integer" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/complete": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Finish upload session and assemble file", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Complete Chunked Upload", + "parameters": [ + { + "description": "Upload ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_core_track.CompleteChunkedUploadRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "md5": { + "type": "string" + }, + "message": { + "type": "string" + }, + "track": { + "$ref": "#/definitions/veza-backend-api_internal_models.Track" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation or Assemblage Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/initiate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Start a new chunked upload session", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Initiate Chunked Upload", + "parameters": [ + { + "description": "Upload Metadata", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_core_track.InitiateChunkedUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/quota/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get remaining upload quota for the user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Upload Quota", + "parameters": [ + { + "type": "string", + "description": "User ID (optional, defaults to current user)", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "quota": { + "type": "object" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/resume/{uploadId}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get state of an interrupted upload", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Resume Upload", + "parameters": [ + { + "type": "string", + "description": "Upload ID", + "name": "uploadId", + "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": { + "chunks_received": { + "type": "integer" + }, + "upload_id": { + "type": "string" + } + } + } + } + } + ] + } + }, + "404": { + "description": "Upload session not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/{id}": { + "get": { + "description": "Get detailed information about a track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Track by ID", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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 ID", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update track metadata", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Update Track", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Track Metadata", + "name": "track", + "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 Error", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Permanently delete a track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Delete Track", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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" + } + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/tracks/{id}/status": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get the processing status of an uploaded track", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Track" + ], + "summary": "Get Upload Status", + "parameters": [ + { + "type": "string", + "description": "Track ID", + "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": { + "progress": { + "type": "integer" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + }, + "404": { + "description": "Track not found", + "schema": { + "$ref": "#/definitions/veza-backend-api_internal_response.APIResponse" + } + } + } + } + }, + "/users/by-username/{username}": { + "get": { + "description": "Get public profile information for a user by username", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile by Username", + "parameters": [ + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Missing username", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/{id}": { + "get": { + "description": "Get public profile information for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile by ID", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "404": { + "description": "User not found", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update user profile details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Update Profile", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Profile Data", + "name": "profile", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers.UpdateProfileRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "profile": { + "type": "object" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } + }, + "/users/{id}/completion": { + "get": { + "description": "Get profile completion percentage and missing fields", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Get Profile Completion", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/internal_handlers.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + } + } + } + ] + } + }, + "400": { + "description": "Invalid ID", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/internal_handlers.APIResponse" + } + } + } + } } }, "definitions": { - "handlers.CreateOrderRequest": { + "internal_core_track.BatchDeleteRequest": { + "type": "object", + "required": [ + "track_ids" + ], + "properties": { + "track_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "internal_core_track.CompleteChunkedUploadRequest": { + "type": "object", + "required": [ + "upload_id" + ], + "properties": { + "upload_id": { + "type": "string" + } + } + }, + "internal_core_track.InitiateChunkedUploadRequest": { + "type": "object", + "required": [ + "filename", + "total_chunks", + "total_size" + ], + "properties": { + "filename": { + "type": "string" + }, + "total_chunks": { + "type": "integer", + "minimum": 1 + }, + "total_size": { + "type": "integer", + "minimum": 1 + } + } + }, + "internal_core_track.UpdateTrackRequest": { + "type": "object", + "properties": { + "album": { + "type": "string" + }, + "artist": { + "type": "string" + }, + "genre": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "year": { + "type": "integer" + } + } + }, + "internal_handlers.APIResponse": { + "type": "object", + "properties": { + "data": {}, + "error": {}, + "success": { + "type": "boolean" + } + } + }, + "internal_handlers.CreateOrderRequest": { "type": "object", "required": [ "items" @@ -253,7 +2568,26 @@ } } }, - "handlers.CreateProductRequest": { + "internal_handlers.CreatePlaylistRequest": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 200, + "minLength": 1 + } + } + }, + "internal_handlers.CreateProductRequest": { "type": "object", "required": [ "price", @@ -296,7 +2630,77 @@ } } }, - "marketplace.LicenseType": { + "internal_handlers.ReorderTracksRequest": { + "type": "object", + "required": [ + "track_ids" + ], + "properties": { + "track_ids": { + "description": "Changed to []uuid.UUID", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } + }, + "internal_handlers.UpdatePlaylistRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 200, + "minLength": 1 + } + } + }, + "internal_handlers.UpdateProfileRequest": { + "type": "object", + "properties": { + "bio": { + "type": "string", + "maxLength": 500 + }, + "birthdate": { + "type": "string" + }, + "first_name": { + "type": "string", + "maxLength": 100 + }, + "gender": { + "type": "string", + "enum": [ + "Male", + "Female", + "Other", + "Prefer not to say" + ] + }, + "last_name": { + "type": "string", + "maxLength": 100 + }, + "location": { + "type": "string", + "maxLength": 100 + }, + "username": { + "type": "string", + "maxLength": 30, + "minLength": 3 + } + } + }, + "veza-backend-api_internal_core_marketplace.LicenseType": { "type": "string", "enum": [ "basic", @@ -309,7 +2713,7 @@ "LicenseExclusive" ] }, - "marketplace.Order": { + "veza-backend-api_internal_core_marketplace.Order": { "type": "object", "properties": { "buyer_id": { @@ -327,7 +2731,7 @@ "items": { "type": "array", "items": { - "$ref": "#/definitions/marketplace.OrderItem" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.OrderItem" } }, "payment_intent": { @@ -346,7 +2750,7 @@ } } }, - "marketplace.OrderItem": { + "veza-backend-api_internal_core_marketplace.OrderItem": { "type": "object", "properties": { "id": { @@ -363,7 +2767,7 @@ } } }, - "marketplace.Product": { + "veza-backend-api_internal_core_marketplace.Product": { "type": "object", "properties": { "created_at": { @@ -379,7 +2783,7 @@ "type": "string" }, "license_type": { - "$ref": "#/definitions/marketplace.LicenseType" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.LicenseType" }, "price": { "type": "number" @@ -392,7 +2796,7 @@ "type": "string" }, "status": { - "$ref": "#/definitions/marketplace.ProductStatus" + "$ref": "#/definitions/veza-backend-api_internal_core_marketplace.ProductStatus" }, "title": { "type": "string" @@ -406,7 +2810,7 @@ } } }, - "marketplace.ProductStatus": { + "veza-backend-api_internal_core_marketplace.ProductStatus": { "type": "string", "enum": [ "draft", @@ -418,6 +2822,410 @@ "ProductStatusActive", "ProductStatusArchived" ] + }, + "veza-backend-api_internal_dto.LoginRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "remember_me": { + "type": "boolean" + } + } + }, + "veza-backend-api_internal_dto.LoginResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_dto.UserResponse" + } + } + }, + "veza-backend-api_internal_dto.RefreshRequest": { + "type": "object", + "required": [ + "refresh_token" + ], + "properties": { + "refresh_token": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.RegisterRequest": { + "type": "object", + "required": [ + "email", + "password", + "password_confirm" + ], + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string", + "minLength": 12 + }, + "password_confirm": { + "type": "string" + }, + "username": { + "type": "string", + "maxLength": 50, + "minLength": 3 + } + } + }, + "veza-backend-api_internal_dto.RegisterResponse": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/veza-backend-api_internal_dto.TokenResponse" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_dto.UserResponse" + } + } + }, + "veza-backend-api_internal_dto.ResendVerificationRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.TokenResponse": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "refresh_token": { + "type": "string" + } + } + }, + "veza-backend-api_internal_dto.UserResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.Playlist": { + "type": "object", + "properties": { + "collaborators": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistCollaborator" + } + }, + "cover_url": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "follower_count": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "track_count": { + "type": "integer" + }, + "tracks": { + "type": "array", + "items": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistTrack" + } + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.PlaylistCollaborator": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "permission": { + "$ref": "#/definitions/veza-backend-api_internal_models.PlaylistPermission" + }, + "playlist_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/veza-backend-api_internal_models.User" + }, + "user_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.PlaylistPermission": { + "type": "string", + "enum": [ + "read", + "write", + "admin" + ], + "x-enum-varnames": [ + "PlaylistPermissionRead", + "PlaylistPermissionWrite", + "PlaylistPermissionAdmin" + ] + }, + "veza-backend-api_internal_models.PlaylistTrack": { + "type": "object", + "properties": { + "added_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "playlist_id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "track": { + "$ref": "#/definitions/veza-backend-api_internal_models.Track" + }, + "track_id": { + "type": "string" + } + } + }, + "veza-backend-api_internal_models.Track": { + "type": "object", + "properties": { + "album": { + "type": "string" + }, + "artist": { + "type": "string" + }, + "bitrate": { + "description": "kbps", + "type": "integer" + }, + "cover_art_path": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "duration": { + "description": "seconds", + "type": "integer" + }, + "file_path": { + "type": "string" + }, + "file_size": { + "description": "bytes", + "type": "integer" + }, + "format": { + "description": "mp3, flac, wav, etc.", + "type": "string" + }, + "genre": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "like_count": { + "type": "integer" + }, + "play_count": { + "type": "integer" + }, + "sample_rate": { + "description": "Hz", + "type": "integer" + }, + "status": { + "$ref": "#/definitions/veza-backend-api_internal_models.TrackStatus" + }, + "status_message": { + "type": "string" + }, + "stream_manifest_url": { + "type": "string" + }, + "stream_status": { + "description": "pending, processing, ready, error", + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "waveform_path": { + "type": "string" + }, + "year": { + "type": "integer" + } + } + }, + "veza-backend-api_internal_models.TrackStatus": { + "type": "string", + "enum": [ + "uploading", + "processing", + "completed", + "failed" + ], + "x-enum-varnames": [ + "TrackStatusUploading", + "TrackStatusProcessing", + "TrackStatusCompleted", + "TrackStatusFailed" + ] + }, + "veza-backend-api_internal_models.User": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "birthdate": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_admin": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "is_verified": { + "type": "boolean" + }, + "last_login_at": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "password": { + "description": "Virtual field for input", + "type": "string" + }, + "role": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "token_version": { + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "username_changed_at": { + "type": "string" + } + } + }, + "veza-backend-api_internal_response.APIResponse": { + "type": "object", + "properties": { + "data": {}, + "error": {}, + "success": { + "type": "boolean" + } + } } }, "securityDefinitions": { diff --git a/veza-backend-api/docs/swagger.yaml b/veza-backend-api/docs/swagger.yaml index ff16b7b94..d2e6e94eb 100644 --- a/veza-backend-api/docs/swagger.yaml +++ b/veza-backend-api/docs/swagger.yaml @@ -1,6 +1,59 @@ basePath: /api/v1 definitions: - handlers.CreateOrderRequest: + internal_core_track.BatchDeleteRequest: + properties: + track_ids: + items: + type: string + type: array + required: + - track_ids + type: object + internal_core_track.CompleteChunkedUploadRequest: + properties: + upload_id: + type: string + required: + - upload_id + type: object + internal_core_track.InitiateChunkedUploadRequest: + properties: + filename: + type: string + total_chunks: + minimum: 1 + type: integer + total_size: + minimum: 1 + type: integer + required: + - filename + - total_chunks + - total_size + type: object + internal_core_track.UpdateTrackRequest: + properties: + album: + type: string + artist: + type: string + genre: + type: string + is_public: + type: boolean + title: + type: string + year: + type: integer + type: object + internal_handlers.APIResponse: + properties: + data: {} + error: {} + success: + type: boolean + type: object + internal_handlers.CreateOrderRequest: properties: items: items: @@ -15,7 +68,20 @@ definitions: required: - items type: object - handlers.CreateProductRequest: + internal_handlers.CreatePlaylistRequest: + properties: + description: + type: string + is_public: + type: boolean + title: + maxLength: 200 + minLength: 1 + type: string + required: + - title + type: object + internal_handlers.CreateProductRequest: properties: description: maxLength: 2000 @@ -47,7 +113,57 @@ definitions: - product_type - title type: object - marketplace.LicenseType: + internal_handlers.ReorderTracksRequest: + properties: + track_ids: + description: Changed to []uuid.UUID + items: + type: string + minItems: 1 + type: array + required: + - track_ids + type: object + internal_handlers.UpdatePlaylistRequest: + properties: + description: + type: string + is_public: + type: boolean + title: + maxLength: 200 + minLength: 1 + type: string + type: object + internal_handlers.UpdateProfileRequest: + properties: + bio: + maxLength: 500 + type: string + birthdate: + type: string + first_name: + maxLength: 100 + type: string + gender: + enum: + - Male + - Female + - Other + - Prefer not to say + type: string + last_name: + maxLength: 100 + type: string + location: + maxLength: 100 + type: string + username: + maxLength: 30 + minLength: 3 + type: string + type: object + veza-backend-api_internal_core_marketplace.LicenseType: enum: - basic - premium @@ -57,7 +173,7 @@ definitions: - LicenseBasic - LicensePremium - LicenseExclusive - marketplace.Order: + veza-backend-api_internal_core_marketplace.Order: properties: buyer_id: type: string @@ -69,7 +185,7 @@ definitions: type: string items: items: - $ref: '#/definitions/marketplace.OrderItem' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.OrderItem' type: array payment_intent: description: Stripe PaymentIntent ID @@ -82,7 +198,7 @@ definitions: updated_at: type: string type: object - marketplace.OrderItem: + veza-backend-api_internal_core_marketplace.OrderItem: properties: id: type: string @@ -93,7 +209,7 @@ definitions: product_id: type: string type: object - marketplace.Product: + veza-backend-api_internal_core_marketplace.Product: properties: created_at: type: string @@ -104,7 +220,7 @@ definitions: id: type: string license_type: - $ref: '#/definitions/marketplace.LicenseType' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.LicenseType' price: type: number product_type: @@ -113,7 +229,7 @@ definitions: seller_id: type: string status: - $ref: '#/definitions/marketplace.ProductStatus' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.ProductStatus' title: type: string track_id: @@ -122,7 +238,7 @@ definitions: updated_at: type: string type: object - marketplace.ProductStatus: + veza-backend-api_internal_core_marketplace.ProductStatus: enum: - draft - active @@ -132,6 +248,279 @@ definitions: - ProductStatusDraft - ProductStatusActive - ProductStatusArchived + veza-backend-api_internal_dto.LoginRequest: + properties: + email: + type: string + password: + type: string + remember_me: + type: boolean + required: + - email + - password + type: object + veza-backend-api_internal_dto.LoginResponse: + properties: + token: + $ref: '#/definitions/veza-backend-api_internal_dto.TokenResponse' + user: + $ref: '#/definitions/veza-backend-api_internal_dto.UserResponse' + type: object + veza-backend-api_internal_dto.RefreshRequest: + properties: + refresh_token: + type: string + required: + - refresh_token + type: object + veza-backend-api_internal_dto.RegisterRequest: + properties: + email: + type: string + password: + minLength: 12 + type: string + password_confirm: + type: string + username: + maxLength: 50 + minLength: 3 + type: string + required: + - email + - password + - password_confirm + type: object + veza-backend-api_internal_dto.RegisterResponse: + properties: + token: + $ref: '#/definitions/veza-backend-api_internal_dto.TokenResponse' + user: + $ref: '#/definitions/veza-backend-api_internal_dto.UserResponse' + type: object + veza-backend-api_internal_dto.ResendVerificationRequest: + properties: + email: + type: string + required: + - email + type: object + veza-backend-api_internal_dto.TokenResponse: + properties: + access_token: + type: string + expires_in: + type: integer + refresh_token: + type: string + type: object + veza-backend-api_internal_dto.UserResponse: + properties: + email: + type: string + id: + type: string + username: + type: string + type: object + veza-backend-api_internal_models.Playlist: + properties: + collaborators: + items: + $ref: '#/definitions/veza-backend-api_internal_models.PlaylistCollaborator' + type: array + cover_url: + type: string + created_at: + type: string + description: + type: string + follower_count: + type: integer + id: + type: string + is_public: + type: boolean + title: + type: string + track_count: + type: integer + tracks: + items: + $ref: '#/definitions/veza-backend-api_internal_models.PlaylistTrack' + type: array + updated_at: + type: string + user_id: + type: string + type: object + veza-backend-api_internal_models.PlaylistCollaborator: + properties: + created_at: + type: string + id: + type: string + permission: + $ref: '#/definitions/veza-backend-api_internal_models.PlaylistPermission' + playlist_id: + type: string + updated_at: + type: string + user: + $ref: '#/definitions/veza-backend-api_internal_models.User' + user_id: + type: string + type: object + veza-backend-api_internal_models.PlaylistPermission: + enum: + - read + - write + - admin + type: string + x-enum-varnames: + - PlaylistPermissionRead + - PlaylistPermissionWrite + - PlaylistPermissionAdmin + veza-backend-api_internal_models.PlaylistTrack: + properties: + added_at: + type: string + id: + type: string + playlist_id: + type: string + position: + type: integer + track: + $ref: '#/definitions/veza-backend-api_internal_models.Track' + track_id: + type: string + type: object + veza-backend-api_internal_models.Track: + properties: + album: + type: string + artist: + type: string + bitrate: + description: kbps + type: integer + cover_art_path: + type: string + created_at: + type: string + duration: + description: seconds + type: integer + file_path: + type: string + file_size: + description: bytes + type: integer + format: + description: mp3, flac, wav, etc. + type: string + genre: + type: string + id: + type: string + is_public: + type: boolean + like_count: + type: integer + play_count: + type: integer + sample_rate: + description: Hz + type: integer + status: + $ref: '#/definitions/veza-backend-api_internal_models.TrackStatus' + status_message: + type: string + stream_manifest_url: + type: string + stream_status: + description: pending, processing, ready, error + type: string + title: + type: string + updated_at: + type: string + user_id: + type: string + waveform_path: + type: string + year: + type: integer + type: object + veza-backend-api_internal_models.TrackStatus: + enum: + - uploading + - processing + - completed + - failed + type: string + x-enum-varnames: + - TrackStatusUploading + - TrackStatusProcessing + - TrackStatusCompleted + - TrackStatusFailed + veza-backend-api_internal_models.User: + properties: + avatar: + type: string + bio: + type: string + birthdate: + type: string + created_at: + type: string + email: + type: string + first_name: + type: string + gender: + type: string + id: + type: string + is_active: + type: boolean + is_admin: + type: boolean + is_public: + type: boolean + is_verified: + type: boolean + last_login_at: + type: string + last_name: + type: string + location: + type: string + password: + description: Virtual field for input + type: string + role: + type: string + slug: + type: string + token_version: + type: integer + updated_at: + type: string + username: + type: string + username_changed_at: + type: string + type: object + veza-backend-api_internal_response.APIResponse: + properties: + data: {} + error: {} + success: + type: boolean + type: object host: localhost:8080 info: contact: @@ -194,14 +583,14 @@ paths: name: order required: true schema: - $ref: '#/definitions/handlers.CreateOrderRequest' + $ref: '#/definitions/internal_handlers.CreateOrderRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/marketplace.Order' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.Order' "400": description: Bad Request schema: @@ -240,7 +629,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/marketplace.Product' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.Product' type: array summary: List products tags: @@ -255,14 +644,14 @@ paths: name: product required: true schema: - $ref: '#/definitions/handlers.CreateProductRequest' + $ref: '#/definitions/internal_handlers.CreateProductRequest' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/marketplace.Product' + $ref: '#/definitions/veza-backend-api_internal_core_marketplace.Product' "400": description: Bad Request schema: @@ -280,6 +669,1369 @@ paths: summary: Create a new product tags: - Marketplace + /auth/check-username: + get: + consumes: + - application/json + description: Check if a username is already taken + parameters: + - description: Username to check + in: query + name: username + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + available: + type: boolean + username: + type: string + type: object + type: object + "400": + description: Missing Username + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Check Username Availability + tags: + - Auth + /auth/login: + post: + consumes: + - application/json + description: Authenticate user and return access/refresh tokens + parameters: + - description: Login Credentials + in: body + name: request + required: true + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.LoginResponse' + "400": + description: Validation or Bad Request + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Invalid credentials + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: User Login + tags: + - Auth + /auth/logout: + post: + consumes: + - application/json + description: Revoke refresh token and current session + parameters: + - description: Refresh Token to revoke + in: body + name: request + required: true + schema: + properties: + refresh_token: + type: string + type: object + produces: + - application/json + responses: + "200": + description: Success message + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Logout + tags: + - Auth + /auth/me: + get: + consumes: + - application/json + description: Get profile information of the currently logged-in user + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + email: + type: string + id: + type: string + role: + type: string + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get Current User + tags: + - Auth + /auth/refresh: + post: + consumes: + - application/json + description: Get a new access token using a refresh token + parameters: + - description: Refresh Token + in: body + name: request + required: true + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.RefreshRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.TokenResponse' + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Invalid/Expired Refresh Token + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Refresh Token + tags: + - Auth + /auth/register: + post: + consumes: + - application/json + description: Register a new user account + parameters: + - description: Registration Data + in: body + name: request + required: true + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.RegisterRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.RegisterResponse' + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "409": + description: User already exists + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: User Registration + tags: + - Auth + /auth/resend-verification: + post: + consumes: + - application/json + description: Resend the email verification link + parameters: + - description: Email + in: body + name: request + required: true + schema: + $ref: '#/definitions/veza-backend-api_internal_dto.ResendVerificationRequest' + produces: + - application/json + responses: + "200": + description: Success message + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Resend Verification Email + tags: + - Auth + /auth/verify-email: + post: + consumes: + - application/json + description: Verify user email address using a token + parameters: + - description: Verification Token + in: query + name: token + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success message + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "400": + description: Invalid Token + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Verify Email + tags: + - Auth + /chat/token: + get: + consumes: + - application/json + description: Generate a short-lived token for chat authentication + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + token: + type: string + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get Chat Token + tags: + - Chat + /playlists: + get: + consumes: + - application/json + description: Get a paginated list of playlists + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 20 + description: Items per page + in: query + name: limit + type: integer + - description: Filter by User ID + in: query + name: user_id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + pagination: + type: object + playlists: + items: + $ref: '#/definitions/veza-backend-api_internal_models.Playlist' + type: array + type: object + type: object + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get Playlists + tags: + - Playlist + post: + consumes: + - application/json + description: Create a new playlist + parameters: + - description: Playlist Metadata + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers.CreatePlaylistRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + playlist: + $ref: '#/definitions/veza-backend-api_internal_models.Playlist' + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "500": + description: Internal Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Create Playlist + tags: + - Playlist + /playlists/{id}: + delete: + consumes: + - application/json + description: Permanently delete a playlist + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: Playlist not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Delete Playlist + tags: + - Playlist + get: + consumes: + - application/json + description: Get detailed information about a playlist + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + playlist: + $ref: '#/definitions/veza-backend-api_internal_models.Playlist' + type: object + type: object + "400": + description: Invalid ID + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: Playlist not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Get Playlist by ID + tags: + - Playlist + put: + consumes: + - application/json + description: Update playlist metadata + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + - description: Playlist Metadata + in: body + name: playlist + required: true + schema: + $ref: '#/definitions/internal_handlers.UpdatePlaylistRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + playlist: + $ref: '#/definitions/veza-backend-api_internal_models.Playlist' + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: Playlist not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Update Playlist + tags: + - Playlist + /playlists/{id}/tracks: + post: + consumes: + - application/json + description: Add a track to the playlist + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + - description: Track ID (in body) + in: body + name: trackId + required: true + schema: + properties: + track_id: + type: string + type: object + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Track already present or invalid ID + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: Playlist or Track not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Add Track to Playlist + tags: + - Playlist + /playlists/{id}/tracks/{trackId}: + delete: + consumes: + - application/json + description: Remove a track from the playlist + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + - description: Track ID + in: path + name: trackId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "404": + description: Playlist or Track not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Remove Track from Playlist + tags: + - Playlist + /playlists/{id}/tracks/reorder: + put: + consumes: + - application/json + description: Reorder tracks in the playlist + parameters: + - description: Playlist ID + in: path + name: id + required: true + type: string + - description: New Track Order + in: body + name: order + required: true + schema: + $ref: '#/definitions/internal_handlers.ReorderTracksRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + message: + type: string + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Reorder Tracks + tags: + - Playlist + /tracks: + get: + consumes: + - application/json + description: Get a paginated list of tracks with filters + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 20 + description: Items per page + in: query + name: limit + type: integer + - description: Filter by User ID + in: query + name: user_id + type: string + - description: Filter by Genre + in: query + name: genre + type: string + - description: Filter by Format + in: query + name: format + type: string + - default: created_at + description: Sort field + in: query + name: sort_by + type: string + - default: desc + description: Sort order (asc/desc) + in: query + name: sort_order + 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 + "500": + description: Internal Error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + summary: List Tracks + tags: + - Track + post: + consumes: + - multipart/form-data + description: Upload a new track (audio file) + parameters: + - description: Audio File (MP3, WAV, FLAC, OGG) + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "201": + description: Created + 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: No file or validation error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "403": + description: Quota exceeded + 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: Upload Track + tags: + - Track + /tracks/{id}: + delete: + consumes: + - application/json + description: Permanently delete a track + parameters: + - description: Track ID + 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 + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "404": + description: Track not found + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Delete Track + tags: + - Track + get: + consumes: + - application/json + description: Get detailed information about a track + parameters: + - description: Track ID + 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 ID + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "404": + description: Track not found + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + summary: Get Track by ID + tags: + - Track + put: + consumes: + - application/json + description: Update track metadata + parameters: + - description: Track ID + in: path + name: id + required: true + type: string + - description: Track Metadata + in: body + name: track + 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 Error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "404": + description: Track not found + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Update Track + tags: + - Track + /tracks/{id}/status: + get: + consumes: + - application/json + description: Get the processing status of an uploaded track + parameters: + - description: Track ID + 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: + progress: + type: integer + type: object + type: object + "400": + description: Invalid ID + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "404": + description: Track not found + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Get Upload Status + tags: + - Track + /tracks/batch/delete: + post: + consumes: + - application/json + description: Delete multiple tracks at once + parameters: + - description: List of Track IDs + 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: + type: object + type: object + type: object + "400": + description: Validation Error + 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/chunk: + post: + consumes: + - multipart/form-data + description: Upload a single chunk of a file + parameters: + - description: Chunk Data + in: formData + name: chunk + required: true + type: file + - description: Upload ID + in: formData + name: upload_id + required: true + type: string + - description: Chunk Number + in: formData + name: chunk_number + required: true + type: integer + - description: Total Chunks + in: formData + name: total_chunks + required: true + type: integer + - description: Total Size + format: int64 + in: formData + name: total_size + required: true + type: integer + - description: Filename + in: formData + name: filename + 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 + progress: + format: float64 + type: number + received_chunks: + type: integer + upload_id: + type: string + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Upload Chunk + tags: + - Track + /tracks/complete: + post: + consumes: + - application/json + description: Finish upload session and assemble file + parameters: + - description: Upload ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_core_track.CompleteChunkedUploadRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + - properties: + data: + properties: + md5: + type: string + message: + type: string + track: + $ref: '#/definitions/veza-backend-api_internal_models.Track' + type: object + type: object + "400": + description: Validation or Assemblage Error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Complete Chunked Upload + tags: + - Track + /tracks/initiate: + post: + consumes: + - application/json + description: Start a new chunked upload session + parameters: + - description: Upload Metadata + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_core_track.InitiateChunkedUploadRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + - properties: + data: + properties: + message: + type: string + upload_id: + type: string + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Initiate Chunked Upload + tags: + - Track + /tracks/quota/{id}: + get: + consumes: + - application/json + description: Get remaining upload quota for the user + parameters: + - description: User ID (optional, defaults to current user) + in: path + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + - properties: + data: + properties: + quota: + type: object + type: object + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Get Upload Quota + tags: + - Track + /tracks/resume/{uploadId}: + get: + consumes: + - application/json + description: Get state of an interrupted upload + parameters: + - description: Upload ID + in: path + name: uploadId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + - properties: + data: + properties: + chunks_received: + type: integer + upload_id: + type: string + type: object + type: object + "404": + description: Upload session not found + schema: + $ref: '#/definitions/veza-backend-api_internal_response.APIResponse' + security: + - BearerAuth: [] + summary: Resume Upload + tags: + - Track + /users/{id}: + get: + consumes: + - application/json + description: Get public profile information for a user + parameters: + - description: User ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + profile: + type: object + type: object + type: object + "400": + description: Invalid ID + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Get Profile by ID + tags: + - User + put: + consumes: + - application/json + description: Update user profile details + parameters: + - description: User ID + in: path + name: id + required: true + type: string + - description: Profile Data + in: body + name: profile + required: true + schema: + $ref: '#/definitions/internal_handlers.UpdateProfileRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + profile: + type: object + type: object + type: object + "400": + description: Validation Error + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + security: + - BearerAuth: [] + summary: Update Profile + tags: + - User + /users/{id}/completion: + get: + consumes: + - application/json + description: Get profile completion percentage and missing fields + parameters: + - description: User ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + type: object + type: object + "400": + description: Invalid ID + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Get Profile Completion + tags: + - User + /users/by-username/{username}: + get: + consumes: + - application/json + description: Get public profile information for a user by username + parameters: + - description: Username + in: path + name: username + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/internal_handlers.APIResponse' + - properties: + data: + properties: + profile: + type: object + type: object + type: object + "400": + description: Missing username + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + "404": + description: User not found + schema: + $ref: '#/definitions/internal_handlers.APIResponse' + summary: Get Profile by Username + tags: + - User securityDefinitions: BearerAuth: in: header diff --git a/veza-backend-api/internal/core/track/handler.go b/veza-backend-api/internal/core/track/handler.go index bfad5ecb4..74e9e563f 100644 --- a/veza-backend-api/internal/core/track/handler.go +++ b/veza-backend-api/internal/core/track/handler.go @@ -16,7 +16,9 @@ import ( "gorm.io/gorm" "veza-backend-api/internal/models" "veza-backend-api/internal/services" + "veza-backend-api/internal/services" "veza-backend-api/internal/validators" + "veza-backend-api/internal/response" ) // TrackHandler gère les opérations sur les tracks @@ -70,6 +72,19 @@ func (h *TrackHandler) SetHistoryService(historyService *services.TrackHistorySe } // UploadTrack gère l'upload d'un fichier audio +// @Summary Upload Track +// @Description Upload a new track (audio file) +// @Tags Track +// @Accept multipart/form-data +// @Produce json +// @Security BearerAuth +// @Param file formData file true "Audio File (MP3, WAV, FLAC, OGG)" +// @Success 201 {object} response.APIResponse{data=object{track=models.Track}} +// @Failure 400 {object} response.APIResponse "No file or validation error" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 403 {object} response.APIResponse "Quota exceeded" +// @Failure 500 {object} response.APIResponse "Internal Error" +// @Router /tracks [post] func (h *TrackHandler) UploadTrack(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -107,6 +122,18 @@ func (h *TrackHandler) UploadTrack(c *gin.Context) { } // GetUploadStatus récupère le statut d'upload d'un track +// @Summary Get Upload Status +// @Description Get the processing status of an uploaded track +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Track ID" +// @Success 200 {object} response.APIResponse{data=object{progress=int}} +// @Failure 400 {object} response.APIResponse "Invalid ID" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 404 {object} response.APIResponse "Track not found" +// @Router /tracks/{id}/status [get] func (h *TrackHandler) GetUploadStatus(c *gin.Context) { trackIDStr := c.Param("id") if trackIDStr == "" { @@ -168,6 +195,17 @@ type InitiateChunkedUploadRequest struct { } // InitiateChunkedUpload initialise un nouvel upload par chunks +// @Summary Initiate Chunked Upload +// @Description Start a new chunked upload session +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body InitiateChunkedUploadRequest true "Upload Metadata" +// @Success 200 {object} response.APIResponse{data=object{upload_id=string,message=string}} +// @Failure 400 {object} response.APIResponse "Validation Error" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Router /tracks/initiate [post] func (h *TrackHandler) InitiateChunkedUpload(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -214,6 +252,22 @@ type UploadChunkRequest struct { } // UploadChunk gère l'upload d'un chunk +// @Summary Upload Chunk +// @Description Upload a single chunk of a file +// @Tags Track +// @Accept multipart/form-data +// @Produce json +// @Security BearerAuth +// @Param chunk formData file true "Chunk Data" +// @Param upload_id formData string true "Upload ID" +// @Param chunk_number formData int true "Chunk Number" +// @Param total_chunks formData int true "Total Chunks" +// @Param total_size formData int64 true "Total Size" +// @Param filename formData string true "Filename" +// @Success 200 {object} response.APIResponse{data=object{message=string,upload_id=string,received_chunks=int,progress=float64}} +// @Failure 400 {object} response.APIResponse "Validation Error" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Router /tracks/chunk [post] func (h *TrackHandler) UploadChunk(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -261,6 +315,17 @@ type CompleteChunkedUploadRequest struct { } // CompleteChunkedUpload assemble tous les chunks et crée le track final +// @Summary Complete Chunked Upload +// @Description Finish upload session and assemble file +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body CompleteChunkedUploadRequest true "Upload ID" +// @Success 201 {object} response.APIResponse{data=object{message=string,track=models.Track,md5=string}} +// @Failure 400 {object} response.APIResponse "Validation or Assemblage Error" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Router /tracks/complete [post] func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -441,6 +506,17 @@ func (h *TrackHandler) getErrorStatusCode(err error) int { } // GetUploadQuota récupère les informations de quota d'upload pour un utilisateur +// @Summary Get Upload Quota +// @Description Get remaining upload quota for the user +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string false "User ID (optional, defaults to current user)" +// @Success 200 {object} response.APIResponse{data=object{quota=object}} +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 403 {object} response.APIResponse "Forbidden" +// @Router /tracks/quota/{id} [get] func (h *TrackHandler) GetUploadQuota(c *gin.Context) { // Récupérer l'ID utilisateur depuis l'URL ou depuis le contexte d'authentification userIDParam := c.Param("id") @@ -489,6 +565,16 @@ func (h *TrackHandler) GetUploadQuota(c *gin.Context) { } // ResumeUpload récupère l'état d'un upload pour permettre la reprise +// @Summary Resume Upload +// @Description Get state of an interrupted upload +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param uploadId path string true "Upload ID" +// @Success 200 {object} response.APIResponse{data=object{upload_id=string,chunks_received=int}} +// @Failure 404 {object} response.APIResponse "Upload session not found" +// @Router /tracks/resume/{uploadId} [get] func (h *TrackHandler) ResumeUpload(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -531,6 +617,21 @@ func (h *TrackHandler) ResumeUpload(c *gin.Context) { } // ListTracks gère la liste des tracks avec pagination, filtres et tri +// @Summary List Tracks +// @Description Get a paginated list of tracks with filters +// @Tags Track +// @Accept json +// @Produce json +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(20) +// @Param user_id query string false "Filter by User ID" +// @Param genre query string false "Filter by Genre" +// @Param format query string false "Filter by Format" +// @Param sort_by query string false "Sort field" default(created_at) +// @Param sort_order query string false "Sort order (asc/desc)" default(desc) +// @Success 200 {object} response.APIResponse{data=object{tracks=[]models.Track,pagination=object}} +// @Failure 500 {object} response.APIResponse "Internal Error" +// @Router /tracks [get] func (h *TrackHandler) ListTracks(c *gin.Context) { // Récupérer les paramètres de query page := c.DefaultQuery("page", "1") @@ -608,6 +709,16 @@ func (h *TrackHandler) ListTracks(c *gin.Context) { } // GetTrack gère la récupération d'un track par son ID +// @Summary Get Track by ID +// @Description Get detailed information about a track +// @Tags Track +// @Accept json +// @Produce json +// @Param id path string true "Track ID" +// @Success 200 {object} response.APIResponse{data=object{track=models.Track}} +// @Failure 400 {object} response.APIResponse "Invalid ID" +// @Failure 404 {object} response.APIResponse "Track not found" +// @Router /tracks/{id} [get] func (h *TrackHandler) GetTrack(c *gin.Context) { trackIDStr := c.Param("id") if trackIDStr == "" { @@ -652,6 +763,20 @@ type UpdateTrackRequest struct { } // UpdateTrack gère la mise à jour d'un track +// @Summary Update Track +// @Description Update track metadata +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Track ID" +// @Param track body UpdateTrackRequest true "Track Metadata" +// @Success 200 {object} response.APIResponse{data=object{track=models.Track}} +// @Failure 400 {object} response.APIResponse "Validation Error" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 403 {object} response.APIResponse "Forbidden" +// @Failure 404 {object} response.APIResponse "Track not found" +// @Router /tracks/{id} [put] func (h *TrackHandler) UpdateTrack(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -711,6 +836,18 @@ func (h *TrackHandler) UpdateTrack(c *gin.Context) { } // DeleteTrack gère la suppression d'un track +// @Summary Delete Track +// @Description Permanently delete a track +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Track ID" +// @Success 200 {object} response.APIResponse{data=object{message=string}} +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 403 {object} response.APIResponse "Forbidden" +// @Failure 404 {object} response.APIResponse "Track not found" +// @Router /tracks/{id} [delete] func (h *TrackHandler) DeleteTrack(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { @@ -754,6 +891,17 @@ type BatchDeleteRequest struct { } // BatchDeleteTracks gère la suppression en lot de plusieurs tracks +// @Summary Batch Delete Tracks +// @Description Delete multiple tracks at once +// @Tags Track +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body BatchDeleteRequest true "List of Track IDs" +// @Success 200 {object} response.APIResponse{data=object{deleted=[]string,failed=object}} +// @Failure 400 {object} response.APIResponse "Validation Error" +// @Failure 500 {object} response.APIResponse "Internal Error" +// @Router /tracks/batch/delete [post] func (h *TrackHandler) BatchDeleteTracks(c *gin.Context) { userID := c.MustGet("user_id").(uuid.UUID) if userID == uuid.Nil { diff --git a/veza-backend-api/internal/handlers/auth.go b/veza-backend-api/internal/handlers/auth.go index 9dfb0310b..d62191ab7 100644 --- a/veza-backend-api/internal/handlers/auth.go +++ b/veza-backend-api/internal/handlers/auth.go @@ -17,8 +17,17 @@ import ( ) // Login gère la connexion des utilisateurs -// T0203: Intègre création de session après login avec IP et User-Agent -// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs +// @Summary User Login +// @Description Authenticate user and return access/refresh tokens +// @Tags Auth +// @Accept json +// @Produce json +// @Param request body dto.LoginRequest true "Login Credentials" +// @Success 200 {object} dto.LoginResponse +// @Failure 400 {object} handlers.APIResponse "Validation or Bad Request" +// @Failure 401 {object} handlers.APIResponse "Invalid credentials" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /auth/login [post] func Login(authService *auth.AuthService, sessionService *services.SessionService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) @@ -94,8 +103,17 @@ func Login(authService *auth.AuthService, sessionService *services.SessionServic } // Register gère l'inscription des utilisateurs -// GO-013: Utilise validator centralisé pour validation améliorée -// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs +// @Summary User Registration +// @Description Register a new user account +// @Tags Auth +// @Accept json +// @Produce json +// @Param request body dto.RegisterRequest true "Registration Data" +// @Success 201 {object} dto.RegisterResponse +// @Failure 400 {object} handlers.APIResponse "Validation Error" +// @Failure 409 {object} handlers.APIResponse "User already exists" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /auth/register [post] func Register(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) @@ -131,8 +149,17 @@ func Register(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc } // Refresh gère le rafraîchissement d'un access token -// GO-013: Utilise validator centralisé pour validation améliorée -// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs +// @Summary Refresh Token +// @Description Get a new access token using a refresh token +// @Tags Auth +// @Accept json +// @Produce json +// @Param request body dto.RefreshRequest true "Refresh Token" +// @Success 200 {object} dto.TokenResponse +// @Failure 400 {object} handlers.APIResponse "Validation Error" +// @Failure 401 {object} handlers.APIResponse "Invalid/Expired Refresh Token" +// @Failure 500 {object} handlers.APIResponse "Internal Error" +// @Router /auth/refresh [post] func Refresh(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) @@ -164,7 +191,17 @@ func Refresh(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc } // Logout gère la déconnexion des utilisateurs -// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs +// @Summary Logout +// @Description Revoke refresh token and current session +// @Tags Auth +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body object{refresh_token=string} true "Refresh Token to revoke" +// @Success 200 {object} handlers.APIResponse "Success message" +// @Failure 400 {object} handlers.APIResponse "Validation Error" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Router /auth/logout [post] func Logout(authService *auth.AuthService, sessionService *services.SessionService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) @@ -208,6 +245,15 @@ func Logout(authService *auth.AuthService, sessionService *services.SessionServi } // VerifyEmail gère la vérification de l'email +// @Summary Verify Email +// @Description Verify user email address using a token +// @Tags Auth +// @Accept json +// @Produce json +// @Param token query string true "Verification Token" +// @Success 200 {object} handlers.APIResponse "Success message" +// @Failure 400 {object} handlers.APIResponse "Invalid Token" +// @Router /auth/verify-email [post] func VerifyEmail(authService *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { token := c.Query("token") @@ -226,7 +272,15 @@ func VerifyEmail(authService *auth.AuthService) gin.HandlerFunc { } // ResendVerification gère la demande de renvoi d'email de vérification -// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs +// @Summary Resend Verification Email +// @Description Resend the email verification link +// @Tags Auth +// @Accept json +// @Produce json +// @Param request body dto.ResendVerificationRequest true "Email" +// @Success 200 {object} handlers.APIResponse "Success message" +// @Failure 400 {object} handlers.APIResponse "Validation Error" +// @Router /auth/resend-verification [post] func ResendVerification(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) @@ -248,6 +302,15 @@ func ResendVerification(authService *auth.AuthService, logger *zap.Logger) gin.H } // CheckUsername vérifie la disponibilité d'un nom d'utilisateur +// @Summary Check Username Availability +// @Description Check if a username is already taken +// @Tags Auth +// @Accept json +// @Produce json +// @Param username query string true "Username to check" +// @Success 200 {object} handlers.APIResponse{data=object{available=boolean,username=string}} +// @Failure 400 {object} handlers.APIResponse "Missing Username" +// @Router /auth/check-username [get] func CheckUsername(authService *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { username := c.Query("username") @@ -267,6 +330,15 @@ func CheckUsername(authService *auth.AuthService) gin.HandlerFunc { } // GetMe retourne les informations de l'utilisateur connecté +// @Summary Get Current User +// @Description Get profile information of the currently logged-in user +// @Tags Auth +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} handlers.APIResponse{data=object{id=string,email=string,role=string}} +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Router /auth/me [get] func GetMe() gin.HandlerFunc { return func(c *gin.Context) { userID, exists := c.Get("user_id") diff --git a/veza-backend-api/internal/handlers/chat_handler.go b/veza-backend-api/internal/handlers/chat_handler.go index ad5150b78..84079ebc4 100644 --- a/veza-backend-api/internal/handlers/chat_handler.go +++ b/veza-backend-api/internal/handlers/chat_handler.go @@ -24,6 +24,17 @@ func NewChatHandler(chatService *services.ChatService, userService *services.Use } } +// GetToken generates a JWT token for the chat service +// @Summary Get Chat Token +// @Description Generate a short-lived token for chat authentication +// @Tags Chat +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} APIResponse{data=object{token=string}} +// @Failure 401 {object} APIResponse "Unauthorized" +// @Failure 500 {object} APIResponse "Internal Error" +// @Router /chat/token [get] func (h *ChatHandler) GetToken(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { diff --git a/veza-backend-api/internal/handlers/playlist_handler.go b/veza-backend-api/internal/handlers/playlist_handler.go index 07e4e660a..33b502d9e 100644 --- a/veza-backend-api/internal/handlers/playlist_handler.go +++ b/veza-backend-api/internal/handlers/playlist_handler.go @@ -64,7 +64,18 @@ type ReorderTracksRequest struct { } // CreatePlaylist gère la création d'une playlist -// GO-013: Utilise validator centralisé pour validation améliorée +// @Summary Create Playlist +// @Description Create a new playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param request body CreatePlaylistRequest true "Playlist Metadata" +// @Success 201 {object} APIResponse{data=object{playlist=models.Playlist}} +// @Failure 400 {object} APIResponse "Validation Error" +// @Failure 401 {object} APIResponse "Unauthorized" +// @Failure 500 {object} APIResponse "Internal Error" +// @Router /playlists [post] func (h *PlaylistHandler) CreatePlaylist(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { @@ -93,6 +104,18 @@ func (h *PlaylistHandler) CreatePlaylist(c *gin.Context) { } // GetPlaylists gère la récupération des playlists avec pagination +// @Summary Get Playlists +// @Description Get a paginated list of playlists +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(20) +// @Param user_id query string false "Filter by User ID" +// @Success 200 {object} APIResponse{data=object{playlists=[]models.Playlist,pagination=object}} +// @Failure 500 {object} APIResponse "Internal Error" +// @Router /playlists [get] func (h *PlaylistHandler) GetPlaylists(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) @@ -138,6 +161,17 @@ func (h *PlaylistHandler) GetPlaylists(c *gin.Context) { } // GetPlaylist gère la récupération d'une playlist +// @Summary Get Playlist by ID +// @Description Get detailed information about a playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Success 200 {object} APIResponse{data=object{playlist=models.Playlist}} +// @Failure 400 {object} APIResponse "Invalid ID" +// @Failure 404 {object} APIResponse "Playlist not found" +// @Router /playlists/{id} [get] func (h *PlaylistHandler) GetPlaylist(c *gin.Context) { // Playlist IDs are uuid.UUID playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse @@ -167,6 +201,20 @@ func (h *PlaylistHandler) GetPlaylist(c *gin.Context) { } // UpdatePlaylist gère la mise à jour d'une playlist +// @Summary Update Playlist +// @Description Update playlist metadata +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Param playlist body UpdatePlaylistRequest true "Playlist Metadata" +// @Success 200 {object} APIResponse{data=object{playlist=models.Playlist}} +// @Failure 400 {object} APIResponse "Validation Error" +// @Failure 401 {object} APIResponse "Unauthorized" +// @Failure 403 {object} APIResponse "Forbidden" +// @Failure 404 {object} APIResponse "Playlist not found" +// @Router /playlists/{id} [put] func (h *PlaylistHandler) UpdatePlaylist(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { @@ -210,6 +258,18 @@ func (h *PlaylistHandler) UpdatePlaylist(c *gin.Context) { } // DeletePlaylist gère la suppression d'une playlist +// @Summary Delete Playlist +// @Description Permanently delete a playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Success 200 {object} APIResponse{data=object{message=string}} +// @Failure 401 {object} APIResponse "Unauthorized" +// @Failure 403 {object} APIResponse "Forbidden" +// @Failure 404 {object} APIResponse "Playlist not found" +// @Router /playlists/{id} [delete] func (h *PlaylistHandler) DeletePlaylist(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { @@ -246,6 +306,18 @@ func (h *PlaylistHandler) DeletePlaylist(c *gin.Context) { } // AddTrack gère l'ajout d'un track à une playlist +// @Summary Add Track to Playlist +// @Description Add a track to the playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Param trackId body object{track_id=string} true "Track ID (in body)" +// @Success 200 {object} APIResponse{data=object{message=string}} +// @Failure 400 {object} APIResponse "Track already present or invalid ID" +// @Failure 404 {object} APIResponse "Playlist or Track not found" +// @Router /playlists/{id}/tracks [post] func (h *PlaylistHandler) AddTrack(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { @@ -297,6 +369,17 @@ func (h *PlaylistHandler) AddTrack(c *gin.Context) { } // RemoveTrack gère la suppression d'un track d'une playlist +// @Summary Remove Track from Playlist +// @Description Remove a track from the playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Param trackId path string true "Track ID" +// @Success 200 {object} APIResponse{data=object{message=string}} +// @Failure 404 {object} APIResponse "Playlist or Track not found" +// @Router /playlists/{id}/tracks/{trackId} [delete] func (h *PlaylistHandler) RemoveTrack(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { @@ -344,6 +427,17 @@ func (h *PlaylistHandler) RemoveTrack(c *gin.Context) { } // ReorderTracks gère la réorganisation des tracks d'une playlist +// @Summary Reorder Tracks +// @Description Reorder tracks in the playlist +// @Tags Playlist +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "Playlist ID" +// @Param order body ReorderTracksRequest true "New Track Order" +// @Success 200 {object} APIResponse{data=object{message=string}} +// @Failure 400 {object} APIResponse "Validation Error" +// @Router /playlists/{id}/tracks/reorder [put] func (h *PlaylistHandler) ReorderTracks(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { diff --git a/veza-backend-api/internal/handlers/profile_handler.go b/veza-backend-api/internal/handlers/profile_handler.go index c73057738..2656f16c6 100644 --- a/veza-backend-api/internal/handlers/profile_handler.go +++ b/veza-backend-api/internal/handlers/profile_handler.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "veza-backend-api/internal/types" ) @@ -26,11 +27,21 @@ func NewProfileHandler(userService *services.UserService, logger *zap.Logger) *P } // GetProfile retrieves a public user profile by ID +// @Summary Get Profile by ID +// @Description Get public profile information for a user +// @Tags User +// @Accept json +// @Produce json +// @Param id path string true "User ID" +// @Success 200 {object} handlers.APIResponse{data=object{profile=object}} +// @Failure 400 {object} handlers.APIResponse "Invalid ID" +// @Failure 404 {object} handlers.APIResponse "User not found" +// @Router /users/{id} [get] func (h *ProfileHandler) GetProfile(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid user id")) return } @@ -45,18 +56,28 @@ func (h *ProfileHandler) GetProfile(c *gin.Context) { // Get user profile with privacy check profile, err := h.userService.GetProfile(userID, requesterID) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeNotFound, "user not found")) return } - c.JSON(http.StatusOK, gin.H{"profile": profile}) + RespondSuccess(c, http.StatusOK, gin.H{"profile": profile}) } // GetProfileByUsername retrieves a public profile by username +// @Summary Get Profile by Username +// @Description Get public profile information for a user by username +// @Tags User +// @Accept json +// @Produce json +// @Param username path string true "Username" +// @Success 200 {object} handlers.APIResponse{data=object{profile=object}} +// @Failure 400 {object} handlers.APIResponse "Missing username" +// @Failure 404 {object} handlers.APIResponse "User not found" +// @Router /users/by-username/{username} [get] func (h *ProfileHandler) GetProfileByUsername(c *gin.Context) { username := c.Param("username") if username == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "username required"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "username required")) return } @@ -71,20 +92,31 @@ func (h *ProfileHandler) GetProfileByUsername(c *gin.Context) { // Get profile with privacy check profile, err := h.userService.GetProfileByUsername(username, requesterID) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeNotFound, "user not found")) return } - c.JSON(http.StatusOK, gin.H{"profile": profile}) + RespondSuccess(c, http.StatusOK, gin.H{"profile": profile}) } // GetProfileCompletion retrieves the profile completion status // T0220: Returns percentage and missing fields +// @Summary Get Profile Completion +// @Description Get profile completion percentage and missing fields +// @Tags User +// @Accept json +// @Produce json +// @Param id path string true "User ID" +// @Success 200 {object} handlers.APIResponse{data=object} +// @Failure 400 {object} handlers.APIResponse "Invalid ID" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 403 {object} handlers.APIResponse "Forbidden" +// @Router /users/{id}/completion [get] func (h *ProfileHandler) GetProfileCompletion(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid user id")) return } @@ -94,28 +126,28 @@ func (h *ProfileHandler) GetProfileCompletion(c *gin.Context) { if reqUUID, ok := reqID.(uuid.UUID); ok { authenticatedUserID = reqUUID } else { - c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("user not authenticated")) return } } else { - c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("user not authenticated")) return } // Verify that user_id corresponds to authenticated user if userID != authenticatedUserID { - c.JSON(http.StatusForbidden, gin.H{"error": "cannot access other user's profile completion"}) + RespondWithAppError(c, apperrors.NewForbiddenError("cannot access other user's profile completion")) return } // Calculate profile completion completion, err := h.userService.CalculateProfileCompletion(userID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to calculate profile completion"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "failed to calculate profile completion")) return } - c.JSON(http.StatusOK, completion) + RespondSuccess(c, http.StatusOK, completion) } // UpdateProfileRequest represents the request body for updating a user profile @@ -130,11 +162,24 @@ type UpdateProfileRequest struct { } // UpdateProfile updates a user profile +// @Summary Update Profile +// @Description Update user profile details +// @Tags User +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "User ID" +// @Param profile body UpdateProfileRequest true "Profile Data" +// @Success 200 {object} handlers.APIResponse{data=object{profile=object}} +// @Failure 400 {object} handlers.APIResponse "Validation Error" +// @Failure 401 {object} handlers.APIResponse "Unauthorized" +// @Failure 403 {object} handlers.APIResponse "Forbidden" +// @Router /users/{id} [put] func (h *ProfileHandler) UpdateProfile(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid user id")) return } @@ -144,17 +189,17 @@ func (h *ProfileHandler) UpdateProfile(c *gin.Context) { if reqUUID, ok := reqID.(uuid.UUID); ok { authenticatedUserID = reqUUID } else { - c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("user not authenticated")) return } } else { - c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) + RespondWithAppError(c, apperrors.NewUnauthorizedError("user not authenticated")) return } // Verify that user_id corresponds to authenticated user if userID != authenticatedUserID { - c.JSON(http.StatusForbidden, gin.H{"error": "cannot update other user's profile"}) + RespondWithAppError(c, apperrors.NewForbiddenError("cannot update other user's profile")) return } @@ -168,24 +213,24 @@ func (h *ProfileHandler) UpdateProfile(c *gin.Context) { if req.Username != "" { // Validate username format (alphanumeric + underscore, 3-30 chars) if !isValidUsername(req.Username) { - c.JSON(http.StatusBadRequest, gin.H{"error": "username must be 3-30 characters, alphanumeric and underscore only"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "username must be 3-30 characters, alphanumeric and underscore only")) return } // Validate username uniqueness if modified if err := h.userService.ValidateUsername(userID, req.Username); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, err.Error())) return } // Check if username can be modified (once per month) canChange, err := h.userService.CanChangeUsername(userID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to check username change eligibility"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "failed to check username change eligibility")) return } if !canChange { - c.JSON(http.StatusBadRequest, gin.H{"error": "username can only be changed once per month"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "username can only be changed once per month")) return } } @@ -194,7 +239,7 @@ func (h *ProfileHandler) UpdateProfile(c *gin.Context) { if req.Birthdate != "" { birthdate, err := time.Parse("2006-01-02", req.Birthdate) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid birthdate format, expected YYYY-MM-DD"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid birthdate format, expected YYYY-MM-DD")) return } @@ -202,7 +247,7 @@ func (h *ProfileHandler) UpdateProfile(c *gin.Context) { age := time.Since(birthdate) minAge := 13 * 365 * 24 * time.Hour // 13 years if age < minAge { - c.JSON(http.StatusBadRequest, gin.H{"error": "user must be at least 13 years old"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "user must be at least 13 years old")) return } } @@ -226,11 +271,11 @@ func (h *ProfileHandler) UpdateProfile(c *gin.Context) { // Update profile using the new UpdateProfile method profile, err := h.userService.UpdateProfile(userID, serviceReq) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update profile"}) + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "failed to update profile")) return } - c.JSON(http.StatusOK, gin.H{"profile": profile}) + RespondSuccess(c, http.StatusOK, gin.H{"profile": profile}) } // isValidUsername validates username format (alphanumeric + underscore, 3-30 chars) diff --git a/veza-backend-api/internal/response/response.go b/veza-backend-api/internal/response/response.go index ed53c6667..a0e0d4b54 100644 --- a/veza-backend-api/internal/response/response.go +++ b/veza-backend-api/internal/response/response.go @@ -6,6 +6,13 @@ import ( "github.com/gin-gonic/gin" ) +// APIResponse is the unified response envelope +type APIResponse struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Error interface{} `json:"error,omitempty"` +} + // Success sends a successful JSON response func Success(c *gin.Context, data interface{}, message ...string) { response := gin.H{