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

383 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}