diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index 77375965a..ae6ba0d70 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -1600,7 +1600,20 @@ "description": "POST /api/v1/conversations/:id/participants and DELETE /api/v1/conversations/:id/participants/:userId", "owner": "backend", "estimated_hours": 3, - "status": "todo", + "status": "completed", + "completion": { + "completed_at": "2025-12-23T09:48:25Z", + "actual_hours": 0.75, + "commits": [], + "files_changed": [ + "veza-backend-api/internal/repositories/room_repository.go", + "veza-backend-api/internal/services/room_service.go", + "veza-backend-api/internal/handlers/room_handler.go", + "veza-backend-api/internal/api/router.go" + ], + "notes": "Added RemoveMember method to RoomService and RoomServiceInterface. Corrected RemoveMember in RoomRepository to use uuid.UUID instead of int64. Added AddParticipant and RemoveParticipant handlers in RoomHandler. Added POST /conversations/:id/participants and DELETE /conversations/:id/participants/:userId routes. Handlers use standard API response format (RespondSuccess, RespondWithAppError). Handlers reuse AddMember/RemoveMember service methods.", + "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 12fe59b84..19a97f149 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -865,6 +865,8 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) { conversations.GET("/:id", roomHandler.GetRoom) conversations.DELETE("/:id", roomHandler.DeleteRoom) // BE-API-010: Delete conversation endpoint conversations.POST("/:id/members", roomHandler.AddMember) + conversations.POST("/:id/participants", roomHandler.AddParticipant) // BE-API-011: Add participant endpoint + conversations.DELETE("/:id/participants/:userId", roomHandler.RemoveParticipant) // BE-API-011: Remove participant endpoint conversations.GET("/:id/history", roomHandler.GetRoomHistory) } diff --git a/veza-backend-api/internal/handlers/room_handler.go b/veza-backend-api/internal/handlers/room_handler.go index ffd6ded53..239282e27 100644 --- a/veza-backend-api/internal/handlers/room_handler.go +++ b/veza-backend-api/internal/handlers/room_handler.go @@ -20,6 +20,7 @@ type RoomServiceInterface interface { GetUserRooms(ctx context.Context, userID uuid.UUID) ([]*services.RoomResponse, error) GetRoom(ctx context.Context, roomID uuid.UUID) (*services.RoomResponse, error) AddMember(ctx context.Context, roomID, userID uuid.UUID) error + RemoveMember(ctx context.Context, roomID, userID uuid.UUID) error // BE-API-011: Remove member method GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error // BE-API-010: Delete room method } @@ -270,3 +271,84 @@ func (h *RoomHandler) DeleteRoom(c *gin.Context) { RespondSuccess(c, http.StatusOK, gin.H{"message": "Conversation deleted successfully"}) } + +// AddParticipant ajoute un participant à une conversation +// POST /api/v1/conversations/:id/participants +// BE-API-011: Implement conversation participants endpoints +func (h *RoomHandler) AddParticipant(c *gin.Context) { + // Récupérer l'ID de la room depuis l'URL + roomIDStr := c.Param("id") + roomID, err := uuid.Parse(roomIDStr) + if err != nil { + RespondWithAppError(c, apperrors.NewValidationError("invalid room id")) + return + } + + // Parser la requête + var req AddMemberRequest + if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { + RespondWithAppError(c, appErr) + return + } + + // Ajouter le participant (utilise la même logique que AddMember) + if err := h.roomService.AddMember(c.Request.Context(), roomID, req.UserID); err != nil { + if errors.Is(err, services.ErrRoomNotFound) { + RespondWithAppError(c, apperrors.NewNotFoundError("conversation")) + return + } + h.logger.Error("failed to add participant to room", + zap.Error(err), + zap.String("room_id", roomID.String()), + zap.String("user_id", req.UserID.String())) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to add participant", err)) + return + } + + h.logger.Info("participant added to room", + zap.String("room_id", roomID.String()), + zap.String("user_id", req.UserID.String())) + + RespondSuccess(c, http.StatusOK, gin.H{"message": "Participant added successfully"}) +} + +// RemoveParticipant retire un participant d'une conversation +// DELETE /api/v1/conversations/:id/participants/:userId +// BE-API-011: Implement conversation participants endpoints +func (h *RoomHandler) RemoveParticipant(c *gin.Context) { + // Récupérer l'ID de la room depuis l'URL + roomIDStr := c.Param("id") + roomID, err := uuid.Parse(roomIDStr) + if err != nil { + RespondWithAppError(c, apperrors.NewValidationError("invalid room id")) + return + } + + // Récupérer l'ID de l'utilisateur depuis l'URL + userIDStr := c.Param("userId") + userID, err := uuid.Parse(userIDStr) + if err != nil { + RespondWithAppError(c, apperrors.NewValidationError("invalid user id")) + return + } + + // Retirer le participant + if err := h.roomService.RemoveMember(c.Request.Context(), roomID, userID); err != nil { + if errors.Is(err, services.ErrRoomNotFound) { + RespondWithAppError(c, apperrors.NewNotFoundError("conversation")) + return + } + h.logger.Error("failed to remove participant from room", + zap.Error(err), + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to remove participant", err)) + return + } + + h.logger.Info("participant removed from room", + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + + RespondSuccess(c, http.StatusOK, gin.H{"message": "Participant removed successfully"}) +} diff --git a/veza-backend-api/internal/repositories/room_repository.go b/veza-backend-api/internal/repositories/room_repository.go index 346d77fd1..014af1279 100644 --- a/veza-backend-api/internal/repositories/room_repository.go +++ b/veza-backend-api/internal/repositories/room_repository.go @@ -73,7 +73,8 @@ func (r *RoomRepository) AddMember(ctx context.Context, member *models.RoomMembe } // RemoveMember retire un membre d'une room -func (r *RoomRepository) RemoveMember(ctx context.Context, roomID uuid.UUID, userID int64) error { +// MIGRATION UUID: userID migré vers uuid.UUID +func (r *RoomRepository) RemoveMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { return r.db.WithContext(ctx). Where("room_id = ? AND user_id = ?", roomID, userID). Delete(&models.RoomMember{}).Error diff --git a/veza-backend-api/internal/services/room_service.go b/veza-backend-api/internal/services/room_service.go index 31b73c466..be945cfb9 100644 --- a/veza-backend-api/internal/services/room_service.go +++ b/veza-backend-api/internal/services/room_service.go @@ -217,6 +217,34 @@ func (s *RoomService) AddMember(ctx context.Context, roomID uuid.UUID, userID uu return nil } +// RemoveMember retire un membre d'une room +// BE-API-011: Implement conversation participants endpoints +func (s *RoomService) RemoveMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { + // Vérifier que la room existe + _, err := s.roomRepo.GetByID(ctx, roomID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRoomNotFound + } + return fmt.Errorf("failed to get room: %w", err) + } + + // Retirer le membre + if err := s.roomRepo.RemoveMember(ctx, roomID, userID); err != nil { + s.logger.Error("failed to remove member from room", + zap.Error(err), + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + return fmt.Errorf("failed to remove member: %w", err) + } + + s.logger.Info("member removed from room", + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + + return nil +} + // ChatMessageResponse pour la réponse d'historique type ChatMessageResponse struct { ID uuid.UUID `json:"id"`