feat(openapi): annotate track subsystem (social/analytics/search/hls/waveform) — v1.0.8 B-annot
Second batch of the Veza backend OpenAPI annotation campaign. Completes
the track/ handler subtree — 22 more handlers annotated across 5 files —
so the orval-generated frontend client now covers the full track API
surface (stream, download, like, repost, share, search, recommendations,
stats, history, play, waveform, version restore).
Handlers annotated:
- internal/core/track/track_social_handler.go (11):
LikeTrack, UnlikeTrack, GetTrackLikes, GetUserLikedTracks,
GetUserRepostedTracks, CreateShare, GetSharedTrack, RevokeShare,
RepostTrack, UnrepostTrack, GetRepostStatus
- internal/core/track/track_analytics_handler.go (4):
GetTrackStats, GetTrackHistory, RecordPlay, RestoreVersion
- internal/core/track/track_search_handler.go (3):
GetRecommendations, GetSuggestedTags, SearchTracks
- internal/core/track/track_hls_handler.go (3):
HandleStreamCallback (internal), DownloadTrack, StreamTrack
— both user-facing endpoints document the v1.0.8 P2 302-to-signed-URL
behavior for S3-backed tracks alongside the local-FS path.
- internal/core/track/track_waveform_handler.go (1): GetWaveform
All comment blocks converge on the established template:
Summary / Description / Tags / Accept/Produce / Security (BearerAuth
when required) / typed Param path|query|body / Success envelope
handlers.APIResponse{data=...} / Failure 400/401/403/404/500 / Router.
track_hls_handler.go + track_waveform_handler.go receive a blank
import of internal/handlers so swaggo's type resolver can locate
handlers.APIResponse without forcing the file to call that package
at runtime.
Spec coverage:
/tracks/* paths: 13 → 29
make openapi: ✅ valid (Swagger 2.0)
go build ./...: ✅
openapi.yaml: +780 lines describing 16 new track endpoints.
Leaves /internal/core/ subsystems still blank: admin, moderation,
analytics/*, auth/handler.go (duplicates routes handled elsewhere),
discover, feed. Batch 2b next will cover playlists + marketplace gap
so the 4 frontend services (auth/users/tracks/playlists) become
fully orval-migratable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2aa2e6cd51
commit
3dc0654a52
9 changed files with 5943 additions and 0 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,16 @@ type RecordPlayRequest struct {
|
|||
}
|
||||
|
||||
// GetTrackStats returns track statistics (plays, likes, views, etc.)
|
||||
// @Summary Get track statistics
|
||||
// @Description Aggregated track stats: views, likes, comments, play time, downloads, average duration.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{stats=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/stats [get]
|
||||
func (h *TrackHandler) GetTrackStats(c *gin.Context) {
|
||||
trackIDStr := c.Param("id")
|
||||
if trackIDStr == "" {
|
||||
|
|
@ -62,6 +72,18 @@ func (h *TrackHandler) GetTrackStats(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetTrackHistory returns modification history for a track
|
||||
// @Summary Get track history
|
||||
// @Description Paginated audit log of modifications (metadata updates, version changes) for a track.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param limit query int false "Items per page" default(50)
|
||||
// @Param offset query int false "Offset" default(0)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{history=[]object,total=integer,limit=integer,offset=integer}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/history [get]
|
||||
func (h *TrackHandler) GetTrackHistory(c *gin.Context) {
|
||||
if h.historyService == nil {
|
||||
h.respondWithError(c, http.StatusInternalServerError, "history service not available")
|
||||
|
|
@ -124,6 +146,20 @@ func (h *TrackHandler) GetTrackHistory(c *gin.Context) {
|
|||
}
|
||||
|
||||
// RecordPlay enregistre un événement de lecture pour un track
|
||||
// @Summary Record play event
|
||||
// @Description Persist a playback event with optional play_time so the creator's analytics dashboard tracks listening behaviour.
|
||||
// @Tags Track
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param request body RecordPlayRequest false "Playback metadata (optional)"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string,id=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id / body"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/play [post]
|
||||
func (h *TrackHandler) RecordPlay(c *gin.Context) {
|
||||
if h.playbackAnalyticsService == nil {
|
||||
h.respondWithError(c, http.StatusInternalServerError, "playback analytics service not available")
|
||||
|
|
@ -183,6 +219,20 @@ func (h *TrackHandler) RecordPlay(c *gin.Context) {
|
|||
}
|
||||
|
||||
// RestoreVersion restaure une version spécifique d'un track
|
||||
// @Summary Restore track version
|
||||
// @Description Rollback a track to a previous version. Only the track owner can restore.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param versionId path string true "Version UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} handlers.APIResponse "Not owner"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track or version not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/versions/{versionId}/restore [post]
|
||||
func (h *TrackHandler) RestoreVersion(c *gin.Context) {
|
||||
if h.versionService == nil {
|
||||
h.respondWithError(c, http.StatusInternalServerError, "version service not available")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import (
|
|||
"github.com/google/uuid"
|
||||
|
||||
"veza-backend-api/internal/common"
|
||||
// handlers is imported to let swaggo resolve handlers.APIResponse refs in
|
||||
// doc comments (@Failure / @Success); not called directly from this file.
|
||||
_ "veza-backend-api/internal/handlers"
|
||||
"veza-backend-api/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -26,6 +29,17 @@ type StreamCallbackRequest struct {
|
|||
}
|
||||
|
||||
// HandleStreamCallback handles the callback from stream server
|
||||
// @Summary Stream server callback
|
||||
// @Description Internal endpoint called by the Rust stream server when HLS transcoding completes or fails. Updates the track's stream_status and stream_manifest_url. Requires internal API key (not user-facing).
|
||||
// @Tags Track
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param request body StreamCallbackRequest true "Callback payload"
|
||||
// @Success 200 {object} object{message=string}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation / invalid id"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /internal/tracks/{id}/stream-ready [post]
|
||||
func (h *TrackHandler) HandleStreamCallback(c *gin.Context) {
|
||||
trackIDStr := c.Param("id")
|
||||
// MIGRATION UUID: TrackID is UUID
|
||||
|
|
@ -52,6 +66,19 @@ func (h *TrackHandler) HandleStreamCallback(c *gin.Context) {
|
|||
}
|
||||
|
||||
// DownloadTrack gère le téléchargement d'un track
|
||||
// @Summary Download a track
|
||||
// @Description Serve the original audio file. For S3-backed tracks returns a 302 redirect to a signed URL (TTL 30min). For local-backed tracks streams the file with Range support. Public tracks or share_token access; paid tracks require a license.
|
||||
// @Tags Track
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param share_token query string false "Grants access without authentication for a limited time"
|
||||
// @Success 200 {file} binary
|
||||
// @Success 302 {string} string "Location header points to signed S3 URL (s3-backed tracks)"
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 403 {object} handlers.APIResponse "No permission / license required"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track or file not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/download [get]
|
||||
func (h *TrackHandler) DownloadTrack(c *gin.Context) {
|
||||
// Récupérer l'utilisateur s'il est authentifié
|
||||
var userID uuid.UUID
|
||||
|
|
@ -193,6 +220,19 @@ func (h *TrackHandler) DownloadTrack(c *gin.Context) {
|
|||
// by HLSEnabled), /stream is always available and is the default playback path when
|
||||
// HLS transcoding is off. The file is served via http.ServeContent which handles
|
||||
// Range, If-Modified-Since and If-None-Match automatically.
|
||||
// @Summary Stream a track (raw audio + Range)
|
||||
// @Description Default playback path. S3-backed tracks return a 302 redirect to a signed URL (TTL 15min). Local-backed tracks are streamed via http.ServeContent with Range support. Always available, unlike /hls/* which is gated by HLSEnabled.
|
||||
// @Tags Track
|
||||
// @Produce audio/*
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param share_token query string false "Grants access without authentication"
|
||||
// @Success 200 {file} binary
|
||||
// @Success 302 {string} string "Location header points to signed S3 URL (s3-backed tracks)"
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 403 {object} handlers.APIResponse "No permission"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track or file not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/stream [get]
|
||||
func (h *TrackHandler) StreamTrack(c *gin.Context) {
|
||||
var userID uuid.UUID
|
||||
if userIDInterface, exists := c.Get("user_id"); exists {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,16 @@ var tagSuggestionsByGenre = map[string][]string{
|
|||
}
|
||||
|
||||
// GetRecommendations returns personalized track recommendations (D2 autoplay)
|
||||
// @Summary Get track recommendations
|
||||
// @Description Personalized tracks for D2 autoplay. If seed_track_id is given, returns tracks similar to that seed. Otherwise, uses the caller's history (chronological, no behavioural ranking — CLAUDE.md rule 7).
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param limit query int false "Max items (max 100)" default(20)
|
||||
// @Param seed_track_id query string false "Start from this track's similarity neighbours"
|
||||
// @Success 200 {object} response.APIResponse{data=object{tracks=[]models.Track}}
|
||||
// @Failure 500 {object} response.APIResponse "Internal Error"
|
||||
// @Router /tracks/recommendations [get]
|
||||
func (h *TrackHandler) GetRecommendations(c *gin.Context) {
|
||||
if h.trackRecommendationService == nil {
|
||||
response.InternalServerError(c, "recommendations unavailable")
|
||||
|
|
@ -72,6 +82,13 @@ func (h *TrackHandler) GetRecommendations(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetSuggestedTags returns tag suggestions based on genre and BPM (E4)
|
||||
// @Summary Get suggested tags
|
||||
// @Description Returns a static tag suggestion list for a genre — useful for upload autocomplete and filter chips.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param genre query string false "Genre slug (pop, rock, electronic, hip-hop, jazz, classical, ambient, default)" default(default)
|
||||
// @Success 200 {object} response.APIResponse{data=object{tags=[]string}}
|
||||
// @Router /tracks/suggested-tags [get]
|
||||
func (h *TrackHandler) GetSuggestedTags(c *gin.Context) {
|
||||
genre := strings.ToLower(strings.TrimSpace(c.DefaultQuery("genre", "")))
|
||||
if genre == "" {
|
||||
|
|
@ -85,6 +102,29 @@ func (h *TrackHandler) GetSuggestedTags(c *gin.Context) {
|
|||
}
|
||||
|
||||
// SearchTracks gère la recherche avancée de tracks
|
||||
// @Summary Advanced track search
|
||||
// @Description Full-text + faceted search on tracks (genre, BPM, duration, tags, musical key, dates). Sort-by and order configurable.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param q query string false "Full-text query (title/artist/album)"
|
||||
// @Param tags query string false "Comma-separated tag list"
|
||||
// @Param tag_mode query string false "Tag combinator (OR / AND)" default(OR)
|
||||
// @Param min_duration query int false "Minimum duration (seconds)"
|
||||
// @Param max_duration query int false "Maximum duration (seconds)"
|
||||
// @Param min_bpm query int false "Minimum BPM"
|
||||
// @Param max_bpm query int false "Maximum BPM"
|
||||
// @Param genre query string false "Genre filter"
|
||||
// @Param format query string false "Audio format filter"
|
||||
// @Param musical_key query string false "Musical key filter"
|
||||
// @Param min_date query string false "Created-after (RFC3339)"
|
||||
// @Param max_date query string false "Created-before (RFC3339)"
|
||||
// @Param page query int false "Page (1-based)" default(1)
|
||||
// @Param limit query int false "Items per page (max 100)" default(20)
|
||||
// @Param sort_by query string false "Sort column" default(created_at)
|
||||
// @Param sort_order query string false "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/search [get]
|
||||
func (h *TrackHandler) SearchTracks(c *gin.Context) {
|
||||
if h.searchService == nil {
|
||||
h.respondWithError(c, http.StatusInternalServerError, "search service not available")
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@ type CreateShareRequest struct {
|
|||
}
|
||||
|
||||
// LikeTrack gère l'ajout d'un like sur un track
|
||||
// @Summary Like a track
|
||||
// @Description Record a like from the authenticated user. Creates a grouped notification for the creator (F554).
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/like [post]
|
||||
func (h *TrackHandler) LikeTrack(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -67,6 +79,17 @@ func (h *TrackHandler) LikeTrack(c *gin.Context) {
|
|||
}
|
||||
|
||||
// UnlikeTrack gère la suppression d'un like sur un track
|
||||
// @Summary Unlike a track
|
||||
// @Description Remove the authenticated user's like on the track (idempotent).
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/like [delete]
|
||||
func (h *TrackHandler) UnlikeTrack(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -95,6 +118,18 @@ func (h *TrackHandler) UnlikeTrack(c *gin.Context) {
|
|||
|
||||
// GetTrackLikes gère la récupération du nombre de likes d'un track.
|
||||
// v0.10.3 F202: count visible only by track creator (or admin); others get is_liked only.
|
||||
// @Summary Get track like status
|
||||
// @Description Returns whether the current user has liked the track. The total like count is returned ONLY to the creator or an admin (privacy per ORIGIN_UI_UX_SYSTEM §13).
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{is_liked=boolean,count=integer}} "count is omitted for non-owners"
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/likes [get]
|
||||
func (h *TrackHandler) GetTrackLikes(c *gin.Context) {
|
||||
trackIDStr := c.Param("id")
|
||||
if trackIDStr == "" {
|
||||
|
|
@ -137,6 +172,19 @@ func (h *TrackHandler) GetTrackLikes(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetUserLikedTracks gère la récupération des tracks likés par un utilisateur
|
||||
// @Summary List tracks liked by a user
|
||||
// @Description Returns paginated tracks the given user has liked. Used for profile "Likes" tab.
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "User UUID"
|
||||
// @Param limit query int false "Items per page (max 100)" default(20)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{tracks=[]models.Track,total=integer,limit=integer,offset=integer}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/likes [get]
|
||||
func (h *TrackHandler) GetUserLikedTracks(c *gin.Context) {
|
||||
userIDStr := c.Param("id")
|
||||
if userIDStr == "" {
|
||||
|
|
@ -188,6 +236,17 @@ func (h *TrackHandler) GetUserLikedTracks(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetUserRepostedTracks returns tracks reposted by the user (v0.10.3 F203).
|
||||
// @Summary List tracks reposted by a user
|
||||
// @Description Returns paginated tracks the user has reposted. Used for profile "Reposts" tab.
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param id path string true "User UUID"
|
||||
// @Param limit query int false "Items per page (max 100)" default(20)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{tracks=[]models.Track,total=integer,limit=integer,offset=integer}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/reposts [get]
|
||||
func (h *TrackHandler) GetUserRepostedTracks(c *gin.Context) {
|
||||
userIDStr := c.Param("id")
|
||||
if userIDStr == "" {
|
||||
|
|
@ -243,6 +302,21 @@ func (h *TrackHandler) GetUserRepostedTracks(c *gin.Context) {
|
|||
}
|
||||
|
||||
// CreateShare crée un nouveau lien de partage pour un track
|
||||
// @Summary Create share link
|
||||
// @Description Generate a tokenized share link for a track with given permission level and optional expiry.
|
||||
// @Tags Track
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Param request body CreateShareRequest true "Share parameters"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{share=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} handlers.APIResponse "Not owner"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/share [post]
|
||||
func (h *TrackHandler) CreateShare(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -289,6 +363,17 @@ func (h *TrackHandler) CreateShare(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetSharedTrack récupère un track via son token de partage
|
||||
// @Summary Get track by share token
|
||||
// @Description Public endpoint that resolves a share token and returns the track + share metadata. No auth required; the token IS the auth.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param token path string true "Opaque share token issued by CreateShare"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{track=models.Track,share=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Missing token"
|
||||
// @Failure 403 {object} handlers.APIResponse "Share link expired"
|
||||
// @Failure 404 {object} handlers.APIResponse "Share or track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/shared/{token} [get]
|
||||
func (h *TrackHandler) GetSharedTrack(c *gin.Context) {
|
||||
token := c.Param("token")
|
||||
if token == "" {
|
||||
|
|
@ -332,6 +417,19 @@ func (h *TrackHandler) GetSharedTrack(c *gin.Context) {
|
|||
}
|
||||
|
||||
// RevokeShare révoque un lien de partage
|
||||
// @Summary Revoke share link
|
||||
// @Description Permanently disable a share token. Only the share issuer (or admin) can revoke.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Share UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} handlers.APIResponse "Not issuer"
|
||||
// @Failure 404 {object} handlers.APIResponse "Share not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/share/{id} [delete]
|
||||
func (h *TrackHandler) RevokeShare(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -373,6 +471,18 @@ func (h *TrackHandler) RevokeShare(c *gin.Context) {
|
|||
}
|
||||
|
||||
// RepostTrack adds a track repost to the user's profile (v0.10.3 F203).
|
||||
// @Summary Repost a track
|
||||
// @Description Add a track to the authenticated user's profile as a repost. Notifies the creator (F204) unless self-repost.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "Track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/repost [post]
|
||||
func (h *TrackHandler) RepostTrack(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -418,6 +528,17 @@ func (h *TrackHandler) RepostTrack(c *gin.Context) {
|
|||
}
|
||||
|
||||
// UnrepostTrack removes a track repost (v0.10.3 F203).
|
||||
// @Summary Remove track repost
|
||||
// @Description Remove the authenticated user's repost of the track (idempotent).
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/repost [delete]
|
||||
func (h *TrackHandler) UnrepostTrack(c *gin.Context) {
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
|
|
@ -451,6 +572,14 @@ func (h *TrackHandler) UnrepostTrack(c *gin.Context) {
|
|||
|
||||
// GetRepostStatus returns whether the current user has reposted the track (v0.10.3 F203).
|
||||
// Works with OptionalAuth: if not authenticated, returns is_reposted: false.
|
||||
// @Summary Get repost status
|
||||
// @Description Returns whether the current user has reposted the track. Public (optional auth); unauthenticated callers get is_reposted=false.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{is_reposted=boolean}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Router /tracks/{id}/repost [get]
|
||||
func (h *TrackHandler) GetRepostStatus(c *gin.Context) {
|
||||
trackIDStr := c.Param("id")
|
||||
if trackIDStr == "" {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,25 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
|
||||
// handlers imported so swaggo resolves handlers.APIResponse refs in
|
||||
// doc comments; no direct call.
|
||||
_ "veza-backend-api/internal/handlers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetWaveform returns the waveform JSON data for a track (S1-06)
|
||||
// GET /api/v1/tracks/:id/waveform
|
||||
// @Summary Get track waveform
|
||||
// @Description Returns a JSON peaks array used by the client to draw the audio waveform preview. 404 if waveform extraction is not complete yet.
|
||||
// @Tags Track
|
||||
// @Produce json
|
||||
// @Param id path string true "Track UUID"
|
||||
// @Success 200 {object} object "Waveform peaks JSON (tool-specific shape)"
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid track id"
|
||||
// @Failure 404 {object} handlers.APIResponse "Waveform not generated / track not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /tracks/{id}/waveform [get]
|
||||
func (h *TrackHandler) GetWaveform(c *gin.Context) {
|
||||
if h.waveformService == nil {
|
||||
h.respondWithError(c, http.StatusInternalServerError, "waveform service not available")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue