package service import ( "errors" "time" "dating-app/internal/dto" "dating-app/internal/models" "dating-app/internal/repository" "dating-app/internal/utils" ) var ErrNotMatchMember = errors.New("این گفتگو مال تو نیست") type ChatService struct { matchRepo *repository.MatchRepository messageRepo *repository.MessageRepository userRepo *repository.UserRepository photoRepo *repository.PhotoRepository } func NewChatService( matchRepo *repository.MatchRepository, messageRepo *repository.MessageRepository, userRepo *repository.UserRepository, photoRepo *repository.PhotoRepository, ) *ChatService { return &ChatService{ matchRepo: matchRepo, messageRepo: messageRepo, userRepo: userRepo, photoRepo: photoRepo, } } // ListMatches همه‌ی مچ‌های کاربر رو همراه با اطلاعات طرف مقابل و آخرین پیام // برمی‌گردونه. قبلاً به‌ازای هر مچ ۳ کوئری جدا (کاربر، عکس‌ها، آخرین پیام) // می‌زد؛ الان هر سه رو batch می‌کنه تا صرف‌نظر از تعداد مچ‌ها، تعداد کوئری‌ها // ثابت (۴ تا) بمونه. func (s *ChatService) ListMatches(userID uint) ([]dto.MatchView, error) { matches, err := s.matchRepo.ListByUser(userID) if err != nil { return nil, err } if len(matches) == 0 { return []dto.MatchView{}, nil } otherIDs := make([]uint, len(matches)) matchIDs := make([]uint, len(matches)) for i, m := range matches { otherIDs[i] = repository.OtherUserID(&m, userID) matchIDs[i] = m.ID } otherUsers, err := s.userRepo.FindByIDs(otherIDs) if err != nil { return nil, err } userByID := make(map[uint]models.User, len(otherUsers)) for _, u := range otherUsers { userByID[u.ID] = u } photos, err := s.photoRepo.ListByUserIDs(otherIDs) if err != nil { return nil, err } photosByUser := make(map[uint][]string, len(otherIDs)) for _, p := range photos { photosByUser[p.UserID] = append(photosByUser[p.UserID], p.URL) } lastMessages, err := s.messageRepo.LastMessagesByMatchIDs(matchIDs) if err != nil { return nil, err } views := make([]dto.MatchView, 0, len(matches)) for _, m := range matches { otherID := repository.OtherUserID(&m, userID) other, ok := userByID[otherID] if !ok { continue } view := dto.MatchView{ ID: m.ID, UserID: other.ID, FirstName: other.FirstName, Photos: orEmptyStrings(photosByUser[otherID]), CreatedAt: m.CreatedAt.Format(time.RFC3339), } if last, ok := lastMessages[m.ID]; ok { text := last.Text at := last.CreatedAt.Format(time.RFC3339) view.LastMessage = &text view.LastAt = &at } views = append(views, view) } return views, nil } func (s *ChatService) getAuthorizedMatch(userID, matchID uint) (uint, error) { match, err := s.matchRepo.FindByID(matchID) if err != nil { return 0, err } if !repository.IsMember(match, userID) { return 0, ErrNotMatchMember } return match.ID, nil } // ListMessages پیام‌های یک مچ رو برمی‌گردونه؛ فقط اگه کاربر عضو اون مچ باشه. func (s *ChatService) ListMessages(userID, matchID uint) ([]dto.MessageView, error) { if _, err := s.getAuthorizedMatch(userID, matchID); err != nil { return nil, err } messages, err := s.messageRepo.ListByMatch(matchID) if err != nil { return nil, err } views := make([]dto.MessageView, 0, len(messages)) for _, m := range messages { views = append(views, dto.MessageView{ ID: m.ID, MatchID: m.MatchID, SenderID: m.SenderID, Text: m.Text, CreatedAt: m.CreatedAt.Format(time.RFC3339), IsMine: m.SenderID == userID, }) } return views, nil } // SendMessage یک پیام جدید توی یک مچ ثبت می‌کنه؛ فقط اگه کاربر عضو اون مچ باشه. func (s *ChatService) SendMessage(userID, matchID uint, text string) (*dto.MessageView, error) { if _, err := s.getAuthorizedMatch(userID, matchID); err != nil { return nil, err } clean, err := utils.SanitizeMessageText(text) if err != nil { return nil, err } msg := &models.Message{MatchID: matchID, SenderID: userID, Text: clean} if err := s.messageRepo.Create(msg); err != nil { return nil, err } return &dto.MessageView{ ID: msg.ID, MatchID: msg.MatchID, SenderID: msg.SenderID, Text: msg.Text, CreatedAt: msg.CreatedAt.Format(time.RFC3339), IsMine: true, }, nil }