[BE-API-012] be-api: Implement conversation update endpoint

- Added UpdateRoom method to RoomService with ownership check
- Only room creator can update the room
- Added UpdateRoomRequest type
- Added UpdateRoom to RoomServiceInterface and RoomHandler
- Added PUT /conversations/:id route
- Handler uses standard API response format
- Service updates name and/or description fields

Phase: PHASE-2
Priority: P1
Progress: 21/267 (7.9%)
This commit is contained in:
senke 2025-12-23 10:51:18 +01:00
parent 23afb1fca5
commit f9e10b5d94
4 changed files with 139 additions and 1 deletions

View file

@ -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": [
{

View file

@ -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

View file

@ -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 {

View file

@ -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"`