package services import ( "context" "errors" "fmt" "time" // Add time import "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "github.com/google/uuid" // Add uuid import "go.uber.org/zap" "gorm.io/gorm" ) // RoomService gère la logique métier pour les rooms type RoomService struct { roomRepo *repositories.RoomRepository messageRepo *repositories.ChatMessageRepository logger *zap.Logger } // NewRoomService crée une nouvelle instance de RoomService func NewRoomService(roomRepo *repositories.RoomRepository, messageRepo *repositories.ChatMessageRepository, logger *zap.Logger) *RoomService { return &RoomService{ roomRepo: roomRepo, messageRepo: messageRepo, logger: logger, } } // CreateRoomRequest représente une requête de création de room type CreateRoomRequest struct { Name string `json:"name" binding:"required,min=1,max=255"` Description *string `json:"description,omitempty"` Type string `json:"type" binding:"required,oneof=public private direct"` IsPrivate bool `json:"is_private"` } // RoomResponse représente une réponse de room pour l'API // MIGRATION UUID: CreatedBy et Participants migrés vers UUID type RoomResponse struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Type string `json:"type"` IsPrivate bool `json:"is_private"` CreatedBy *uuid.UUID `json:"created_by"` Participants []uuid.UUID `json:"participants"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // CreateRoom crée une nouvelle room func (s *RoomService) CreateRoom(ctx context.Context, userID uuid.UUID, req CreateRoomRequest) (*RoomResponse, error) { if req.Name == "" { return nil, errors.New("room name is required") } // Créer la room room := &models.Room{ Name: req.Name, Description: "", Type: req.Type, IsPrivate: req.IsPrivate, CreatedBy: userID, // Corrected: userID is uuid.UUID, models.Room.CreatedBy is uuid.UUID } if req.Description != nil { room.Description = *req.Description } if err := s.roomRepo.Create(ctx, room); err != nil { s.logger.Error("failed to create room", zap.Error(err), zap.String("user_id", userID.String()), zap.String("room_name", req.Name)) return nil, fmt.Errorf("failed to create room: %w", err) } // Ajouter le créateur comme membre admin member := &models.RoomMember{ RoomID: room.ID, // use uuid UserID: userID, Role: "admin", } if err := s.roomRepo.AddMember(ctx, member); err != nil { s.logger.Error("failed to add creator as room member", zap.Error(err), zap.String("room_id", room.ID.String()), zap.String("user_id", userID.String())) // Ne pas retourner d'erreur, la room est créée } s.logger.Info("room created successfully", zap.String("room_id", room.ID.String()), zap.String("user_id", userID.String()), zap.String("room_name", room.Name)) return &RoomResponse{ ID: room.ID, Name: room.Name, Description: room.Description, Type: room.Type, IsPrivate: room.IsPrivate, CreatedBy: &room.CreatedBy, // Corrected: & to get pointer to uuid.UUID Participants: []uuid.UUID{userID}, CreatedAt: room.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: room.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), }, nil } // GetUserRooms récupère toutes les rooms d'un utilisateur func (s *RoomService) GetUserRooms(ctx context.Context, userID uuid.UUID) ([]*RoomResponse, error) { rooms, err := s.roomRepo.GetByUserID(ctx, userID) if err != nil { s.logger.Error("failed to get user rooms", zap.Error(err), zap.String("user_id", userID.String())) return nil, fmt.Errorf("failed to get user rooms: %w", err) } responses := make([]*RoomResponse, 0, len(rooms)) for _, room := range rooms { // Récupérer les membres pour avoir la liste des participants members, err := s.roomRepo.GetMembersByRoomID(ctx, room.ID) if err != nil { s.logger.Warn("failed to get room members", zap.Error(err), zap.String("room_id", room.ID.String())) members = []*models.RoomMember{} } participants := make([]uuid.UUID, 0, len(members)) for _, member := range members { participants = append(participants, member.UserID) } responses = append(responses, &RoomResponse{ ID: room.ID, Name: room.Name, Description: room.Description, Type: room.Type, IsPrivate: room.IsPrivate, CreatedBy: &room.CreatedBy, // Corrected: & to get pointer to uuid.UUID Participants: participants, CreatedAt: room.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: room.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), }) } return responses, nil } // GetRoom récupère une room par son ID func (s *RoomService) GetRoom(ctx context.Context, roomID uuid.UUID) (*RoomResponse, error) { room, err := s.roomRepo.GetByID(ctx, roomID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrRoomNotFound } s.logger.Error("failed to get room", zap.Error(err), zap.String("room_id", roomID.String())) return nil, fmt.Errorf("failed to get room: %w", err) } // Récupérer les membres 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) } return &RoomResponse{ ID: room.ID, Name: room.Name, Description: room.Description, Type: room.Type, IsPrivate: room.IsPrivate, CreatedBy: &room.CreatedBy, // Corrected: & to get pointer to uuid.UUID Participants: participants, CreatedAt: room.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), UpdatedAt: room.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), }, nil } // AddMember ajoute un membre à une room func (s *RoomService) AddMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { member := &models.RoomMember{ RoomID: roomID, UserID: userID, Role: "member", } if err := s.roomRepo.AddMember(ctx, member); err != nil { s.logger.Error("failed to add member to room", zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) return fmt.Errorf("failed to add member: %w", err) } s.logger.Info("member added to room", zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) 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 } // 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"` ConversationID uuid.UUID `json:"conversation_id"` SenderID uuid.UUID `json:"sender_id"` Content string `json:"content"` MessageType string `json:"message_type"` CreatedAt time.Time `json:"created_at"` } // GetRoomHistory récupère l'historique des messages d'une room func (s *RoomService) GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]ChatMessageResponse, error) { messages, err := s.messageRepo.GetConversationMessages(ctx, roomID, limit, offset) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) || err.Error() == "conversation not found" { // Check if room exists first? Assuming Repo handles it or we could use GetRoom logic // If messageRepo returns error on room not found return nil, ErrRoomNotFound } s.logger.Error("failed to get room history", zap.Error(err), zap.String("room_id", roomID.String())) return nil, fmt.Errorf("failed to get room history: %w", err) } responses := make([]ChatMessageResponse, len(messages)) for i, msg := range messages { responses[i] = ChatMessageResponse{ ID: msg.ID, ConversationID: msg.ConversationID, SenderID: msg.SenderID, Content: msg.Content, MessageType: msg.MessageType, CreatedAt: msg.CreatedAt, } } return responses, nil } // DeleteRoom supprime une room (soft delete) // BE-API-010: Implement conversation delete endpoint // Seul le créateur de la room ou un admin peut supprimer la room func (s *RoomService) DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { // Vérifier que la room existe room, 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) } // Vérifier que l'utilisateur est le créateur de la room if room.CreatedBy != userID { return fmt.Errorf("forbidden: only room creator can delete the room") } // Supprimer la room (soft delete via GORM) if err := s.roomRepo.Delete(ctx, roomID); err != nil { s.logger.Error("failed to delete room", zap.Error(err), zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) return fmt.Errorf("failed to delete room: %w", err) } s.logger.Info("room deleted successfully", zap.String("room_id", roomID.String()), zap.String("user_id", userID.String())) return nil }