package service import ( "context" "errors" //"fmt" "messenger/internal/models" "messenger/internal/pkg/logger" "messenger/internal/repository" "time" ) type ChatService struct { chatRepo repository.ChatRepository userRepo repository.UserRepository messageRepo repository.MessageRepository } func NewChatService(chatRepo repository.ChatRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository) *ChatService { return &ChatService{ chatRepo: chatRepo, userRepo: userRepo, messageRepo: messageRepo, } } // CreatePrivateChat создает личный чат между двумя пользователями func (s *ChatService) CreatePrivateChat(ctx context.Context, userID, targetUserID int64) (*models.Chat, error) { // Проверка, что чат не существует existingChat, err := s.chatRepo.FindPrivateChat(ctx, userID, targetUserID) if err != nil { logger.Error("Failed to find private chat", "error", err) return nil, errors.New("internal server error") } if existingChat != nil { return existingChat, nil } // Создаем чат chat := &models.Chat{ Type: models.ChatTypePrivate, CreatedAt: time.Now(), } if err := s.chatRepo.Create(ctx, chat); err != nil { logger.Error("Failed to create chat", "error", err) return nil, errors.New("failed to create chat") } // Добавляем участников if err := s.chatRepo.AddMember(ctx, chat.ID, userID, models.MemberRoleMember); err != nil { return nil, err } if err := s.chatRepo.AddMember(ctx, chat.ID, targetUserID, models.MemberRoleMember); err != nil { return nil, err } logger.Info("Private chat created", "chat_id", chat.ID, "user1", userID, "user2", targetUserID) return chat, nil } // CreateGroupChat создает групповой чат func (s *ChatService) CreateGroupChat(ctx context.Context, creatorID int64, title string, memberLogins []string) (*models.Chat, error) { if title == "" { return nil, errors.New("group title is required") } // Создаем чат chat := &models.Chat{ Type: models.ChatTypeGroup, Title: &title, CreatedAt: time.Now(), } if err := s.chatRepo.Create(ctx, chat); err != nil { logger.Error("Failed to create group chat", "error", err) return nil, errors.New("failed to create chat") } // Добавляем создателя как админа if err := s.chatRepo.AddMember(ctx, chat.ID, creatorID, models.MemberRoleAdmin); err != nil { return nil, err } // Добавляем остальных участников for _, login := range memberLogins { user, err := s.userRepo.FindByLogin(ctx, login) if err != nil { logger.Error("Failed to find user", "login", login, "error", err) continue } if user != nil && user.ID != creatorID { if err := s.chatRepo.AddMember(ctx, chat.ID, user.ID, models.MemberRoleMember); err != nil { logger.Error("Failed to add member", "user_id", user.ID, "error", err) } } } logger.Info("Group chat created", "chat_id", chat.ID, "creator", creatorID, "title", title) return chat, nil } // GetUserChats возвращает все чаты пользователя func (s *ChatService) GetUserChats(ctx context.Context, userID int64) ([]*models.ChatWithDetails, error) { chats, err := s.chatRepo.GetUserChats(ctx, userID) if err != nil { logger.Error("Failed to get user chats", "error", err) return nil, errors.New("internal server error") } var chatsWithDetails []*models.ChatWithDetails for _, chat := range chats { lastMessage, _ := s.messageRepo.GetLastMessage(ctx, chat.ID) unreadCount, _ := s.messageRepo.GetUnreadCount(ctx, chat.ID, userID) chatDetail := &models.ChatWithDetails{ ID: chat.ID, Type: chat.Type, Title: chat.Title, CreatedAt: chat.CreatedAt, UnreadCount: unreadCount, } if lastMessage != nil { chatDetail.LastMessage = lastMessage } chatsWithDetails = append(chatsWithDetails, chatDetail) } return chatsWithDetails, nil } // GetChatByID возвращает чат по ID с проверкой доступа func (s *ChatService) GetChatByID(ctx context.Context, chatID, userID int64) (*models.Chat, error) { // Проверяем, что пользователь участник чата isMember, err := s.chatRepo.IsMember(ctx, chatID, userID) if err != nil { return nil, err } if !isMember { return nil, errors.New("access denied") } return s.chatRepo.FindByID(ctx, chatID) } // GetChatMembers возвращает участников чата func (s *ChatService) GetChatMembers(ctx context.Context, chatID, userID int64) ([]*models.ChatMember, error) { // Проверяем доступ isMember, err := s.chatRepo.IsMember(ctx, chatID, userID) if err != nil { return nil, err } if !isMember { return nil, errors.New("access denied") } return s.chatRepo.GetMembers(ctx, chatID) } // AddMembers добавляет участников в групповой чат func (s *ChatService) AddMembers(ctx context.Context, chatID, adminID int64, userLogins []string) error { // Проверяем, что администратор имеет права role, err := s.chatRepo.GetMemberRole(ctx, chatID, adminID) if err != nil { return err } if role == nil || (*role != models.MemberRoleAdmin) { return errors.New("only admins can add members") } // Проверяем, что чат групповой chat, err := s.chatRepo.FindByID(ctx, chatID) if err != nil { return err } if chat == nil || chat.Type != models.ChatTypeGroup { return errors.New("only group chats can have members added") } // Добавляем участников for _, login := range userLogins { user, err := s.userRepo.FindByLogin(ctx, login) if err != nil || user == nil { logger.Error("User not found", "login", login) continue } isMember, _ := s.chatRepo.IsMember(ctx, chatID, user.ID) if !isMember { if err := s.chatRepo.AddMember(ctx, chatID, user.ID, models.MemberRoleMember); err != nil { logger.Error("Failed to add member", "user_id", user.ID, "error", err) } } } logger.Info("Members added to chat", "chat_id", chatID, "admin_id", adminID) return nil } // RemoveMember удаляет участника из группового чата func (s *ChatService) RemoveMember(ctx context.Context, chatID, adminID, targetID int64) error { // Проверяем права администратора role, err := s.chatRepo.GetMemberRole(ctx, chatID, adminID) if err != nil { return err } if role == nil || (*role != models.MemberRoleAdmin) { return errors.New("only admins can remove members") } // Нельзя удалить самого себя через эту функцию (есть LeaveChat) if adminID == targetID { return errors.New("use leave chat instead") } // Проверяем, что чат групповой chat, err := s.chatRepo.FindByID(ctx, chatID) if err != nil { return err } if chat == nil || chat.Type != models.ChatTypeGroup { return errors.New("only group chats can have members removed") } return s.chatRepo.RemoveMember(ctx, chatID, targetID) } // LeaveChat выход из чата func (s *ChatService) LeaveChat(ctx context.Context, chatID, userID int64) error { chat, err := s.chatRepo.FindByID(ctx, chatID) if err != nil { return err } if chat == nil { return errors.New("chat not found") } // Для приватных чатов выход означает удаление чата? if chat.Type == models.ChatTypePrivate { // В приватном чате выход не допускается, только удаление return errors.New("cannot leave private chat") } // Проверяем, что пользователь участник isMember, err := s.chatRepo.IsMember(ctx, chatID, userID) if err != nil { return err } if !isMember { return errors.New("not a member of this chat") } return s.chatRepo.RemoveMember(ctx, chatID, userID) } // UpdateMemberRole обновляет роль участника в группе func (s *ChatService) UpdateMemberRole(ctx context.Context, chatID, adminID, targetID int64, role models.MemberRole) error { // Проверяем права администратора adminRole, err := s.chatRepo.GetMemberRole(ctx, chatID, adminID) if err != nil { return err } if adminRole == nil || (*adminRole != models.MemberRoleAdmin) { return errors.New("only admins can update member roles") } // Нельзя изменить роль администратора, если он единственный if targetID == adminID && role != models.MemberRoleAdmin { members, err := s.chatRepo.GetMembers(ctx, chatID) if err != nil { return err } adminCount := 0 for _, m := range members { if m.Role == models.MemberRoleAdmin { adminCount++ } } if adminCount <= 1 { return errors.New("cannot remove the only admin") } } return s.chatRepo.UpdateMemberRole(ctx, chatID, targetID, role) } // UpdateChatTitle обновляет название группового чата func (s *ChatService) UpdateChatTitle(ctx context.Context, chatID, adminID int64, title string) error { // Проверяем права администратора role, err := s.chatRepo.GetMemberRole(ctx, chatID, adminID) if err != nil { return err } if role == nil || (*role != models.MemberRoleAdmin) { return errors.New("only admins can update chat title") } if title == "" { return errors.New("title cannot be empty") } return s.chatRepo.UpdateTitle(ctx, chatID, title) } // DeleteChat удаляет чат (только для глобального админа) func (s *ChatService) DeleteChat(ctx context.Context, chatID int64, isGlobalAdmin bool) error { if !isGlobalAdmin { return errors.New("only global admin can delete chats") } return s.chatRepo.Delete(ctx, chatID) } // IsMember проверяет, является ли пользователь участником чата func (s *ChatService) IsMember(ctx context.Context, chatID, userID int64) (bool, error) { return s.chatRepo.IsMember(ctx, chatID, userID) } // GetUserChatsWithDetails возвращает чаты пользователя с информацией о собеседнике func (s *ChatService) GetUserChatsWithDetails(ctx context.Context, userID int64) ([]*models.ChatWithDetails, error) { chats, err := s.chatRepo.GetUserChats(ctx, userID) if err != nil { return nil, err } var chatsWithDetails []*models.ChatWithDetails for _, chat := range chats { lastMessage, _ := s.messageRepo.GetLastMessage(ctx, chat.ID) unreadCount, _ := s.messageRepo.GetUnreadCount(ctx, chat.ID, userID) chatDetail := &models.ChatWithDetails{ ID: chat.ID, Type: chat.Type, CreatedAt: chat.CreatedAt, UnreadCount: unreadCount, } if lastMessage != nil { chatDetail.LastMessage = lastMessage } // Для приватных чатов - подставляем имя собеседника if chat.Type == models.ChatTypePrivate { members, err := s.chatRepo.GetMembers(ctx, chat.ID) if err == nil { for _, member := range members { if member.UserID != userID { otherUser, err := s.userRepo.FindByID(ctx, member.UserID) if err == nil && otherUser != nil { title := otherUser.Login chatDetail.Title = &title } break } } } } else { // Для групповых чатов - используем заданное название chatDetail.Title = chat.Title } chatsWithDetails = append(chatsWithDetails, chatDetail) } return chatsWithDetails, nil }