Initial commit: Эфир мессенджер
This commit is contained in:
383
internal/service/chat_service.go
Normal file
383
internal/service/chat_service.go
Normal file
@@ -0,0 +1,383 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user