package service import ( "context" "errors" "fmt" "messenger/internal/crypto" "messenger/internal/models" "messenger/internal/pkg/logger" "messenger/internal/repository" "time" ) type MessageService struct { messageRepo repository.MessageRepository chatRepo repository.ChatRepository userRepo repository.UserRepository attachmentRepo repository.AttachmentRepository encryptor *crypto.Encryptor } func NewMessageService( messageRepo repository.MessageRepository, chatRepo repository.ChatRepository, userRepo repository.UserRepository, attachmentRepo repository.AttachmentRepository, encryptor *crypto.Encryptor, ) *MessageService { return &MessageService{ messageRepo: messageRepo, chatRepo: chatRepo, userRepo: userRepo, attachmentRepo: attachmentRepo, encryptor: encryptor, } } // SendMessage отправляет новое сообщение в чат func (s *MessageService) SendMessage(ctx context.Context, senderID, chatID int64, plaintext string, attachmentID *int64) (*models.MessageResponse, error) { // Проверяем, что пользователь участник чата isMember, err := s.chatRepo.IsMember(ctx, chatID, senderID) if err != nil { return nil, fmt.Errorf("failed to check membership: %w", err) } if !isMember { return nil, errors.New("you are not a member of this chat") } // Шифруем сообщение encryptedBody, err := s.encryptor.EncryptString(plaintext) if err != nil { logger.Error("Failed to encrypt message", "error", err) return nil, errors.New("failed to encrypt message") } // Создаем сообщение message := &models.Message{ ChatID: chatID, SenderID: senderID, EncryptedBody: []byte(encryptedBody), AttachmentID: attachmentID, IsRead: false, CreatedAt: time.Now(), } if err := s.messageRepo.Create(ctx, message); err != nil { logger.Error("Failed to create message", "error", err) return nil, errors.New("failed to send message") } // Обновляем last_seen пользователя go func() { _ = s.userRepo.UpdateLastSeen(context.Background(), senderID) }() // Возвращаем расшифрованное сообщение return &models.MessageResponse{ ID: message.ID, ChatID: message.ChatID, SenderID: message.SenderID, Plaintext: plaintext, IsRead: message.IsRead, CreatedAt: message.CreatedAt, }, nil } // GetMessageByID возвращает сообщение по ID с расшифровкой func (s *MessageService) GetMessageByID(ctx context.Context, messageID int64) (*models.MessageResponse, error) { message, err := s.messageRepo.FindByID(ctx, messageID) if err != nil { return nil, err } if message == nil { return nil, errors.New("message not found") } // Расшифровываем plaintext, err := s.encryptor.DecryptString(string(message.EncryptedBody)) if err != nil { logger.Error("Failed to decrypt message", "error", err) return nil, errors.New("failed to decrypt message") } response := &models.MessageResponse{ ID: message.ID, ChatID: message.ChatID, SenderID: message.SenderID, Plaintext: plaintext, IsRead: message.IsRead, CreatedAt: message.CreatedAt, } // Добавляем информацию о вложении, если есть if message.AttachmentID != nil { attachment, err := s.attachmentRepo.FindByID(ctx, *message.AttachmentID) if err == nil && attachment != nil { response.Attachment = &models.Attachment{ ID: attachment.ID, FileName: attachment.FileName, FileSize: attachment.FileSize, MimeType: attachment.MimeType, UploadedAt: attachment.UploadedAt, } } } return response, nil } // GetChatHistory возвращает историю сообщений чата func (s *MessageService) GetChatHistory(ctx context.Context, chatID, userID int64, limit int, before time.Time) ([]*models.MessageResponse, error) { // Проверяем доступ к чату isMember, err := s.chatRepo.IsMember(ctx, chatID, userID) if err != nil { return nil, err } if !isMember { return nil, errors.New("access denied") } if limit <= 0 || limit > 100 { limit = 50 } messages, err := s.messageRepo.GetChatHistory(ctx, chatID, limit, before) if err != nil { logger.Error("Failed to get chat history", "error", err) return nil, errors.New("failed to get messages") } // Расшифровываем сообщения responses := make([]*models.MessageResponse, 0, len(messages)) for _, msg := range messages { plaintext, err := s.encryptor.DecryptString(string(msg.EncryptedBody)) if err != nil { logger.Error("Failed to decrypt message", "message_id", msg.ID, "error", err) continue } response := &models.MessageResponse{ ID: msg.ID, ChatID: msg.ChatID, SenderID: msg.SenderID, Plaintext: plaintext, IsRead: msg.IsRead, CreatedAt: msg.CreatedAt, } responses = append(responses, response) } return responses, nil } // MarkMessageAsRead отмечает сообщение как прочитанное func (s *MessageService) MarkMessageAsRead(ctx context.Context, messageID, userID int64) error { message, err := s.messageRepo.FindByID(ctx, messageID) if err != nil { return err } if message == nil { return errors.New("message not found") } // Нельзя отметить своё сообщение как прочитанное if message.SenderID == userID { return nil } // Проверяем, что пользователь участник чата isMember, err := s.chatRepo.IsMember(ctx, message.ChatID, userID) if err != nil { return err } if !isMember { return errors.New("access denied") } return s.messageRepo.MarkAsRead(ctx, messageID) } // EditMessage редактирует существующее сообщение func (s *MessageService) EditMessage(ctx context.Context, userID, messageID int64, newPlaintext string) error { message, err := s.messageRepo.FindByID(ctx, messageID) if err != nil { return err } if message == nil { return errors.New("message not found") } // Только автор может редактировать if message.SenderID != userID { return errors.New("only the author can edit the message") } // Шифруем новое содержимое encryptedBody, err := s.encryptor.EncryptString(newPlaintext) if err != nil { logger.Error("Failed to encrypt edited message", "error", err) return errors.New("failed to encrypt message") } message.EncryptedBody = []byte(encryptedBody) return s.messageRepo.Update(ctx, message) } // DeleteMessage удаляет сообщение func (s *MessageService) DeleteMessage(ctx context.Context, messageID int64) error { return s.messageRepo.Delete(ctx, messageID) } // CanDeleteMessage проверяет, может ли пользователь удалить сообщение func (s *MessageService) CanDeleteMessage(ctx context.Context, userID, messageID int64) (bool, error) { message, err := s.messageRepo.FindByID(ctx, messageID) if err != nil { return false, err } if message == nil { return false, errors.New("message not found") } // Автор может удалить своё сообщение if message.SenderID == userID { return true, nil } // Проверяем, является ли пользователь глобальным админом user, err := s.userRepo.FindByID(ctx, userID) if err != nil { return false, err } if user != nil && user.IsGlobalAdmin() { return true, nil } // Проверяем, является ли пользователь админом чата role, err := s.chatRepo.GetMemberRole(ctx, message.ChatID, userID) if err != nil { return false, err } return role != nil && *role == models.MemberRoleAdmin, nil } // GetUnreadCount возвращает количество непрочитанных сообщений в чате func (s *MessageService) GetUnreadCount(ctx context.Context, chatID, userID int64) (int, error) { return s.messageRepo.GetUnreadCount(ctx, chatID, userID) }