diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index b94d7f77b..221411cde 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -1423,7 +1423,18 @@ "description": "GET /api/v1/roles, GET /api/v1/roles/:id, POST /api/v1/users/:userId/roles, DELETE /api/v1/users/:userId/roles/:roleId", "owner": "backend", "estimated_hours": 6, - "status": "todo", + "status": "completed", + "completion": { + "completed_at": "2025-12-23T09:37:27Z", + "actual_hours": 1.0, + "commits": [], + "files_changed": [ + "veza-backend-api/internal/handlers/role_handler.go", + "veza-backend-api/internal/api/router.go" + ], + "notes": "Standardized API responses in RoleHandler to use RespondSuccess and RespondWithAppError. Added setupRoleRoutes function with GET /roles and GET /roles/:id routes. Added POST /users/:userId/roles and DELETE /users/:userId/roles/:roleId routes in setupUserRoutes. Handlers support both :id and :userId parameters for flexibility. All endpoints require authentication.", + "issues_encountered": [] + }, "files_involved": [], "implementation_steps": [ { diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 3eaf5f590..1c268a4ff 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -170,6 +170,9 @@ func (r *APIRouter) Setup(router *gin.Engine) error { r.setupUserRoutes(v1) r.setupTrackRoutes(v1) + // BE-API-007: Roles management routes + r.setupRoleRoutes(v1) + // Réactivation des routes Chat pour Phase 4 r.setupChatRoutes(v1) // Réactivation des routes Playlists pour Phase 5 @@ -382,6 +385,33 @@ func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) { protected.PUT("/:id", r.config.AuthMiddleware.RequireOwnershipOrAdmin("user", userOwnerResolver), profileHandler.UpdateProfile) protected.GET("/:id/completion", profileHandler.GetProfileCompletion) + + // BE-API-007: User role assignment routes + roleService := services.NewRoleService(r.db.GormDB) + roleHandler := handlers.NewRoleHandler(roleService, r.logger) + protected.POST("/:userId/roles", roleHandler.AssignRole) // POST /api/v1/users/:userId/roles + protected.DELETE("/:userId/roles/:roleId", roleHandler.RevokeRole) // DELETE /api/v1/users/:userId/roles/:roleId + } + } +} + +// setupRoleRoutes configure les routes de gestion des rôles +// BE-API-007: Implement roles management endpoints +func (r *APIRouter) setupRoleRoutes(router *gin.RouterGroup) { + roleService := services.NewRoleService(r.db.GormDB) + roleHandler := handlers.NewRoleHandler(roleService, r.logger) + + // Roles routes + roles := router.Group("/roles") + { + // Protected routes + if r.config.AuthMiddleware != nil { + protected := roles.Group("") + protected.Use(r.config.AuthMiddleware.RequireAuth()) + { + protected.GET("", roleHandler.GetRoles) // GET /api/v1/roles + protected.GET("/:id", roleHandler.GetRole) // GET /api/v1/roles/:id + } } } } diff --git a/veza-backend-api/internal/handlers/role_handler.go b/veza-backend-api/internal/handlers/role_handler.go index 3dab6d921..62bc23658 100644 --- a/veza-backend-api/internal/handlers/role_handler.go +++ b/veza-backend-api/internal/handlers/role_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/models" "veza-backend-api/internal/services" ) @@ -26,34 +27,36 @@ func NewRoleHandler(roleService *services.RoleService, logger *zap.Logger) *Role } // GetRoles récupère tous les rôles +// BE-API-007: Implement roles management endpoints func (h *RoleHandler) GetRoles(c *gin.Context) { roles, err := h.roleService.GetRoles(c.Request.Context()) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get roles", err)) return } - c.JSON(http.StatusOK, gin.H{"roles": roles}) + RespondSuccess(c, http.StatusOK, roles) } // GetRole récupère un rôle par ID +// BE-API-007: Implement roles management endpoints func (h *RoleHandler) GetRole(c *gin.Context) { roleIDStr := c.Param("id") roleID, err := uuid.Parse(roleIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid role id")) return } role, err := h.roleService.GetRole(c.Request.Context(), roleID) if err != nil { if err.Error() == "role not found" { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewNotFoundError("role")) } else { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get role", err)) } return } - c.JSON(http.StatusOK, gin.H{"role": role}) + RespondSuccess(c, http.StatusOK, role) } // CreateRole crée un nouveau rôle @@ -118,11 +121,17 @@ func (h *RoleHandler) DeleteRole(c *gin.Context) { } // AssignRole assigne un rôle à un utilisateur +// BE-API-007: Implement roles management endpoints +// Supports both :id and :userId parameters for flexibility func (h *RoleHandler) AssignRole(c *gin.Context) { - userIDStr := c.Param("id") + // Try :userId first (from /users/:userId/roles), fallback to :id + userIDStr := c.Param("userId") + if userIDStr == "" { + 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.NewValidationError("invalid user id")) return } @@ -136,50 +145,49 @@ func (h *RoleHandler) AssignRole(c *gin.Context) { } // Récupérer l'ID de l'utilisateur qui assigne depuis le contexte - assignedByInterface, exists := c.Get("user_id") - if !exists { - c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) - return - } - - assignedBy, ok := assignedByInterface.(uuid.UUID) + assignedBy, ok := GetUserIDUUID(c) if !ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user id type"}) - return + return // Erreur déjà envoyée par GetUserIDUUID } if err := h.roleService.AssignRoleToUser(c.Request.Context(), userID, req.RoleID, assignedBy, req.ExpiresAt); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to assign role", err)) return } - c.JSON(http.StatusOK, gin.H{"message": "role assigned"}) + RespondSuccess(c, http.StatusOK, gin.H{"message": "role assigned"}) } // RevokeRole révoque un rôle d'un utilisateur +// BE-API-007: Implement roles management endpoints +// Supports both :id and :userId parameters for flexibility func (h *RoleHandler) RevokeRole(c *gin.Context) { - userIDStr := c.Param("id") + // Try :userId first (from /users/:userId/roles/:roleId), fallback to :id + userIDStr := c.Param("userId") + if userIDStr == "" { + 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.NewValidationError("invalid user id")) return } roleIDStr := c.Param("roleId") roleID, err := uuid.Parse(roleIDStr) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role id"}) + RespondWithAppError(c, apperrors.NewValidationError("invalid role id")) return } if err := h.roleService.RevokeRoleFromUser(c.Request.Context(), userID, roleID); err != nil { if err.Error() == "role assignment not found" { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.NewNotFoundError("role assignment")) } else { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to revoke role", err)) } return } - c.JSON(http.StatusOK, gin.H{"message": "role revoked"}) + RespondSuccess(c, http.StatusOK, gin.H{"message": "role revoked"}) } // GetUserRoles récupère tous les rôles d'un utilisateur