[BE-API-002] api: Implement playlist collaborators endpoints
- Added routes in router.go: POST, GET, PUT, DELETE /playlists/:id/collaborators - Applied RequireOwnershipOrAdmin middleware to POST, PUT, DELETE routes - GET route accessible to collaborators (service layer checks permissions) - Fixed UpdateCollaboratorPermission handler to use RespondWithAppError - All handlers already existed in playlist_handler.go - All endpoints properly authenticated and ownership checks enforced Phase: PHASE-1 Priority: P0 Progress: 5/267 (1.9%)
This commit is contained in:
parent
8d65f85541
commit
ed8949ee76
7 changed files with 31 additions and 11 deletions
|
|
@ -686,7 +686,18 @@
|
|||
"description": "Frontend calls POST /playlists/:id/collaborators, DELETE /playlists/:id/collaborators/:userId, PUT /playlists/:id/collaborators/:userId, GET /playlists/:id/collaborators but these endpoints don't exist.",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 6,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"completion": {
|
||||
"completed_at": "2025-12-23T00:41:32Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": [],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/api/router.go",
|
||||
"veza-backend-api/internal/handlers/playlist_handler.go"
|
||||
],
|
||||
"notes": "All collaborator handlers already existed in playlist_handler.go. Added routes in router.go: POST /playlists/:id/collaborators, GET /playlists/:id/collaborators, PUT /playlists/:id/collaborators/:userId, DELETE /playlists/:id/collaborators/:userId. Applied RequireOwnershipOrAdmin middleware to POST, PUT, DELETE routes. GET route accessible to collaborators (service layer checks permissions). Fixed UpdateCollaboratorPermission handler to use RespondWithAppError. All endpoints properly authenticated and ownership checks enforced.",
|
||||
"issues_encountered": []
|
||||
},
|
||||
"files_involved": [
|
||||
{
|
||||
"path": "veza-backend-api/internal/api/router.go",
|
||||
|
|
|
|||
|
|
@ -563,6 +563,14 @@ func (r *APIRouter) setupPlaylistRoutes(router *gin.RouterGroup) {
|
|||
playlists.POST("/:id/tracks", playlistHandler.AddTrack)
|
||||
playlists.DELETE("/:id/tracks/:track_id", playlistHandler.RemoveTrack)
|
||||
playlists.PUT("/:id/tracks/reorder", playlistHandler.ReorderTracks)
|
||||
|
||||
// Playlist Collaborators
|
||||
// BE-API-002: Add collaborator routes with ownership checks
|
||||
// POST and DELETE require ownership (enforced by service layer, but middleware adds extra security)
|
||||
playlists.POST("/:id/collaborators", r.config.AuthMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), playlistHandler.AddCollaborator)
|
||||
playlists.GET("/:id/collaborators", playlistHandler.GetCollaborators) // GET accessible to collaborators (service checks permissions)
|
||||
playlists.PUT("/:id/collaborators/:userId", r.config.AuthMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), playlistHandler.UpdateCollaboratorPermission)
|
||||
playlists.DELETE("/:id/collaborators/:userId", r.config.AuthMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), playlistHandler.RemoveCollaborator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -729,16 +737,16 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) {
|
|||
|
||||
// Services nécessaires
|
||||
sessionService := services.NewSessionService(r.db, r.logger)
|
||||
|
||||
|
||||
// CSRF Middleware (si Redis est disponible)
|
||||
var csrfMiddleware *middleware.CSRFMiddleware
|
||||
if r.config.RedisClient != nil {
|
||||
csrfMiddleware = middleware.NewCSRFMiddleware(r.config.RedisClient, r.logger)
|
||||
csrfHandler := handlers.NewCSRFHandler(csrfMiddleware, r.logger)
|
||||
|
||||
|
||||
// Route CSRF token (doit être accessible sans vérification CSRF)
|
||||
protected.GET("/csrf-token", csrfHandler.GetCSRFToken())
|
||||
|
||||
|
||||
// Appliquer le middleware CSRF à toutes les routes protégées (sauf /csrf-token qui est déjà définie)
|
||||
protected.Use(csrfMiddleware.Middleware())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -429,4 +429,3 @@ func TestDeleteTrack_UserCanDeleteOwnTrack(t *testing.T) {
|
|||
err := db.First(&track, "id = ?", trackID).Error
|
||||
assert.Error(t, err) // Should not find the track
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -635,7 +635,8 @@ func (h *PlaylistHandler) UpdateCollaboratorPermission(c *gin.Context) {
|
|||
// Playlist IDs are uuid.UUID
|
||||
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
||||
// MOD-P1-002: Utiliser RespondWithAppError au lieu de gin.H{"error"}
|
||||
RespondWithAppError(c, apperrors.NewValidationError("invalid playlist id"))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ func createTestUserForProfile(t *testing.T, db *gorm.DB, userID uuid.UUID, usern
|
|||
// Create user first
|
||||
err := db.Create(user).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
// Then create admin role and assign it if admin
|
||||
if isAdmin {
|
||||
// Create admin role and assign it to user
|
||||
|
|
@ -128,7 +128,7 @@ func createTestUserForProfile(t *testing.T, db *gorm.DB, userID uuid.UUID, usern
|
|||
}
|
||||
err = db.FirstOrCreate(adminRole, models.Role{Name: "admin"}).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
userRole := &models.UserRole{
|
||||
UserID: userID,
|
||||
RoleID: adminRole.ID,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import (
|
|||
|
||||
"veza-backend-api/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apperrors "veza-backend-api/internal/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
@ -248,4 +249,3 @@ func (h *TwoFactorHandler) GetTwoFactorStatus(c *gin.Context) {
|
|||
|
||||
RespondSuccess(c, http.StatusOK, gin.H{"enabled": enabled})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import (
|
|||
"database/sql"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
mathrand "math/rand"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"veza-backend-api/internal/database"
|
||||
"veza-backend-api/internal/models"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue