Compare commits
3 commits
2aa2e6cd51
...
9e948d5102
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e948d5102 | ||
|
|
72c5381c73 | ||
|
|
3dc0654a52 |
11 changed files with 10780 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")
|
||||
|
|
|
|||
|
|
@ -140,6 +140,18 @@ type ImportPlaylistRequest struct {
|
|||
}
|
||||
|
||||
// ImportPlaylist gère l'import d'une playlist depuis JSON (v0.10.4 F145)
|
||||
// @Summary Import playlist
|
||||
// @Description Create a playlist from a JSON payload (title, description, is_public, ordered track IDs). Useful for bulk seed / migration.
|
||||
// @Tags Playlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body ImportPlaylistRequest true "Playlist + tracks"
|
||||
// @Success 201 {object} handlers.APIResponse{data=object{playlist=models.Playlist}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/import [post]
|
||||
func (h *PlaylistHandler) ImportPlaylist(c *gin.Context) {
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
|
|
@ -188,6 +200,15 @@ func (h *PlaylistHandler) ImportPlaylist(c *gin.Context) {
|
|||
}
|
||||
|
||||
// GetFavorisPlaylist returns the current user's Favoris playlist, creating it if needed (v0.10.4 F136)
|
||||
// @Summary Get Favoris playlist
|
||||
// @Description Returns the authenticated user's "Favoris" playlist. Auto-created on first call. Used by the like-as-save pattern.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{playlist=models.Playlist}}
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/favoris [get]
|
||||
func (h *PlaylistHandler) GetFavorisPlaylist(c *gin.Context) {
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
|
|
@ -316,6 +337,18 @@ func (h *PlaylistHandler) GetPlaylist(c *gin.Context) {
|
|||
|
||||
// GetPlaylistByShareToken returns a playlist by its public share token (v0.10.4 F143).
|
||||
// No authentication required.
|
||||
// GetPlaylistByShareToken returns a playlist via its share token (no auth required).
|
||||
// @Summary Get playlist by share token
|
||||
// @Description Public endpoint resolving a share token. Allows unauthenticated access to the playlist snapshot + tracks.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Param token path string true "Share token"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{playlist=models.Playlist}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Missing token"
|
||||
// @Failure 403 {object} handlers.APIResponse "Share expired"
|
||||
// @Failure 404 {object} handlers.APIResponse "Share or playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/shared/{token} [get]
|
||||
func (h *PlaylistHandler) GetPlaylistByShareToken(c *gin.Context) {
|
||||
token := c.Param("token")
|
||||
if token == "" {
|
||||
|
|
@ -635,6 +668,22 @@ type UpdateCollaboratorPermissionRequest struct {
|
|||
|
||||
// AddCollaborator gère l'ajout d'un collaborateur à une playlist
|
||||
// T0479: POST /api/v1/playlists/:id/collaborators
|
||||
// AddCollaborator adds a collaborator with a permission level to the playlist.
|
||||
// @Summary Add playlist collaborator
|
||||
// @Description Invite a user as collaborator. Only the owner (or admin) can add.
|
||||
// @Tags Playlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @Param request body AddCollaboratorRequest true "Collaborator + permission"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{collaborator=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 "Playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/collaborators [post]
|
||||
func (h *PlaylistHandler) AddCollaborator(c *gin.Context) {
|
||||
// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
|
|
@ -703,6 +752,21 @@ func (h *PlaylistHandler) AddCollaborator(c *gin.Context) {
|
|||
|
||||
// RemoveCollaborator gère la suppression d'un collaborateur d'une playlist
|
||||
// T0479: DELETE /api/v1/playlists/:id/collaborators/:userId
|
||||
// RemoveCollaborator removes a collaborator from a playlist.
|
||||
// @Summary Remove playlist collaborator
|
||||
// @Description Revoke a collaborator's access. Only the owner (or admin) can remove.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @Param userId path string true "Collaborator user 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 owner"
|
||||
// @Failure 404 {object} handlers.APIResponse "Playlist or collaborator not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/collaborators/{userId} [delete]
|
||||
func (h *PlaylistHandler) RemoveCollaborator(c *gin.Context) {
|
||||
// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
|
|
@ -748,6 +812,23 @@ func (h *PlaylistHandler) RemoveCollaborator(c *gin.Context) {
|
|||
|
||||
// UpdateCollaboratorPermission gère la mise à jour de la permission d'un collaborateur
|
||||
// T0479: PUT /api/v1/playlists/:id/collaborators/:userId
|
||||
// UpdateCollaboratorPermission changes a collaborator's permission level.
|
||||
// @Summary Update collaborator permission
|
||||
// @Description Change a collaborator's permission level (read / write / admin). Only the owner can update.
|
||||
// @Tags Playlist
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @Param userId path string true "Collaborator user UUID"
|
||||
// @Param request body UpdateCollaboratorPermissionRequest true "New permission"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{collaborator=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 "Playlist or collaborator not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/collaborators/{userId} [put]
|
||||
func (h *PlaylistHandler) UpdateCollaboratorPermission(c *gin.Context) {
|
||||
// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
|
|
@ -819,6 +900,19 @@ func (h *PlaylistHandler) UpdateCollaboratorPermission(c *gin.Context) {
|
|||
|
||||
// GetCollaborators gère la récupération des collaborateurs d'une playlist
|
||||
// T0479: GET /api/v1/playlists/:id/collaborators
|
||||
// GetCollaborators lists a playlist's collaborators.
|
||||
// @Summary List playlist collaborators
|
||||
// @Description Returns the collaborators of a playlist with their permission level.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{collaborators=[]object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "Playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/collaborators [get]
|
||||
func (h *PlaylistHandler) GetCollaborators(c *gin.Context) {
|
||||
// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
|
|
@ -854,6 +948,20 @@ func (h *PlaylistHandler) GetCollaborators(c *gin.Context) {
|
|||
|
||||
// CreateShareLink gère la création d'un lien de partage public pour une playlist
|
||||
// T0488: Create Playlist Public Share Link
|
||||
// CreateShareLink generates a tokenised share link for a playlist.
|
||||
// @Summary Create playlist share link
|
||||
// @Description Generate a tokenised link to share a playlist (read-only). Only owner / admin can issue. No body required.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @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 "Playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/share [post]
|
||||
func (h *PlaylistHandler) CreateShareLink(c *gin.Context) {
|
||||
// MOD-P1-001: Utiliser GetUserIDUUID au lieu de c.Get manuel
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
|
|
@ -957,6 +1065,20 @@ func (h *PlaylistHandler) UnfollowPlaylist(c *gin.Context) {
|
|||
|
||||
// GetPlaylistStats gère la récupération des statistiques d'une playlist
|
||||
// T0491: Create Playlist Analytics Backend
|
||||
// GetPlaylistStats returns aggregated stats for a playlist.
|
||||
// @Summary Get playlist statistics
|
||||
// @Description Returns aggregated stats for a playlist (plays, follows, tracks count, etc.). Visible to the owner, collaborators and admins.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Playlist UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{stats=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} handlers.APIResponse "Forbidden"
|
||||
// @Failure 404 {object} handlers.APIResponse "Playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/analytics [get]
|
||||
func (h *PlaylistHandler) GetPlaylistStats(c *gin.Context) {
|
||||
// Playlist IDs are uuid.UUID
|
||||
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
||||
|
|
@ -1038,6 +1160,20 @@ type DuplicatePlaylistRequest struct {
|
|||
|
||||
// DuplicatePlaylist gère la duplication d'une playlist
|
||||
// T0495: Create Playlist Duplicate Feature
|
||||
// DuplicatePlaylist duplicates a playlist into a new one owned by the caller.
|
||||
// @Summary Duplicate playlist
|
||||
// @Description Copy a playlist's track list into a new playlist owned by the authenticated user. Cover/description copied; original unchanged.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Source playlist UUID"
|
||||
// @Success 201 {object} handlers.APIResponse{data=object{playlist=models.Playlist}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 403 {object} handlers.APIResponse "Source not visible"
|
||||
// @Failure 404 {object} handlers.APIResponse "Source playlist not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/{id}/duplicate [post]
|
||||
func (h *PlaylistHandler) DuplicatePlaylist(c *gin.Context) {
|
||||
// Playlist IDs are uuid.UUID
|
||||
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
||||
|
|
@ -1095,6 +1231,18 @@ func (h *PlaylistHandler) DuplicatePlaylist(c *gin.Context) {
|
|||
|
||||
// SearchPlaylists gère la recherche de playlists
|
||||
// T0496: Create Playlist Search Backend
|
||||
// SearchPlaylists searches public playlists by query, sort, filters.
|
||||
// @Summary Search playlists
|
||||
// @Description Full-text search on public playlists (title + description). Paginated.
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Param q query string false "Full-text query"
|
||||
// @Param page query int false "Page number" default(1)
|
||||
// @Param limit query int false "Items per page (max 100)" default(20)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{playlists=[]models.Playlist,pagination=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/search [get]
|
||||
func (h *PlaylistHandler) SearchPlaylists(c *gin.Context) {
|
||||
// Get current user ID
|
||||
var currentUserID *uuid.UUID
|
||||
|
|
@ -1161,6 +1309,17 @@ func (h *PlaylistHandler) SearchPlaylists(c *gin.Context) {
|
|||
|
||||
// GetRecommendations gère la récupération des recommandations de playlists
|
||||
// T0498: Create Playlist Recommendations
|
||||
// GetRecommendations returns playlist recommendations for the caller.
|
||||
// @Summary Get playlist recommendations
|
||||
// @Description Suggested playlists for the authenticated user. Chronological / declarative discovery — no behavioural ranking (CLAUDE.md rule 7).
|
||||
// @Tags Playlist
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param limit query int false "Max items (max 100)" default(20)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{playlists=[]models.Playlist}}
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /playlists/recommendations [get]
|
||||
func (h *PlaylistHandler) GetRecommendations(c *gin.Context) {
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"time"
|
||||
|
||||
apperrors "veza-backend-api/internal/errors"
|
||||
// models imported so swaggo can resolve models.User in doc comments.
|
||||
_ "veza-backend-api/internal/models"
|
||||
"veza-backend-api/internal/services"
|
||||
"veza-backend-api/internal/types"
|
||||
"veza-backend-api/internal/utils"
|
||||
|
|
@ -267,6 +269,17 @@ func (h *ProfileHandler) ListUsers(c *gin.Context) {
|
|||
|
||||
// SearchUsers gère la recherche d'utilisateurs
|
||||
// BE-API-008: Implement user search endpoint
|
||||
// @Summary Search users
|
||||
// @Description Full-text search on users (username, display_name). Paginated. Public.
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param q query string false "Full-text query"
|
||||
// @Param page query int false "Page" default(1)
|
||||
// @Param limit query int false "Items per page (max 100)" default(20)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{users=[]models.User,pagination=object}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation (bounds)"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/search [get]
|
||||
func (h *ProfileHandler) SearchUsers(c *gin.Context) {
|
||||
// Récupérer les paramètres de recherche
|
||||
query := c.Query("q")
|
||||
|
|
@ -308,6 +321,18 @@ func (h *ProfileHandler) SearchUsers(c *gin.Context) {
|
|||
// FollowUser gère le suivi d'un utilisateur
|
||||
// POST /api/v1/users/:id/follow
|
||||
// BE-API-017: Implement user follow/unfollow endpoints
|
||||
// @Summary Follow user
|
||||
// @Description Authenticated user follows target user. Creates a notification (F554 grouped) for the target.
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Target user UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 404 {object} handlers.APIResponse "User not found"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/follow [post]
|
||||
func (h *ProfileHandler) FollowUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à suivre depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
|
|
@ -366,6 +391,16 @@ func (h *ProfileHandler) FollowUser(c *gin.Context) {
|
|||
|
||||
// GetFollowSuggestions returns users to follow (v0.10.0 F211)
|
||||
// GET /api/v1/users/suggestions?limit=10
|
||||
// @Summary Get follow suggestions
|
||||
// @Description Returns suggested users to follow for the authenticated user. Declarative discovery — no behavioural scoring (CLAUDE.md rule 7).
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param limit query int false "Max items (max 50)" default(10)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{users=[]models.User}}
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/suggestions [get]
|
||||
func (h *ProfileHandler) GetFollowSuggestions(c *gin.Context) {
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
|
|
@ -393,6 +428,17 @@ func (h *ProfileHandler) GetFollowSuggestions(c *gin.Context) {
|
|||
// UnfollowUser gère l'arrêt du suivi d'un utilisateur
|
||||
// DELETE /api/v1/users/:id/follow
|
||||
// BE-API-017: Implement user follow/unfollow endpoints
|
||||
// @Summary Unfollow user
|
||||
// @Description Authenticated user stops following target user (idempotent).
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Target user UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/follow [delete]
|
||||
func (h *ProfileHandler) UnfollowUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à ne plus suivre depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
|
|
@ -435,6 +481,17 @@ func (h *ProfileHandler) UnfollowUser(c *gin.Context) {
|
|||
// BlockUser gère le blocage d'un utilisateur
|
||||
// POST /api/v1/users/:id/block
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
// @Summary Block user
|
||||
// @Description Authenticated user blocks target user (hides their content, prevents follows). Cannot self-block.
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Target user UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Validation / self-block"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/block [post]
|
||||
func (h *ProfileHandler) BlockUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à bloquer depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
|
|
@ -487,6 +544,17 @@ func (h *ProfileHandler) BlockUser(c *gin.Context) {
|
|||
// UnblockUser gère le déblocage d'un utilisateur
|
||||
// DELETE /api/v1/users/:id/block
|
||||
// BE-API-018: Implement user block/unblock endpoints
|
||||
// @Summary Unblock user
|
||||
// @Description Authenticated user unblocks target user (idempotent).
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "Target user UUID"
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{message=string}}
|
||||
// @Failure 400 {object} handlers.APIResponse "Invalid id"
|
||||
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users/{id}/block [delete]
|
||||
func (h *ProfileHandler) UnblockUser(c *gin.Context) {
|
||||
// Récupérer l'ID de l'utilisateur à débloquer depuis l'URL
|
||||
userIDStr := c.Param("id")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue