package handler import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "dating-app/internal/dto" "dating-app/internal/models" "dating-app/internal/repository" "dating-app/internal/service" "dating-app/internal/utils" ) type AdminHandler struct { adminService *service.AdminService optionRepo *repository.OptionRepository questionRepo *repository.QuestionRepository photoRepo *repository.PhotoRepository onboardingService *service.OnboardingService } func NewAdminHandler( adminService *service.AdminService, optionRepo *repository.OptionRepository, questionRepo *repository.QuestionRepository, photoRepo *repository.PhotoRepository, onboardingService *service.OnboardingService, ) *AdminHandler { return &AdminHandler{ adminService: adminService, optionRepo: optionRepo, questionRepo: questionRepo, photoRepo: photoRepo, onboardingService: onboardingService, } } func validCategoryKey(key string) (models.CategoryKey, bool) { k := models.CategoryKey(key) for _, valid := range models.AllCategoryKeys { if valid == k { return k, true } } return "", false } // ---------------- دسته‌ها ---------------- // GetCategory GET /admin/categories/:key func (h *AdminHandler) GetCategory(c *gin.Context) { key, ok := validCategoryKey(c.Param("key")) if !ok { utils.Fail(c, http.StatusNotFound, "دسته‌ی نامعتبر است", nil) return } cat, err := h.adminService.GetCategory(key) if err != nil { utils.Fail(c, http.StatusNotFound, "دسته یافت نشد", nil) return } utils.Success(c, http.StatusOK, "", cat) } // UpdateCategory PATCH /admin/categories/:key func (h *AdminHandler) UpdateCategory(c *gin.Context) { key, ok := validCategoryKey(c.Param("key")) if !ok { utils.Fail(c, http.StatusNotFound, "دسته‌ی نامعتبر است", nil) return } var req dto.UpdateCategoryRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } cat, err := h.adminService.UpdateCategory(key, req) if err != nil { utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusOK, "دسته به‌روزرسانی شد", cat) } // ---------------- گزینه‌ها ---------------- // ListOptions GET /admin/categories/:key/options func (h *AdminHandler) ListOptions(c *gin.Context) { key, ok := validCategoryKey(c.Param("key")) if !ok { utils.Fail(c, http.StatusNotFound, "دسته‌ی نامعتبر است", nil) return } options, err := h.adminService.ListOptions(key) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت گزینه‌ها", nil) return } utils.Success(c, http.StatusOK, "", options) } // CreateOption POST /admin/categories/:key/options func (h *AdminHandler) CreateOption(c *gin.Context) { key, ok := validCategoryKey(c.Param("key")) if !ok { utils.Fail(c, http.StatusNotFound, "دسته‌ی نامعتبر است", nil) return } var req dto.CreateOptionRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } opt, err := h.adminService.CreateOption(key, req) if err != nil { utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusCreated, "گزینه ساخته شد", opt) } // UpdateOption PATCH /admin/options/:id func (h *AdminHandler) UpdateOption(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.UpdateOptionRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } opt, err := h.adminService.UpdateOption(uint(id), req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { utils.Fail(c, http.StatusNotFound, "گزینه یافت نشد", nil) return } utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusOK, "گزینه به‌روزرسانی شد", opt) } // DeleteOption DELETE /admin/options/:id func (h *AdminHandler) DeleteOption(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } if err := h.adminService.DeleteOption(uint(id)); err != nil { utils.Fail(c, http.StatusBadRequest, "حذف گزینه با خطا مواجه شد", nil) return } utils.Success(c, http.StatusOK, "گزینه حذف شد", nil) } // ---------------- بررسی و تایید کاربران ---------------- // ListPendingUsers GET /admin/users/pending func (h *AdminHandler) ListPendingUsers(c *gin.Context) { users, photosByUser, err := h.adminService.ListPendingUsers() if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت لیست کاربران", nil) return } result := make([]dto.UserProfileWithPhotos, 0, len(users)) for i := range users { photos := photosByUser[users[i].ID] if photos == nil { photos = []string{} } result = append(result, dto.UserProfileWithPhotos{ UserProfile: toUserProfile(&users[i]), Photos: photos, }) } utils.Success(c, http.StatusOK, "", result) } // GetUserDetail GET /admin/users/:id — پروفایل کامل یک کاربر برای بررسی ادمین func (h *AdminHandler) GetUserDetail(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } user, err := h.adminService.GetUser(uint(id)) if err != nil { utils.Fail(c, http.StatusNotFound, "کاربر یافت نشد", nil) return } utils.Success(c, http.StatusOK, "", fullProfilePayload(user, h.optionRepo, h.questionRepo, h.onboardingService, h.photoRepo)) } // ApproveUser POST /admin/users/:id/approve func (h *AdminHandler) ApproveUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } user, err := h.adminService.ApproveUser(uint(id)) if err != nil { h.handleUserReviewError(c, err) return } utils.Success(c, http.StatusOK, "کاربر تایید شد", toUserProfile(user)) } // RejectUser POST /admin/users/:id/reject body: {reason} func (h *AdminHandler) RejectUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.RejectUserRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است (ذکر دلیل رد الزامی است)", err.Error()) return } user, err := h.adminService.RejectUser(uint(id), req.Reason) if err != nil { h.handleUserReviewError(c, err) return } utils.Success(c, http.StatusOK, "کاربر رد شد", toUserProfile(user)) } func (h *AdminHandler) handleUserReviewError(c *gin.Context, err error) { switch { case errors.Is(err, gorm.ErrRecordNotFound): utils.Fail(c, http.StatusNotFound, "کاربر یافت نشد", nil) case errors.Is(err, service.ErrUserNotOnboarded): utils.Fail(c, http.StatusConflict, err.Error(), nil) default: utils.Fail(c, http.StatusBadRequest, err.Error(), nil) } } // paginationParams صفحه/تعداد رو از کوئری استرینگ می‌خونه (پیش‌فرض‌های معقول اگه نبود/نامعتبر بود) func paginationParams(c *gin.Context, defaultSize int) (page, pageSize int) { page = 1 if p, err := strconv.Atoi(c.Query("page")); err == nil && p > 0 { page = p } pageSize = defaultSize if ps, err := strconv.Atoi(c.Query("page_size")); err == nil && ps > 0 { pageSize = ps } return } // ---------------- همه‌ی کاربران، بلاک/آنبلاک، گزارش ---------------- // ListAllUsers GET /admin/users?search=&page=&page_size= func (h *AdminHandler) ListAllUsers(c *gin.Context) { page, pageSize := paginationParams(c, 25) search := c.Query("search") items, total, err := h.adminService.ListAllUsers(search, page, pageSize) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت لیست کاربران", nil) return } utils.Success(c, http.StatusOK, "", gin.H{ "items": items, "total": total, "page": page, "page_size": pageSize, }) } // BlockUser POST /admin/users/:id/block body: {reason} func (h *AdminHandler) BlockUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.BlockUserRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است (ذکر دلیل بلاک الزامی است)", err.Error()) return } user, err := h.adminService.BlockUser(uint(id), req.Reason) if err != nil { h.handleUserReviewError(c, err) return } utils.Success(c, http.StatusOK, "کاربر بلاک شد", toUserProfile(user)) } // UnblockUser POST /admin/users/:id/unblock func (h *AdminHandler) UnblockUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } user, err := h.adminService.UnblockUser(uint(id)) if err != nil { h.handleUserReviewError(c, err) return } utils.Success(c, http.StatusOK, "کاربر آنبلاک شد", toUserProfile(user)) } // ReportUser POST /admin/users/:id/report body: {reason} func (h *AdminHandler) ReportUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.ReportUserRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است (ذکر دلیل گزارش الزامی است)", err.Error()) return } report, err := h.adminService.ReportUser(uint(id), req.Reason) if err != nil { h.handleUserReviewError(c, err) return } utils.Success(c, http.StatusCreated, "گزارش ثبت شد", report) } // ListUserReports GET /admin/users/:id/reports func (h *AdminHandler) ListUserReports(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } reports, err := h.adminService.ListReports(uint(id)) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت گزارش‌ها", nil) return } utils.Success(c, http.StatusOK, "", reports) } // ---------------- مچ‌ها (رفقا) ---------------- // ListAllMatches GET /admin/matches?page=&page_size= func (h *AdminHandler) ListAllMatches(c *gin.Context) { page, pageSize := paginationParams(c, 25) items, total, err := h.adminService.ListAllMatches(page, pageSize) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت لیست مچ‌ها", nil) return } utils.Success(c, http.StatusOK, "", gin.H{ "items": items, "total": total, "page": page, "page_size": pageSize, }) } // ---------------- نظارت بر عکس‌ها ---------------- // ListAllPhotos GET /admin/photos?page=&page_size= func (h *AdminHandler) ListAllPhotos(c *gin.Context) { page, pageSize := paginationParams(c, 50) items, total, err := h.adminService.ListAllPhotos(page, pageSize) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت عکس‌ها", nil) return } utils.Success(c, http.StatusOK, "", gin.H{ "items": items, "total": total, "page": page, "page_size": pageSize, }) } // DeletePhoto DELETE /admin/photos/:id func (h *AdminHandler) DeletePhoto(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } if err := h.adminService.DeletePhotoAdmin(uint(id)); err != nil { utils.Fail(c, http.StatusNotFound, "عکس یافت نشد", nil) return } utils.Success(c, http.StatusOK, "عکس حذف شد", nil) } // ReportPhoto POST /admin/photos/:id/report body: {reason} func (h *AdminHandler) ReportPhoto(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.ReportUserRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است (ذکر دلیل گزارش الزامی است)", err.Error()) return } report, err := h.adminService.ReportPhoto(uint(id), req.Reason) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { utils.Fail(c, http.StatusNotFound, "عکس یافت نشد", nil) return } utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusCreated, "گزارش ثبت شد", report) } // ---------------- سوالات پروفایل ---------------- // ListQuestions GET /admin/profile-questions func (h *AdminHandler) ListQuestions(c *gin.Context) { qs, err := h.adminService.ListQuestions() if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت سوالات", nil) return } utils.Success(c, http.StatusOK, "", qs) } // CreateQuestion POST /admin/profile-questions func (h *AdminHandler) CreateQuestion(c *gin.Context) { var req dto.CreateProfileQuestionRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } q, err := h.adminService.CreateQuestion(req) if err != nil { utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusCreated, "سوال ساخته شد", q) } // UpdateQuestion PATCH /admin/profile-questions/:id func (h *AdminHandler) UpdateQuestion(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } var req dto.UpdateProfileQuestionRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } q, err := h.adminService.UpdateQuestion(uint(id), req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { utils.Fail(c, http.StatusNotFound, "سوال یافت نشد", nil) return } utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Success(c, http.StatusOK, "سوال به‌روزرسانی شد", q) } // DeleteQuestion DELETE /admin/profile-questions/:id func (h *AdminHandler) DeleteQuestion(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } if err := h.adminService.DeleteQuestion(uint(id)); err != nil { utils.Fail(c, http.StatusBadRequest, "حذف سوال با خطا مواجه شد", nil) return } utils.Success(c, http.StatusOK, "سوال حذف شد", nil) }