Add production hardening: monitoring, backups, caching, and high-traffic resilience
- Recovery middleware (no more crash-on-panic), request metrics/monitoring store + admin API, in-memory TTL caching for read-heavy option/question/ category lookups, gzip + rate-limiting middleware, sanitized chat message encoding, and pg_dump-based database backups with an admin API. - Fix N+1 queries in the discover feed, candidate detail, and match list endpoints by batching selection/photo/user/last-message lookups into single queries instead of one-per-item loops. - Tune DB connection pooling and add HTTP server timeouts + graceful shutdown so the API degrades gracefully instead of falling over under load. - Fix a null (vs empty array) JSON response for candidates/matches without photos or interests, which crashed the web app's swipe card.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"dating-app/internal/dto"
|
||||
"dating-app/internal/models"
|
||||
"dating-app/internal/repository"
|
||||
"dating-app/internal/utils"
|
||||
)
|
||||
|
||||
var ErrNotMatchMember = errors.New("این گفتگو مال تو نیست")
|
||||
@@ -32,34 +33,65 @@ func NewChatService(
|
||||
}
|
||||
}
|
||||
|
||||
// ListMatches همهی مچهای کاربر رو همراه با اطلاعات طرف مقابل و آخرین پیام برمیگردونه
|
||||
// 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, err := s.userRepo.FindByID(otherID)
|
||||
if err != nil {
|
||||
other, ok := userByID[otherID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
photos, _ := s.photoRepo.ListByUser(otherID)
|
||||
urls := make([]string, len(photos))
|
||||
for i, p := range photos {
|
||||
urls[i] = p.URL
|
||||
}
|
||||
|
||||
view := dto.MatchView{
|
||||
ID: m.ID,
|
||||
UserID: other.ID,
|
||||
FirstName: other.FirstName,
|
||||
Photos: urls,
|
||||
Photos: orEmptyStrings(photosByUser[otherID]),
|
||||
CreatedAt: m.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
if last, err := s.messageRepo.LastMessage(m.ID); err == nil {
|
||||
if last, ok := lastMessages[m.ID]; ok {
|
||||
text := last.Text
|
||||
at := last.CreatedAt.Format(time.RFC3339)
|
||||
view.LastMessage = &text
|
||||
@@ -110,7 +142,12 @@ func (s *ChatService) SendMessage(userID, matchID uint, text string) (*dto.Messa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &models.Message{MatchID: matchID, SenderID: userID, Text: text}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user