chore: merge backend repo history into monorepo at backend/
git-subtree-dir: backend git-subtree-mainline:dc11190527git-subtree-split:acae976622
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user