diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index badd0edc2..a95a2ea05 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -2930,7 +2930,7 @@ "description": "DELETE /api/v1/users/:id with soft delete support", "owner": "backend", "estimated_hours": 4, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -2951,7 +2951,9 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-24T15:03:19.333494", + "implementation_notes": "Implemented DELETE /api/v1/users/:id endpoint with soft delete support. Added DeleteUser method to UserService that uses GORM soft delete (sets deleted_at) and also sets is_active to false. Added DeleteUser handler with ownership/admin check using PermissionService.HasRole. Route registered with RequireOwnershipOrAdmin middleware." }, { "id": "BE-API-042", diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 2a9092330..29d925e4a 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -464,6 +464,8 @@ func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) { return uuid.Parse(userIDStr) } protected.PUT("/:id", r.config.AuthMiddleware.RequireOwnershipOrAdmin("user", userOwnerResolver), profileHandler.UpdateProfile) + // BE-API-041: Delete user endpoint (soft delete) + protected.DELETE("/:id", r.config.AuthMiddleware.RequireOwnershipOrAdmin("user", userOwnerResolver), profileHandler.DeleteUser) protected.GET("/:id/completion", profileHandler.GetProfileCompletion) diff --git a/veza-backend-api/internal/handlers/profile_handler.go b/veza-backend-api/internal/handlers/profile_handler.go index a8b18b879..ebd1e45c9 100644 --- a/veza-backend-api/internal/handlers/profile_handler.go +++ b/veza-backend-api/internal/handlers/profile_handler.go @@ -657,3 +657,60 @@ func isValidUsername(username string) bool { return true } + +// DeleteUser gère la suppression d'un utilisateur (soft delete) +// BE-API-041: DELETE /api/v1/users/:id with soft delete support +// @Summary Delete user +// @Description Soft delete a user (only user owner or admin can delete) +// @Tags User +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "User ID" +// @Success 200 {object} response.APIResponse "User deleted successfully" +// @Failure 400 {object} response.APIResponse "Invalid ID" +// @Failure 401 {object} response.APIResponse "Unauthorized" +// @Failure 403 {object} response.APIResponse "Forbidden - Not user owner or admin" +// @Failure 404 {object} response.APIResponse "User not found" +// @Router /users/{id} [delete] +func (h *ProfileHandler) DeleteUser(c *gin.Context) { + userIDStr := c.Param("id") + userID, err := uuid.Parse(userIDStr) + if err != nil { + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid user id")) + return + } + + // Get the requesting user ID + requesterID, ok := GetUserIDUUID(c) + if !ok { + return // Erreur déjà envoyée par GetUserIDUUID + } + + // Check if requester is the user owner or admin + if requesterID != userID { + // Check if requester is admin + if h.permissionService != nil { + isAdmin, err := h.permissionService.HasRole(c.Request.Context(), requesterID, "admin") + if err != nil || !isAdmin { + RespondWithAppError(c, apperrors.NewForbiddenError("only user owner or admin can delete user")) + return + } + } else { + RespondWithAppError(c, apperrors.NewForbiddenError("only user owner or admin can delete user")) + return + } + } + + // Delete user (soft delete) + if err := h.userService.DeleteUser(c.Request.Context(), userID); err != nil { + if err.Error() == "user not found" { + RespondWithAppError(c, apperrors.NewNotFoundError("user")) + return + } + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to delete user", err)) + return + } + + RespondSuccess(c, http.StatusOK, gin.H{"message": "user deleted successfully"}) +} diff --git a/veza-backend-api/internal/services/user_service.go b/veza-backend-api/internal/services/user_service.go index 542ebaabd..6c1c68213 100644 --- a/veza-backend-api/internal/services/user_service.go +++ b/veza-backend-api/internal/services/user_service.go @@ -1,6 +1,7 @@ package services import ( + "context" "errors" "fmt" "github.com/google/uuid" @@ -745,3 +746,30 @@ func (s *UserService) UpdateUserSettings(userID uuid.UUID, req *types.UpdateSett return nil } + +// DeleteUser soft deletes a user +// BE-API-041: Implement user delete endpoint with soft delete support +func (s *UserService) DeleteUser(ctx context.Context, userID uuid.UUID) error { + // Check if user exists + _, err := s.userRepo.GetByID(userID.String()) + if err != nil { + return fmt.Errorf("user not found") + } + + // Use repository Delete method (soft delete via GORM) + if err := s.userRepo.Delete(userID.String()); err != nil { + return fmt.Errorf("failed to delete user: %w", err) + } + + // Also set is_active to false for consistency + if s.db != nil { + if err := s.db.WithContext(ctx).Model(&models.User{}). + Where("id = ?", userID). + Update("is_active", false).Error; err != nil { + // Log but don't fail if this update fails + return fmt.Errorf("failed to deactivate user: %w", err) + } + } + + return nil +}