Add photo upload as a real onboarding step
Photos are now step 2 of onboarding (right after basic info) instead of only being manageable from the profile page later. Uploading still goes through the existing /profile/photos endpoint; the new POST /onboarding/photos just confirms at least one photo exists and advances the step counter. Renumbered the OnboardingStep enum accordingly (9 steps total now). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,23 +78,28 @@ func (s *KavenegarOTPSender) Send(phone, otp string) bool {
|
||||
|
||||
## فرآیند آنبوردینگ
|
||||
|
||||
۸ مرحلهی ثابت که باید بهترتیب طی بشن (نمیشه از روی مرحله پرید، ولی مراحل
|
||||
۹ مرحلهی ثابت که باید بهترتیب طی بشن (نمیشه از روی مرحله پرید، ولی مراحل
|
||||
قبلی هر وقت بخوای قابل ویرایشاند؛ بعد از تکمیل کامل هم همهی مراحل آزادانه
|
||||
قابل ویرایش میشن):
|
||||
قابل ویرایش میشن — حتی توی همون بازهای که کاربر منتظر تایید ادمینه):
|
||||
|
||||
| # | مرحله | Endpoint |
|
||||
|---|-------|----------|
|
||||
| ۱ | جنسیت، اسم، تاریخ تولد | `POST /onboarding/basic-info` |
|
||||
| ۲ | قد و وزن | `POST /onboarding/body-info` |
|
||||
| ۳ | ۳ سوال از پیشتعریفشده + پاسخ | `POST /onboarding/profile-questions` |
|
||||
| ۴ | سطح تحصیلات + رشته | `POST /onboarding/education` |
|
||||
| ۵ | کارهایی که انجام میدهد (چندتایی) | `POST /onboarding/occupations` |
|
||||
| ۶ | ۳ چیز مهم در رابطه | `POST /onboarding/relationship-importance` |
|
||||
| ۷ | ۵ علاقهمندی | `POST /onboarding/interests` |
|
||||
| ۸ | دنبال چی میگردی | `POST /onboarding/looking-for` |
|
||||
| ۲ | عکسها (حداقل ۱ عکس؛ خودِ آپلود از `/profile/photos`) | `POST /onboarding/photos` |
|
||||
| ۳ | قد و وزن | `POST /onboarding/body-info` |
|
||||
| ۴ | ۳ سوال از پیشتعریفشده + پاسخ | `POST /onboarding/profile-questions` |
|
||||
| ۵ | سطح تحصیلات + رشته | `POST /onboarding/education` |
|
||||
| ۶ | کارهایی که انجام میدهد (چندتایی) | `POST /onboarding/occupations` |
|
||||
| ۷ | ۳ چیز مهم در رابطه | `POST /onboarding/relationship-importance` |
|
||||
| ۸ | ۵ علاقهمندی | `POST /onboarding/interests` |
|
||||
| ۹ | دنبال چی میگردی | `POST /onboarding/looking-for` |
|
||||
|
||||
وضعیت فعلی: `GET /onboarding/status`
|
||||
|
||||
اگه کاربر قبلاً رد شده باشه و بعد از onboarded شدن هرکدوم از مراحل رو دوباره
|
||||
بزنه (حتی مرحلهی عکسها)، `approval_status` خودکار به `pending` برمیگرده تا
|
||||
ادمین دوباره بررسی کنه.
|
||||
|
||||
### بعد از تکمیل آنبوردینگ: تایید ادمین
|
||||
|
||||
به محض تکمیل مرحلهی ۸، `approval_status` کاربر روی `pending` قرار میگیرد.
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ func main() {
|
||||
userRepo, otpSender, jwtManager,
|
||||
cfg.OTPExpirySeconds, cfg.OTPLength, cfg.OTPHashSecret,
|
||||
)
|
||||
onboardingService := service.NewOnboardingService(userRepo, optionRepo, questionRepo)
|
||||
onboardingService := service.NewOnboardingService(userRepo, optionRepo, questionRepo, photoRepo)
|
||||
adminService := service.NewAdminService(optionRepo, questionRepo, userRepo)
|
||||
profileService := service.NewProfileService(photoRepo, userRepo, photoStorage)
|
||||
discoveryService := service.NewDiscoveryService(userRepo, photoRepo, swipeRepo, matchRepo, optionRepo, questionRepo)
|
||||
|
||||
@@ -66,6 +66,19 @@ func (h *OnboardingHandler) BasicInfo(c *gin.Context) {
|
||||
utils.Success(c, http.StatusOK, "اطلاعات پایه ثبت شد", h.onboardingService.GetStatus(user))
|
||||
}
|
||||
|
||||
// Photos POST /onboarding/photos — تایید مرحلهی عکسها (خودِ آپلود از /profile/photos انجام میشه)
|
||||
func (h *OnboardingHandler) Photos(c *gin.Context) {
|
||||
user, ok := currentUser(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.onboardingService.ConfirmPhotos(user); err != nil {
|
||||
h.handleStepError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, http.StatusOK, "عکسها ثبت شد", h.onboardingService.GetStatus(user))
|
||||
}
|
||||
|
||||
// BodyInfo POST /onboarding/body-info
|
||||
func (h *OnboardingHandler) BodyInfo(c *gin.Context) {
|
||||
user, ok := currentUser(c)
|
||||
|
||||
@@ -26,6 +26,7 @@ type OnboardingStep int
|
||||
|
||||
const (
|
||||
StepBasicInfo OnboardingStep = iota + 1
|
||||
StepPhotos
|
||||
StepBodyInfo
|
||||
StepProfileQuestions
|
||||
StepEducation
|
||||
@@ -58,6 +59,7 @@ var OnboardingStepOrder = []struct {
|
||||
Key string
|
||||
}{
|
||||
{StepBasicInfo, "basic_info"},
|
||||
{StepPhotos, "photos"},
|
||||
{StepBodyInfo, "body_info"},
|
||||
{StepProfileQuestions, "profile_questions"},
|
||||
{StepEducation, "education"},
|
||||
|
||||
@@ -59,6 +59,7 @@ func Setup(
|
||||
{
|
||||
onboarding.GET("/status", h.Onboarding.Status)
|
||||
onboarding.POST("/basic-info", h.Onboarding.BasicInfo)
|
||||
onboarding.POST("/photos", h.Onboarding.Photos)
|
||||
onboarding.POST("/body-info", h.Onboarding.BodyInfo)
|
||||
onboarding.GET("/profile-questions", h.Onboarding.AvailableQuestions)
|
||||
onboarding.POST("/profile-questions", h.Onboarding.SetProfileQuestions)
|
||||
|
||||
@@ -17,23 +17,27 @@ var (
|
||||
ErrSelectionCount = errors.New("تعداد گزینههای انتخابی مجاز نیست")
|
||||
ErrQuestionsInvalid = errors.New("سوالات انتخابی معتبر نیستند")
|
||||
ErrDuplicateQuestion = errors.New("هر سوال فقط یکبار میتواند انتخاب شود")
|
||||
ErrNoPhotos = errors.New("برای ادامه حداقل باید یک عکس اضافه کنی")
|
||||
)
|
||||
|
||||
type OnboardingService struct {
|
||||
userRepo *repository.UserRepository
|
||||
optionRepo *repository.OptionRepository
|
||||
questionRepo *repository.QuestionRepository
|
||||
photoRepo *repository.PhotoRepository
|
||||
}
|
||||
|
||||
func NewOnboardingService(
|
||||
userRepo *repository.UserRepository,
|
||||
optionRepo *repository.OptionRepository,
|
||||
questionRepo *repository.QuestionRepository,
|
||||
photoRepo *repository.PhotoRepository,
|
||||
) *OnboardingService {
|
||||
return &OnboardingService{
|
||||
userRepo: userRepo,
|
||||
optionRepo: optionRepo,
|
||||
questionRepo: questionRepo,
|
||||
photoRepo: photoRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +113,25 @@ func (s *OnboardingService) SetBasicInfo(user *models.User, req dto.BasicInfoReq
|
||||
return s.advanceIfCurrent(user, models.StepBasicInfo)
|
||||
}
|
||||
|
||||
// ---------------- مرحله ۲: قد و وزن ----------------
|
||||
// ---------------- مرحله ۲: عکسها (حداقل ۱ عکس) ----------------
|
||||
|
||||
// ConfirmPhotos عکس واقعی رو از طریق endpoint عمومی /profile/photos آپلود میکنیم؛
|
||||
// این متد فقط تایید میکنه که حداقل یک عکس هست و مرحله رو جلو میبره.
|
||||
func (s *OnboardingService) ConfirmPhotos(user *models.User) error {
|
||||
if err := s.canModifyStep(user, models.StepPhotos); err != nil {
|
||||
return err
|
||||
}
|
||||
count, err := s.photoRepo.CountByUser(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
return ErrNoPhotos
|
||||
}
|
||||
return s.advanceIfCurrent(user, models.StepPhotos)
|
||||
}
|
||||
|
||||
// ---------------- مرحله ۳: قد و وزن ----------------
|
||||
|
||||
func (s *OnboardingService) SetBodyInfo(user *models.User, req dto.BodyInfoRequest) error {
|
||||
if err := s.canModifyStep(user, models.StepBodyInfo); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user