Files
SingBox-Gopanel/internal/handler/admin_extra_api.go
CN-JS-HuiBai 9d51bc873f
Some checks failed
build / build (api, amd64, linux) (push) Failing after -50s
build / build (api, arm64, linux) (push) Failing after -52s
build / build (api.exe, amd64, windows) (push) Failing after -51s
临时提交
2026-04-20 01:47:48 +08:00

294 lines
7.3 KiB
Go

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(&notices)
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, ">")
}
// --- Commission ---
func AdminCommissionFetch(c *gin.Context) {
var orders []model.Order
// Commissions are orders that are completed/paid and have commission data
database.DB.Preload("User").Where("commission_balance > 0").Order("id DESC").Find(&orders)
Success(c, orders)
}
func AdminCommissionUpdate(c *gin.Context) {
var payload struct {
TradeNo string `json:"trade_no"`
Status int `json:"status"` // 0: pending, 1: valid, 2: rejected
}
if err := c.ShouldBindJSON(&payload); err != nil {
Fail(c, http.StatusBadRequest, "invalid request")
return
}
database.DB.Model(&model.Order{}).Where("trade_no = ?", payload.TradeNo).Update("commission_status", payload.Status)
Success(c, true)
}