Files
jiang13-bbs/backend/handler/ad.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)
}