package handlers import ( "context" "errors" "net/http" "strconv" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // RoomServiceInterface defines the interface for room service operations 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) DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error // BE-API-010: Delete room method } // RoomHandler gère les opérations sur les rooms (conversations) type RoomHandler struct { roomService RoomServiceInterface logger *zap.Logger commonHandler *CommonHandler } // NewRoomHandler crée une nouvelle instance de RoomHandler func NewRoomHandler(roomService RoomServiceInterface, logger *zap.Logger) *RoomHandler { return &RoomHandler{ roomService: roomService, logger: logger, commonHandler: NewCommonHandler(logger), } } // CreateRoom gère la création d'une nouvelle room // POST /api/v1/conversations func (h *RoomHandler) CreateRoom(c *gin.Context) { // Récupérer l'ID utilisateur du contexte userIDInterface, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } // Convertir userID en uuid.UUID userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"}) return } // Parser la requête var req services.CreateRoomRequest if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } // Valider le type de room si non spécifié if req.Type == "" { req.Type = "public" } // Créer la room room, err := h.roomService.CreateRoom(c.Request.Context(), userID, req) if err != nil { h.logger.Error("failed to create room", zap.Error(err), zap.String("user_id", userID.String()), zap.String("room_name", req.Name)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create conversation"}) return } h.logger.Info("room created successfully", zap.String("room_id", room.ID.String()), zap.String("user_id", userID.String()), zap.String("room_name", req.Name)) RespondSuccess(c, http.StatusCreated, room) } // GetUserRooms récupère toutes les rooms d'un utilisateur // GET /api/v1/conversations func (h *RoomHandler) GetUserRooms(c *gin.Context) { // Récupérer l'ID utilisateur du contexte userIDInterface, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } // Convertir userID en uuid.UUID userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"}) return } // Récupérer les rooms rooms, err := h.roomService.GetUserRooms(c.Request.Context(), userID) if err != nil { h.logger.Error("failed to get user rooms", zap.Error(err), zap.String("user_id", userID.String())) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch conversations"}) return } RespondSuccess(c, http.StatusOK, gin.H{ "conversations": rooms, "total": len(rooms), }) } // GetRoom récupère une room par son ID // GET /api/v1/conversations/:id func (h *RoomHandler) GetRoom(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 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room ID"}) return } // Récupérer la room room, err := h.roomService.GetRoom(c.Request.Context(), roomID) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) return } h.logger.Error("failed to get room", zap.Error(err), zap.String("room_id", roomID.String())) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get conversation"}) return } 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 { UserID uuid.UUID `json:"user_id" binding:"required" validate:"required"` // Changed to UUID } // AddMember ajoute un membre à une room // POST /api/v1/conversations/:id/members func (h *RoomHandler) AddMember(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 { c.JSON(http.StatusBadRequest, gin.H{"error": "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 membre if err := h.roomService.AddMember(c.Request.Context(), roomID, req.UserID); err != nil { h.logger.Error("failed to add member to room", zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", req.UserID.String())) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add member"}) return } h.logger.Info("member added to room", zap.String("room_id", roomID.String()), zap.String("user_id", req.UserID.String())) RespondSuccess(c, http.StatusOK, gin.H{"message": "Member added successfully"}) } // GetRoomHistory récupère l'historique des messages d'une room // GET /api/v1/conversations/:id/history func (h *RoomHandler) GetRoomHistory(c *gin.Context) { conversationIDStr := c.Param("id") conversationID, err := uuid.Parse(conversationIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid conversation ID"}) return } limit := c.DefaultQuery("limit", "50") offset := c.DefaultQuery("offset", "0") limitInt, err := strconv.Atoi(limit) if err != nil || limitInt <= 0 { limitInt = 50 } offsetInt, err := strconv.Atoi(offset) if err != nil || offsetInt < 0 { offsetInt = 0 } messages, err := h.roomService.GetRoomHistory(c.Request.Context(), conversationID, limitInt, offsetInt) if err != nil { if errors.Is(err, services.ErrRoomNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "Conversation not found"}) return } h.logger.Error("failed to get room history", zap.Error(err), zap.String("conversation_id", conversationID.String())) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get conversation history"}) return } RespondSuccess(c, http.StatusOK, gin.H{"messages": messages}) } // DeleteRoom supprime une room (conversation) // DELETE /api/v1/conversations/:id // BE-API-010: Implement conversation delete endpoint func (h *RoomHandler) DeleteRoom(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 } // Supprimer la room if err := h.roomService.DeleteRoom(c.Request.Context(), roomID, userID); 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 delete the room" { RespondWithAppError(c, apperrors.NewForbiddenError("only room creator can delete the room")) return } h.logger.Error("failed to delete room", zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to delete conversation", err)) return } h.logger.Info("room deleted successfully", zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) 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"}) }