Files
efir-api-server/internal/service/chat_service.go

383 lines
12 KiB
Go
Raw Normal View History

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
}