diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index cfe80b126..cec62c516 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -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", diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 4013c1a5e..dafdc3fcf 100644 --- a/veza-backend-api/internal/api/router.go +++ b/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 { diff --git a/veza-backend-api/internal/core/track/track_handler_integration_test.go b/veza-backend-api/internal/core/track/track_handler_integration_test.go index c7d81410d..56e444f41 100644 --- a/veza-backend-api/internal/core/track/track_handler_integration_test.go +++ b/veza-backend-api/internal/core/track/track_handler_integration_test.go @@ -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 } - diff --git a/veza-backend-api/internal/handlers/playlist_handler.go b/veza-backend-api/internal/handlers/playlist_handler.go index 179f262c1..55c994245 100644 --- a/veza-backend-api/internal/handlers/playlist_handler.go +++ b/veza-backend-api/internal/handlers/playlist_handler.go @@ -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 } diff --git a/veza-backend-api/internal/handlers/profile_handler_integration_test.go b/veza-backend-api/internal/handlers/profile_handler_integration_test.go index 51a70809f..5afb27c74 100644 --- a/veza-backend-api/internal/handlers/profile_handler_integration_test.go +++ b/veza-backend-api/internal/handlers/profile_handler_integration_test.go @@ -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, diff --git a/veza-backend-api/internal/handlers/two_factor_handler.go b/veza-backend-api/internal/handlers/two_factor_handler.go index 24e7a113a..b827334ab 100644 --- a/veza-backend-api/internal/handlers/two_factor_handler.go +++ b/veza-backend-api/internal/handlers/two_factor_handler.go @@ -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}) } - diff --git a/veza-backend-api/internal/services/two_factor_service.go b/veza-backend-api/internal/services/two_factor_service.go index e7bbc7915..bc9a18be7 100644 --- a/veza-backend-api/internal/services/two_factor_service.go +++ b/veza-backend-api/internal/services/two_factor_service.go @@ -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"