254 lines
7.1 KiB
Go
254 lines
7.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/freefire/jiang13-bbs/middleware"
|
|
"github.com/freefire/jiang13-bbs/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CaptchaIssue GET /api/captcha
|
|
func (h *Handlers) CaptchaIssue(c *gin.Context) {
|
|
if h.Ads == nil || h.Ads.Captcha() == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "验证码服务不可用"})
|
|
return
|
|
}
|
|
id, img, err := h.Ads.Captcha().Issue()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成验证码失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"id": id, "image": img})
|
|
}
|
|
|
|
// AdsPublicConfig GET /api/ads/config
|
|
func (h *Handlers) AdsPublicConfig(c *gin.Context) {
|
|
cfg, err := h.Ads.PublicConfig()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取配置失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
type adSubmitReq struct {
|
|
Kind string `json:"kind"`
|
|
ContactEmail string `json:"contact_email"`
|
|
LinkURL string `json:"link_url"`
|
|
ImageURL string `json:"image_url"`
|
|
Title string `json:"title"`
|
|
TextColor string `json:"text_color"`
|
|
BgColor string `json:"bg_color"`
|
|
DurationDays int `json:"duration_days"`
|
|
PaymentID string `json:"payment_id"`
|
|
BuyerNote string `json:"buyer_note"`
|
|
CaptchaID string `json:"captcha_id"`
|
|
CaptchaCode string `json:"captcha_code"`
|
|
}
|
|
|
|
// AdsSubmit POST /api/ads
|
|
func (h *Handlers) AdsSubmit(c *gin.Context) {
|
|
var req adSubmitReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误"})
|
|
return
|
|
}
|
|
ad, err := h.Ads.Submit(service.AdSubmitInput{
|
|
Kind: req.Kind,
|
|
ContactEmail: req.ContactEmail,
|
|
LinkURL: req.LinkURL,
|
|
ImageURL: req.ImageURL,
|
|
Title: req.Title,
|
|
TextColor: req.TextColor,
|
|
BgColor: req.BgColor,
|
|
DurationDays: req.DurationDays,
|
|
PaymentID: req.PaymentID,
|
|
BuyerNote: req.BuyerNote,
|
|
CaptchaID: req.CaptchaID,
|
|
CaptchaCode: req.CaptchaCode,
|
|
IP: c.ClientIP(),
|
|
})
|
|
if err != nil {
|
|
status := http.StatusBadRequest
|
|
switch {
|
|
case errors.Is(err, service.ErrAdCaptcha):
|
|
status = http.StatusForbidden
|
|
case errors.Is(err, service.ErrAdDisabled):
|
|
status = http.StatusServiceUnavailable
|
|
}
|
|
c.JSON(status, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ad": ad, "message": "提交成功,请完成付款并等待审核"})
|
|
}
|
|
|
|
// AdminGetAdsConfig GET /api/admin/ads/config
|
|
func (h *Handlers) AdminGetAdsConfig(c *gin.Context) {
|
|
cfg, err := h.Ads.GetConfig()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
// AdminSaveAdsConfig PUT /api/admin/ads/config
|
|
func (h *Handlers) AdminSaveAdsConfig(c *gin.Context) {
|
|
var req service.AdConfig
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误"})
|
|
return
|
|
}
|
|
cfg, err := h.Ads.SaveConfig(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
// AdminListAds GET /api/admin/ads
|
|
func (h *Handlers) AdminListAds(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
|
list, total, err := h.Ads.AdminList(c.Query("status"), page, size)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ads": list, "total": total, "page": page, "size": size})
|
|
}
|
|
|
|
// AdminApproveAd PUT /api/admin/ads/:id/approve
|
|
func (h *Handlers) AdminApproveAd(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效 ID"})
|
|
return
|
|
}
|
|
actor := middleware.CurrentActor(c)
|
|
ad, err := h.Ads.Approve(actor.ID, uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ad": ad})
|
|
}
|
|
|
|
type adRejectReq struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
// AdminRejectAd PUT /api/admin/ads/:id/reject
|
|
func (h *Handlers) AdminRejectAd(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效 ID"})
|
|
return
|
|
}
|
|
var req adRejectReq
|
|
_ = c.ShouldBindJSON(&req)
|
|
actor := middleware.CurrentActor(c)
|
|
ad, err := h.Ads.Reject(actor.ID, uint(id), req.Reason)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ad": ad})
|
|
}
|
|
|
|
// AdminDeleteAd DELETE /api/admin/ads/:id
|
|
func (h *Handlers) AdminDeleteAd(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效 ID"})
|
|
return
|
|
}
|
|
if err := h.Ads.Delete(uint(id)); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
type adSortReq struct {
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// AdminSortAd PUT /api/admin/ads/:id/sort
|
|
func (h *Handlers) AdminSortAd(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效 ID"})
|
|
return
|
|
}
|
|
var req adSortReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误"})
|
|
return
|
|
}
|
|
if err := h.Ads.SetSort(uint(id), req.SortOrder); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
// AdminUploadAdQR POST /api/admin/upload/ad-qr — 收款码上传到 /uploads/ads/
|
|
func (h *Handlers) AdminUploadAdQR(c *gin.Context) {
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, service.AdAssetMaxBytes+4096)
|
|
fh, err := c.FormFile("file")
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择图片文件"})
|
|
return
|
|
}
|
|
f, err := fh.Open()
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败"})
|
|
return
|
|
}
|
|
defer f.Close()
|
|
url, err := h.Upload.SaveAdAsset(f)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"url": url})
|
|
}
|
|
|
|
// AdminGetSponsorsConfig GET /api/admin/sponsors/config
|
|
func (h *Handlers) AdminGetSponsorsConfig(c *gin.Context) {
|
|
if h.Ads == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "服务不可用"})
|
|
return
|
|
}
|
|
cfg, err := h.Ads.GetSponsorsConfig()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
// AdminSaveSponsorsConfig PUT /api/admin/sponsors/config
|
|
func (h *Handlers) AdminSaveSponsorsConfig(c *gin.Context) {
|
|
if h.Ads == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "服务不可用"})
|
|
return
|
|
}
|
|
var req service.SponsorsConfig
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误"})
|
|
return
|
|
}
|
|
cfg, err := h.Ads.SaveSponsorsConfig(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, cfg)
|
|
}
|