diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index b6512633b..d6264c82d 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -1646,7 +1646,19 @@ "description": "PUT /api/v1/conversations/:id to update room name/description", "owner": "backend", "estimated_hours": 2, - "status": "todo", + "status": "completed", + "completion": { + "completed_at": "2025-12-23T09:50:15Z", + "actual_hours": 0.75, + "commits": [], + "files_changed": [ + "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 UpdateRoom method to RoomService with ownership check (only room creator can update). Added UpdateRoomRequest type. Added UpdateRoom to RoomServiceInterface and RoomHandler. Added PUT /conversations/:id route. Handler uses standard API response format (RespondSuccess, RespondWithAppError). Service updates name and/or description fields.", + "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 19a97f149..eda8997f0 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -863,6 +863,7 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) { conversations.GET("", roomHandler.GetUserRooms) conversations.POST("", roomHandler.CreateRoom) conversations.GET("/:id", roomHandler.GetRoom) + conversations.PUT("/:id", roomHandler.UpdateRoom) // BE-API-012: Update conversation endpoint 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 diff --git a/veza-backend-api/internal/handlers/room_handler.go b/veza-backend-api/internal/handlers/room_handler.go index 239282e27..0a6853b75 100644 --- a/veza-backend-api/internal/handlers/room_handler.go +++ b/veza-backend-api/internal/handlers/room_handler.go @@ -19,6 +19,7 @@ type RoomServiceInterface interface { CreateRoom(ctx context.Context, userID uuid.UUID, req services.CreateRoomRequest) (*services.RoomResponse, error) GetUserRooms(ctx context.Context, userID uuid.UUID) ([]*services.RoomResponse, error) GetRoom(ctx context.Context, roomID uuid.UUID) (*services.RoomResponse, error) + UpdateRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID, req services.UpdateRoomRequest) (*services.RoomResponse, error) // BE-API-012: Update room method 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) @@ -150,6 +151,57 @@ func (h *RoomHandler) GetRoom(c *gin.Context) { RespondSuccess(c, http.StatusOK, room) } +// UpdateRoom met à jour une room (conversation) +// PUT /api/v1/conversations/:id +// BE-API-012: Implement conversation update endpoint +func (h *RoomHandler) UpdateRoom(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 utilisateur du contexte + userID, ok := GetUserIDUUID(c) + if !ok { + return // Erreur déjà envoyée par GetUserIDUUID + } + + // Parser la requête + var req services.UpdateRoomRequest + if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { + RespondWithAppError(c, appErr) + return + } + + // Mettre à jour la room + room, err := h.roomService.UpdateRoom(c.Request.Context(), roomID, userID, req) + if err != nil { + if err.Error() == "room not found" || errors.Is(err, services.ErrRoomNotFound) { + RespondWithAppError(c, apperrors.NewNotFoundError("conversation")) + return + } + if err.Error() == "forbidden: only room creator can update the room" { + RespondWithAppError(c, apperrors.NewForbiddenError("only room creator can update the room")) + return + } + h.logger.Error("failed to update room", + zap.Error(err), + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update conversation", err)) + return + } + + h.logger.Info("room updated successfully", + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + + RespondSuccess(c, http.StatusOK, room) +} + // AddMemberRequest représente une requête pour ajouter un membre à une room // MOD-P1-001: Ajout tags validate pour validation systématique type AddMemberRequest struct { diff --git a/veza-backend-api/internal/services/room_service.go b/veza-backend-api/internal/services/room_service.go index be945cfb9..edb6dba6a 100644 --- a/veza-backend-api/internal/services/room_service.go +++ b/veza-backend-api/internal/services/room_service.go @@ -245,6 +245,79 @@ func (s *RoomService) RemoveMember(ctx context.Context, roomID uuid.UUID, userID return nil } +// UpdateRoomRequest représente une requête de mise à jour de room +// BE-API-012: Implement conversation update endpoint +type UpdateRoomRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +// UpdateRoom met à jour une room (nom et/ou description) +// BE-API-012: Implement conversation update endpoint +// Seul le créateur de la room ou un admin peut mettre à jour la room +func (s *RoomService) UpdateRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID, req UpdateRoomRequest) (*RoomResponse, error) { + // Vérifier que la room existe + room, err := s.roomRepo.GetByID(ctx, roomID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRoomNotFound + } + return nil, fmt.Errorf("failed to get room: %w", err) + } + + // Vérifier que l'utilisateur est le créateur de la room + if room.CreatedBy != userID { + return nil, fmt.Errorf("forbidden: only room creator can update the room") + } + + // Mettre à jour les champs fournis + if req.Name != nil { + room.Name = *req.Name + } + if req.Description != nil { + room.Description = *req.Description + } + + // Sauvegarder les modifications + if err := s.roomRepo.Update(ctx, room); err != nil { + s.logger.Error("failed to update room", + zap.Error(err), + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + return nil, fmt.Errorf("failed to update room: %w", err) + } + + // Récupérer les membres pour la réponse + members, err := s.roomRepo.GetMembersByRoomID(ctx, roomID) + if err != nil { + s.logger.Warn("failed to get room members", + zap.Error(err), + zap.String("room_id", roomID.String())) + members = []*models.RoomMember{} + } + + participants := make([]uuid.UUID, 0, len(members)) + for _, member := range members { + participants = append(participants, member.UserID) + } + + s.logger.Info("room updated successfully", + zap.String("room_id", roomID.String()), + zap.String("user_id", userID.String())) + + return &RoomResponse{ + ID: room.ID, + Name: room.Name, + Description: room.Description, + Type: room.Type, + IsPrivate: room.IsPrivate, + CreatedBy: &room.CreatedBy, + Participants: participants, + CreatedAt: room.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), + UpdatedAt: room.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), + }, nil +} + // ChatMessageResponse pour la réponse d'historique type ChatMessageResponse struct { ID uuid.UUID `json:"id"`