Add bio field and daily streak check-in for gamification
- User.Bio: a short (300 char) freeform profile bio, editable via PATCH /profile/bio, shown on discover cards and candidate detail (richer profiles beyond the fixed onboarding fields). - User.StreakCount/LastActiveDate: POST /profile/checkin increments the streak on consecutive days, resets after a gap, no-ops if already checked in today. Backs a daily-return hook on the frontend.
This commit is contained in:
@@ -137,6 +137,7 @@ func (s *DiscoveryService) GetFeed(user *models.User, maxDistanceKM *float64, mi
|
||||
ID: wd.user.ID,
|
||||
FirstName: wd.user.FirstName,
|
||||
Age: utils.CalcAge(wd.user.BirthDate),
|
||||
Bio: derefStr(wd.user.Bio),
|
||||
// نبود عکس/علاقهمندی نباید توی JSON بهصورت null دربیاد؛ کلاینتها
|
||||
// همیشه یک آرایه (حتی خالی) انتظار دارن، نه null.
|
||||
Photos: orEmptyStrings(photosByUser[wd.user.ID]),
|
||||
@@ -154,6 +155,13 @@ func orEmptyStrings(s []string) []string {
|
||||
return s
|
||||
}
|
||||
|
||||
func derefStr(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
// batchInterests علاقهمندیهای تمام کاندیداهای فید رو در یک کوئری واحد
|
||||
// میگیره (بهجای یک کوئری جدا بهازای هر کاندیدا)؛ روی فید دیسکاوری که تا ۶۰
|
||||
// کاربر با هم پردازش میشن، این تفاوت بین ۱ کوئری و تا ۶۰ کوئری رو ایجاد میکنه.
|
||||
@@ -252,6 +260,7 @@ func (s *DiscoveryService) GetCandidateDetail(viewer *models.User, targetID uint
|
||||
ID: target.ID,
|
||||
FirstName: target.FirstName,
|
||||
Age: utils.CalcAge(target.BirthDate),
|
||||
Bio: derefStr(target.Bio),
|
||||
Photos: urls,
|
||||
DistanceKM: distance,
|
||||
HeightCM: target.HeightCM,
|
||||
|
||||
@@ -2,7 +2,10 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dating-app/internal/dto"
|
||||
"dating-app/internal/models"
|
||||
"dating-app/internal/repository"
|
||||
)
|
||||
@@ -97,3 +100,44 @@ func (s *ProfileService) UpdateLocation(user *models.User, lat, lng float64) err
|
||||
"longitude": lng,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBio بیوگرافی کوتاه پروفایل رو ذخیره میکنه (طول توسط binding در DTO چک شده)
|
||||
func (s *ProfileService) UpdateBio(user *models.User, bio string) error {
|
||||
bio = strings.TrimSpace(bio)
|
||||
if err := s.userRepo.UpdateFields(user, map[string]interface{}{"bio": bio}); err != nil {
|
||||
return err
|
||||
}
|
||||
user.Bio = &bio
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckIn ورود روزانهی کاربر رو ثبت میکنه و استریک (چند روز پشتسرهم) رو
|
||||
// بهروز میکنه: اگه دیروز هم اومده بوده، استریک +۱ میشه؛ اگه امروز قبلاً
|
||||
// چکاین کرده، چیزی تغییر نمیکنه؛ وگرنه (وقفه افتاده) استریک از ۱ شروع میشه.
|
||||
func (s *ProfileService) CheckIn(user *models.User) (*dto.CheckInResult, error) {
|
||||
now := time.Now()
|
||||
today := now.Format("2006-01-02")
|
||||
|
||||
if user.LastActiveDate != nil && user.LastActiveDate.Format("2006-01-02") == today {
|
||||
return &dto.CheckInResult{StreakCount: user.StreakCount, IsNewToday: false}, nil
|
||||
}
|
||||
|
||||
newStreak := 1
|
||||
if user.LastActiveDate != nil {
|
||||
yesterday := now.AddDate(0, 0, -1).Format("2006-01-02")
|
||||
if user.LastActiveDate.Format("2006-01-02") == yesterday {
|
||||
newStreak = user.StreakCount + 1
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.userRepo.UpdateFields(user, map[string]interface{}{
|
||||
"streak_count": newStreak,
|
||||
"last_active_date": now,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.StreakCount = newStreak
|
||||
user.LastActiveDate = &now
|
||||
|
||||
return &dto.CheckInResult{StreakCount: newStreak, IsNewToday: true}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user