feat(topup): Admin-wide topup listing and route reorganization

Allow administrators to view all platform topup orders and streamline admin-only routes.

Frontend
- TopupHistoryModal: dynamically switch endpoint by role
  - Admin → GET /api/user/topup (all orders)
  - Non-admin → GET /api/user/topup/self (own orders)
- Use shared utils `isAdmin()`; keep logic centralized and DRY
- Minor UI: set admin action button theme to outline for clarity

Backend
- model/topup.go: add GetAllTopUps(pageInfo) with pagination (ordered by id desc)
- controller/topup.go: add GetAllTopUps handler returning PageInfo response
- router/api-router.go:
  - Add admin route GET /api/user/topup (AdminAuth)
  - Move POST /api/user/topup/complete to adminRoute (keeps path stable, consolidates admin endpoints)

Security/Behavior
- Admin-only endpoints now reside under the admin route group with AdminAuth
- No behavior change for regular users; no schema changes

Affected files
- model/topup.go
- controller/topup.go
- router/api-router.go
- web/src/components/topup/modals/TopupHistoryModal.jsx
This commit is contained in:
Apple\Apple
2025-10-07 00:46:47 +08:00
parent 6ef95c97cc
commit 2389dbafc5
4 changed files with 50 additions and 4 deletions

View File

@@ -330,6 +330,21 @@ func GetUserTopUps(c *gin.Context) {
common.ApiSuccess(c, pageInfo) common.ApiSuccess(c, pageInfo)
} }
// GetAllTopUps 管理员获取全平台充值记录
func GetAllTopUps(c *gin.Context) {
pageInfo := common.GetPageQuery(c)
topups, total, err := model.GetAllTopUps(pageInfo)
if err != nil {
common.ApiError(c, err)
return
}
pageInfo.SetTotal(int(total))
pageInfo.SetItems(topups)
common.ApiSuccess(c, pageInfo)
}
type AdminCompleteTopupRequest struct { type AdminCompleteTopupRequest struct {
TradeNo string `json:"trade_no"` TradeNo string `json:"trade_no"`
} }

View File

@@ -136,6 +136,35 @@ func GetUserTopUps(userId int, pageInfo *common.PageInfo) (topups []*TopUp, tota
return topups, total, nil return topups, total, nil
} }
// GetAllTopUps 获取全平台的充值记录(管理员使用)
func GetAllTopUps(pageInfo *common.PageInfo) (topups []*TopUp, total int64, err error) {
tx := DB.Begin()
if tx.Error != nil {
return nil, 0, tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err = tx.Model(&TopUp{}).Count(&total).Error; err != nil {
tx.Rollback()
return nil, 0, err
}
if err = tx.Order("id desc").Limit(pageInfo.GetPageSize()).Offset(pageInfo.GetStartIdx()).Find(&topups).Error; err != nil {
tx.Rollback()
return nil, 0, err
}
if err = tx.Commit().Error; err != nil {
return nil, 0, err
}
return topups, total, nil
}
// ManualCompleteTopUp 管理员手动完成订单并给用户充值 // ManualCompleteTopUp 管理员手动完成订单并给用户充值
func ManualCompleteTopUp(tradeNo string) error { func ManualCompleteTopUp(tradeNo string) error {
if tradeNo == "" { if tradeNo == "" {

View File

@@ -80,7 +80,6 @@ func SetApiRouter(router *gin.Engine) {
selfRoute.POST("/stripe/pay", middleware.CriticalRateLimit(), controller.RequestStripePay) selfRoute.POST("/stripe/pay", middleware.CriticalRateLimit(), controller.RequestStripePay)
selfRoute.POST("/stripe/amount", controller.RequestStripeAmount) selfRoute.POST("/stripe/amount", controller.RequestStripeAmount)
selfRoute.POST("/aff_transfer", controller.TransferAffQuota) selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
selfRoute.POST("/topup/complete", middleware.AdminAuth(), controller.AdminCompleteTopUp)
selfRoute.PUT("/setting", controller.UpdateUserSetting) selfRoute.PUT("/setting", controller.UpdateUserSetting)
// 2FA routes // 2FA routes
@@ -95,6 +94,8 @@ func SetApiRouter(router *gin.Engine) {
adminRoute.Use(middleware.AdminAuth()) adminRoute.Use(middleware.AdminAuth())
{ {
adminRoute.GET("/", controller.GetAllUsers) adminRoute.GET("/", controller.GetAllUsers)
adminRoute.GET("/topup", controller.GetAllTopUps)
adminRoute.POST("/topup/complete", controller.AdminCompleteTopUp)
adminRoute.GET("/search", controller.SearchUsers) adminRoute.GET("/search", controller.SearchUsers)
adminRoute.GET("/:id", controller.GetUser) adminRoute.GET("/:id", controller.GetUser)
adminRoute.POST("/", controller.CreateUser) adminRoute.POST("/", controller.CreateUser)

View File

@@ -63,9 +63,10 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => {
const loadTopups = async (currentPage, currentPageSize) => { const loadTopups = async (currentPage, currentPageSize) => {
setLoading(true); setLoading(true);
try { try {
const res = await API.get( const endpoint = isAdmin()
`/api/user/topup/self?p=${currentPage}&page_size=${currentPageSize}`, ? `/api/user/topup?p=${currentPage}&page_size=${currentPageSize}`
); : `/api/user/topup/self?p=${currentPage}&page_size=${currentPageSize}`;
const res = await API.get(endpoint);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
setTopups(data.items || []); setTopups(data.items || []);