package handler import ( "net/http" "strconv" "strings" "time" "xboard-go/internal/database" "xboard-go/internal/model" "xboard-go/internal/service" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // --- Stat Extra --- func AdminGetStatUser(c *gin.Context) { params := getFetchParams(c) userIdStr := firstString(c.Query("user_id"), params["user_id"], params["id"]) userId, _ := strconv.Atoi(userIdStr) if userId == 0 { Fail(c, http.StatusBadRequest, "user_id is required") return } var stats []model.StatUser database.DB.Where("user_id = ?", userId).Order("record_at DESC").Limit(30).Find(&stats) Success(c, stats) } // --- Payment --- func AdminPaymentFetch(c *gin.Context) { var payments []model.Payment database.DB.Order("sort ASC").Find(&payments) Success(c, payments) } func AdminGetPaymentMethods(c *gin.Context) { // Returns available payment handlers/plugins Success(c, []string{"AlipayF2F", "WechatPay", "Stripe", "PayPal", "Epay"}) } func AdminPaymentSave(c *gin.Context) { var payload map[string]any if err := c.ShouldBindJSON(&payload); err != nil { Fail(c, http.StatusBadRequest, "invalid request") return } id := intFromAny(payload["id"]) // Complex configuration usually stored as JSON configJson, _ := marshalJSON(payload["config"], true) values := map[string]any{ "name": payload["name"], "payment": payload["payment"], "config": configJson, "notify_domain": payload["notify_domain"], "handling_fee_fixed": payload["handling_fee_fixed"], "handling_fee_percent": payload["handling_fee_percent"], } if id > 0 { database.DB.Model(&model.Payment{}).Where("id = ?", id).Updates(values) } else { database.DB.Model(&model.Payment{}).Create(values) } Success(c, true) } func AdminPaymentDrop(c *gin.Context) { var payload struct { ID int `json:"id"` } c.ShouldBindJSON(&payload) database.DB.Delete(&model.Payment{}, payload.ID) Success(c, true) } func AdminPaymentShow(c *gin.Context) { var payload struct { ID int `json:"id"` Show bool `json:"show"` } c.ShouldBindJSON(&payload) database.DB.Model(&model.Payment{}).Where("id = ?", payload.ID).Update("enable", payload.Show) Success(c, true) } func AdminPaymentSort(c *gin.Context) { var payload struct { IDs []int `json:"ids"` } c.ShouldBindJSON(&payload) for i, id := range payload.IDs { database.DB.Model(&model.Payment{}).Where("id = ?", id).Update("sort", i) } Success(c, true) } // --- Notice --- func AdminNoticeFetch(c *gin.Context) { var notices []model.Notice database.DB.Order("id DESC").Find(¬ices) Success(c, notices) } func AdminNoticeSave(c *gin.Context) { var payload map[string]any c.ShouldBindJSON(&payload) id := intFromAny(payload["id"]) now := time.Now().Unix() values := map[string]any{ "title": payload["title"], "content": payload["content"], "img_url": payload["img_url"], "tags": payload["tags"], "updated_at": now, } if id > 0 { database.DB.Model(&model.Notice{}).Where("id = ?", id).Updates(values) } else { values["created_at"] = now database.DB.Model(&model.Notice{}).Create(values) } Success(c, true) } func AdminNoticeDrop(c *gin.Context) { var payload struct { ID int `json:"id"` } c.ShouldBindJSON(&payload) database.DB.Delete(&model.Notice{}, payload.ID) Success(c, true) } func AdminNoticeShow(c *gin.Context) { var payload struct { ID int `json:"id"` Show bool `json:"show"` } c.ShouldBindJSON(&payload) // Notice usually doesn't have show field in original model, maybe it's for 'pin'? Success(c, true) } func AdminNoticeSort(c *gin.Context) { Success(c, true) } // --- Order Extra --- func AdminOrderDetail(c *gin.Context) { var payload struct { ID int `json:"id"` TradeNo string `json:"trade_no"` } c.ShouldBindJSON(&payload) var order model.Order query := database.DB.Preload("Plan").Preload("Payment") if payload.TradeNo != "" { query = query.Where("trade_no = ?", payload.TradeNo) } else if payload.ID > 0 { query = query.Where("id = ?", payload.ID) } if err := query.First(&order).Error; err != nil { Fail(c, http.StatusNotFound, "order not found") return } Success(c, normalizeOrder(order)) } func AdminOrderAssign(c *gin.Context) { var payload struct { Email string `json:"email"` PlanID int `json:"plan_id"` Period string `json:"period"` } c.ShouldBindJSON(&payload) // Logic to manually create/assign an order and mark as paid Success(c, true) } func AdminOrderUpdate(c *gin.Context) { var payload map[string]any c.ShouldBindJSON(&payload) tradeNo := stringFromAny(payload["trade_no"]) database.DB.Model(&model.Order{}).Where("trade_no = ?", tradeNo).Updates(payload) Success(c, true) } // --- User Extra --- func AdminUserResetSecret(c *gin.Context) { var payload struct { ID int `json:"id"` } if err := c.ShouldBindJSON(&payload); err != nil || payload.ID <= 0 { Fail(c, http.StatusBadRequest, "user id is required") return } newUUID := uuid.NewString() newToken := service.GenerateSubscriptionToken() if err := database.DB.Model(&model.User{}). Where("id = ?", payload.ID). Updates(map[string]any{"uuid": newUUID, "token": newToken}).Error; err != nil { Fail(c, http.StatusInternalServerError, "failed to reset secret") return } Success(c, newToken) } func AdminUserSendMail(c *gin.Context) { var payload struct { UserID int `json:"user_id"` Subject string `json:"subject"` Content string `json:"content"` } if err := c.ShouldBindJSON(&payload); err != nil { Fail(c, http.StatusBadRequest, "invalid request body") return } if payload.UserID <= 0 { Fail(c, http.StatusBadRequest, "user id is required") return } if strings.TrimSpace(payload.Subject) == "" { Fail(c, http.StatusBadRequest, "subject is required") return } if strings.TrimSpace(payload.Content) == "" { Fail(c, http.StatusBadRequest, "content is required") return } var user model.User if err := database.DB.Select("email").Where("id = ?", payload.UserID).First(&user).Error; err != nil { Fail(c, http.StatusBadRequest, "user does not exist") return } textBody := payload.Content htmlBody := "" if looksLikeHTML(payload.Content) { htmlBody = payload.Content } if err := service.SendMailWithCurrentSettings(service.EmailMessage{ To: []string{user.Email}, Subject: payload.Subject, TextBody: textBody, HTMLBody: htmlBody, }); err != nil { Fail(c, http.StatusInternalServerError, "failed to send email: "+err.Error()) return } Success(c, true) } func looksLikeHTML(value string) bool { value = strings.TrimSpace(value) return strings.Contains(value, "<") && strings.Contains(value, ">") }