feat: add affiliate admin API
Add admin endpoints for affiliate overview, invites, rebates, transfers, user overrides, batch rate updates, and global rebate config.
This commit is contained in:
parent
6f134b1a83
commit
886d32f255
334
controller/affiliate.go
Normal file
334
controller/affiliate.go
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/QuantumNous/new-api/common"
|
||||||
|
"github.com/QuantumNous/new-api/model"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxAffiliateRatePercent = 100
|
||||||
|
maxAffiliateFrozenDays = 3650
|
||||||
|
maxAffiliateBatchUsers = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
type affiliateUserSettingsRequest struct {
|
||||||
|
AffRebateRatePercent int `json:"aff_rebate_rate_percent"`
|
||||||
|
AffRebateFrozenDays int `json:"aff_rebate_frozen_days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type affiliateBatchRateRequest struct {
|
||||||
|
UserIds []int `json:"user_ids"`
|
||||||
|
AffRebateRatePercent int `json:"aff_rebate_rate_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type affiliateConfigRequest struct {
|
||||||
|
AffRebateRatePercent *int `json:"aff_rebate_rate_percent"`
|
||||||
|
AffRebateFrozenDays *int `json:"aff_rebate_frozen_days"`
|
||||||
|
AffRebateEnabled *bool `json:"aff_rebate_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateOverview(c *gin.Context) {
|
||||||
|
overview, err := model.GetAffiliateOverview()
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ApiSuccess(c, overview)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateRebates(c *gin.Context) {
|
||||||
|
pageInfo := common.GetPageQuery(c)
|
||||||
|
status := strings.TrimSpace(c.Query("status"))
|
||||||
|
if status != "" && status != "frozen" && status != "released" {
|
||||||
|
common.ApiErrorMsg(c, "status 只能是 frozen 或 released")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId := 0
|
||||||
|
if userIdText := strings.TrimSpace(c.Query("user_id")); userIdText != "" {
|
||||||
|
parsed, err := strconv.Atoi(userIdText)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
common.ApiErrorMsg(c, "user_id 参数格式不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userId = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
records, total, err := model.GetRebateRecords(pageInfo.GetPage(), pageInfo.GetPageSize(), status, userId)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageInfo.SetTotal(int(total))
|
||||||
|
pageInfo.SetItems(records)
|
||||||
|
common.ApiSuccess(c, pageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateInvites(c *gin.Context) {
|
||||||
|
pageInfo := common.GetPageQuery(c)
|
||||||
|
userId, ok := parseOptionalPositiveQuery(c, "user_id")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startTimestamp, ok := parseOptionalTimestampQuery(c, "start_timestamp")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endTimestamp, ok := parseOptionalTimestampQuery(c, "end_timestamp")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invites, total, err := model.GetAffiliateInvites(pageInfo.GetPage(), pageInfo.GetPageSize(), userId, startTimestamp, endTimestamp, c.Query("keyword"))
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageInfo.SetTotal(int(total))
|
||||||
|
pageInfo.SetItems(invites)
|
||||||
|
common.ApiSuccess(c, pageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateTransfers(c *gin.Context) {
|
||||||
|
pageInfo := common.GetPageQuery(c)
|
||||||
|
userId, ok := parseOptionalPositiveQuery(c, "user_id")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startTimestamp, ok := parseOptionalTimestampQuery(c, "start_timestamp")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endTimestamp, ok := parseOptionalTimestampQuery(c, "end_timestamp")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := model.GetAffiliateTransferLogs(pageInfo.GetPage(), pageInfo.GetPageSize(), userId, startTimestamp, endTimestamp)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageInfo.SetTotal(int(total))
|
||||||
|
pageInfo.SetItems(logs)
|
||||||
|
common.ApiSuccess(c, pageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateUsers(c *gin.Context) {
|
||||||
|
pageInfo := common.GetPageQuery(c)
|
||||||
|
users, total, err := model.GetAffiliateUsers(pageInfo.GetPage(), pageInfo.GetPageSize(), c.Query("keyword"))
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageInfo.SetTotal(int(total))
|
||||||
|
pageInfo.SetItems(users)
|
||||||
|
common.ApiSuccess(c, pageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetAffiliateUserOverview(c *gin.Context) {
|
||||||
|
userId, ok := getAffiliateUserIdParam(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overview, err := model.GetAffiliateUserOverview(userId)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ApiSuccess(c, overview)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminUpdateAffiliateUserSettings(c *gin.Context) {
|
||||||
|
userId, ok := getAffiliateUserIdParam(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req affiliateUserSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateAffiliateRate(req.AffRebateRatePercent); err != nil {
|
||||||
|
common.ApiErrorMsg(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateAffiliateFrozenDays(req.AffRebateFrozenDays); err != nil {
|
||||||
|
common.ApiErrorMsg(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := model.UpdateUserAffiliateSettings(userId, req.AffRebateRatePercent, req.AffRebateFrozenDays)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ApiSuccess(c, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminBatchUpdateAffiliateRate(c *gin.Context) {
|
||||||
|
var req affiliateBatchRateRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.UserIds) == 0 {
|
||||||
|
common.ApiErrorMsg(c, "user_ids 不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.UserIds) > maxAffiliateBatchUsers {
|
||||||
|
common.ApiErrorMsg(c, "user_ids 一次最多 1000 个")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validateAffiliateRate(req.AffRebateRatePercent); err != nil {
|
||||||
|
common.ApiErrorMsg(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userIds := uniquePositiveUserIds(req.UserIds)
|
||||||
|
if len(userIds) != len(req.UserIds) {
|
||||||
|
common.ApiErrorMsg(c, "user_ids 必须是正整数且不能重复")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := model.BatchUpdateUserAffiliateRate(userIds, req.AffRebateRatePercent)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ApiSuccess(c, gin.H{"affected": affected})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminClearAffiliateUserSettings(c *gin.Context) {
|
||||||
|
userId, ok := getAffiliateUserIdParam(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err := model.ClearUserAffiliateSettings(userId)
|
||||||
|
if err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.ApiSuccess(c, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminUpdateAffiliateConfig(c *gin.Context) {
|
||||||
|
var req affiliateConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.AffRebateRatePercent != nil {
|
||||||
|
if err := validateAffiliateRate(*req.AffRebateRatePercent); err != nil {
|
||||||
|
common.ApiErrorMsg(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.AffRebateFrozenDays != nil {
|
||||||
|
if err := validateAffiliateFrozenDays(*req.AffRebateFrozenDays); err != nil {
|
||||||
|
common.ApiErrorMsg(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.AffRebateRatePercent != nil {
|
||||||
|
if err := model.UpdateOption(model.AffRebateRatePercentKey, strconv.Itoa(*req.AffRebateRatePercent)); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.AffRebateFrozenDays != nil {
|
||||||
|
if err := model.UpdateOption(model.AffRebateFrozenDaysKey, strconv.Itoa(*req.AffRebateFrozenDays)); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.AffRebateEnabled != nil {
|
||||||
|
if err := model.UpdateOption(model.AffRebateEnabledKey, strconv.FormatBool(*req.AffRebateEnabled)); err != nil {
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.ApiSuccess(c, getAffiliateConfigSnapshot())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAffiliateUserIdParam(c *gin.Context) (int, bool) {
|
||||||
|
userId, err := strconv.Atoi(c.Param("user_id"))
|
||||||
|
if err != nil || userId <= 0 {
|
||||||
|
common.ApiErrorMsg(c, "user_id 参数格式不正确")
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return userId, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptionalPositiveQuery(c *gin.Context, key string) (int, bool) {
|
||||||
|
value := strings.TrimSpace(c.Query(key))
|
||||||
|
if value == "" {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
parsed, err := strconv.Atoi(value)
|
||||||
|
if err != nil || parsed <= 0 {
|
||||||
|
common.ApiErrorMsg(c, key+" 参数格式不正确")
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return parsed, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptionalTimestampQuery(c *gin.Context, key string) (int64, bool) {
|
||||||
|
value := strings.TrimSpace(c.Query(key))
|
||||||
|
if value == "" {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil || parsed < 0 {
|
||||||
|
common.ApiErrorMsg(c, key+" 参数格式不正确")
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return parsed, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAffiliateRate(rate int) error {
|
||||||
|
if rate < 0 || rate > maxAffiliateRatePercent {
|
||||||
|
return errors.New("返利比例必须在 0-100 之间")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAffiliateFrozenDays(days int) error {
|
||||||
|
if days < 0 || days > maxAffiliateFrozenDays {
|
||||||
|
return errors.New("冻结天数必须在 0-3650 之间")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniquePositiveUserIds(userIds []int) []int {
|
||||||
|
seen := make(map[int]struct{}, len(userIds))
|
||||||
|
uniqueIds := make([]int, 0, len(userIds))
|
||||||
|
for _, userId := range userIds {
|
||||||
|
if userId <= 0 {
|
||||||
|
return uniqueIds
|
||||||
|
}
|
||||||
|
if _, ok := seen[userId]; ok {
|
||||||
|
return uniqueIds
|
||||||
|
}
|
||||||
|
seen[userId] = struct{}{}
|
||||||
|
uniqueIds = append(uniqueIds, userId)
|
||||||
|
}
|
||||||
|
return uniqueIds
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAffiliateConfigSnapshot() gin.H {
|
||||||
|
common.OptionMapRWMutex.RLock()
|
||||||
|
defer common.OptionMapRWMutex.RUnlock()
|
||||||
|
return gin.H{
|
||||||
|
"aff_rebate_rate_percent": common.OptionMap[model.AffRebateRatePercentKey],
|
||||||
|
"aff_rebate_frozen_days": common.OptionMap[model.AffRebateFrozenDaysKey],
|
||||||
|
"aff_rebate_enabled": common.OptionMap[model.AffRebateEnabledKey],
|
||||||
|
}
|
||||||
|
}
|
||||||
238
controller/affiliate_test.go
Normal file
238
controller/affiliate_test.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/QuantumNous/new-api/common"
|
||||||
|
"github.com/QuantumNous/new-api/model"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type affiliateAPIResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type affiliatePageResponse struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Items json.RawMessage `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAffiliateControllerTestDB(t *testing.T) *gorm.DB {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
previousDB := model.DB
|
||||||
|
previousLogDB := model.LOG_DB
|
||||||
|
common.OptionMapRWMutex.RLock()
|
||||||
|
previousOptionMap := common.OptionMap
|
||||||
|
common.OptionMapRWMutex.RUnlock()
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
common.UsingSQLite = true
|
||||||
|
common.UsingMySQL = false
|
||||||
|
common.UsingPostgreSQL = false
|
||||||
|
common.RedisEnabled = false
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_"))
|
||||||
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, db.AutoMigrate(&model.User{}, &model.RebateRecord{}, &model.Option{}, &model.Log{}))
|
||||||
|
|
||||||
|
model.DB = db
|
||||||
|
model.LOG_DB = db
|
||||||
|
common.OptionMapRWMutex.Lock()
|
||||||
|
common.OptionMap = map[string]string{
|
||||||
|
model.AffRebateRatePercentKey: "0",
|
||||||
|
model.AffRebateFrozenDaysKey: "0",
|
||||||
|
model.AffRebateEnabledKey: "false",
|
||||||
|
}
|
||||||
|
common.OptionMapRWMutex.Unlock()
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
model.DB = previousDB
|
||||||
|
model.LOG_DB = previousLogDB
|
||||||
|
common.OptionMapRWMutex.Lock()
|
||||||
|
common.OptionMap = previousOptionMap
|
||||||
|
common.OptionMapRWMutex.Unlock()
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err == nil {
|
||||||
|
_ = sqlDB.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAffiliateRequest(t *testing.T, handler gin.HandlerFunc, method string, target string, body any, params ...gin.Param) *httptest.ResponseRecorder {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var requestBody *bytes.Reader
|
||||||
|
if body == nil {
|
||||||
|
requestBody = bytes.NewReader(nil)
|
||||||
|
} else {
|
||||||
|
bodyBytes, err := common.Marshal(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requestBody = bytes.NewReader(bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ctx, _ := gin.CreateTestContext(recorder)
|
||||||
|
ctx.Request = httptest.NewRequest(method, target, requestBody)
|
||||||
|
ctx.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
ctx.Params = params
|
||||||
|
handler(ctx)
|
||||||
|
return recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeAffiliateAPIResponse(t *testing.T, recorder *httptest.ResponseRecorder) affiliateAPIResponse {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, recorder.Code)
|
||||||
|
var payload affiliateAPIResponse
|
||||||
|
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &payload))
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUpdateAffiliateConfigRejectsNegativeValues(t *testing.T) {
|
||||||
|
setupAffiliateControllerTestDB(t)
|
||||||
|
|
||||||
|
recorder := performAffiliateRequest(t, AdminUpdateAffiliateConfig, http.MethodPut, "/api/admin/affiliates/config", gin.H{
|
||||||
|
"aff_rebate_rate_percent": -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.False(t, payload.Success)
|
||||||
|
require.Contains(t, payload.Message, "比例")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUpdateAffiliateConfigPersistsOptionMap(t *testing.T) {
|
||||||
|
setupAffiliateControllerTestDB(t)
|
||||||
|
|
||||||
|
recorder := performAffiliateRequest(t, AdminUpdateAffiliateConfig, http.MethodPut, "/api/admin/affiliates/config", gin.H{
|
||||||
|
"aff_rebate_rate_percent": 12,
|
||||||
|
"aff_rebate_frozen_days": 30,
|
||||||
|
"aff_rebate_enabled": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.True(t, payload.Success)
|
||||||
|
common.OptionMapRWMutex.RLock()
|
||||||
|
defer common.OptionMapRWMutex.RUnlock()
|
||||||
|
require.Equal(t, "12", common.OptionMap[model.AffRebateRatePercentKey])
|
||||||
|
require.Equal(t, "30", common.OptionMap[model.AffRebateFrozenDaysKey])
|
||||||
|
require.Equal(t, "true", common.OptionMap[model.AffRebateEnabledKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminBatchUpdateAffiliateRateRejectsTooManyUsers(t *testing.T) {
|
||||||
|
setupAffiliateControllerTestDB(t)
|
||||||
|
|
||||||
|
userIds := make([]int, 1001)
|
||||||
|
for i := range userIds {
|
||||||
|
userIds[i] = i + 1
|
||||||
|
}
|
||||||
|
recorder := performAffiliateRequest(t, AdminBatchUpdateAffiliateRate, http.MethodPost, "/api/admin/affiliates/users/batch-rate", gin.H{
|
||||||
|
"user_ids": userIds,
|
||||||
|
"aff_rebate_rate_percent": 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.False(t, payload.Success)
|
||||||
|
require.Contains(t, payload.Message, "1000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUpdateAffiliateUserSettingsPersistsFields(t *testing.T) {
|
||||||
|
db := setupAffiliateControllerTestDB(t)
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Id: 11,
|
||||||
|
Username: "affiliate_admin_user",
|
||||||
|
Password: "password123",
|
||||||
|
Status: common.UserStatusEnabled,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
recorder := performAffiliateRequest(t, AdminUpdateAffiliateUserSettings, http.MethodPut, "/api/admin/affiliates/users/11", gin.H{
|
||||||
|
"aff_rebate_rate_percent": 25,
|
||||||
|
"aff_rebate_frozen_days": 7,
|
||||||
|
}, gin.Param{Key: "user_id", Value: "11"})
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.True(t, payload.Success)
|
||||||
|
var user model.User
|
||||||
|
require.NoError(t, db.First(&user, 11).Error)
|
||||||
|
require.Equal(t, 25, user.AffRebateRatePercent)
|
||||||
|
require.Equal(t, 7, user.AffRebateFrozenDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminGetAffiliateInvitesReturnsInvitePairs(t *testing.T) {
|
||||||
|
db := setupAffiliateControllerTestDB(t)
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Id: 21,
|
||||||
|
Username: "inviter_user",
|
||||||
|
Password: "password123",
|
||||||
|
Status: common.UserStatusEnabled,
|
||||||
|
AffCode: "aff21",
|
||||||
|
CreatedAt: 100,
|
||||||
|
}).Error)
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Id: 22,
|
||||||
|
Username: "invitee_user",
|
||||||
|
Password: "password123",
|
||||||
|
Status: common.UserStatusEnabled,
|
||||||
|
AffCode: "aff22",
|
||||||
|
InviterId: 21,
|
||||||
|
CreatedAt: 200,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
recorder := performAffiliateRequest(t, AdminGetAffiliateInvites, http.MethodGet, "/api/admin/affiliates/invites?user_id=21", nil)
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.True(t, payload.Success)
|
||||||
|
var page affiliatePageResponse
|
||||||
|
require.NoError(t, common.Unmarshal(payload.Data, &page))
|
||||||
|
require.Equal(t, 1, page.Total)
|
||||||
|
var invites []model.AffiliateInvite
|
||||||
|
require.NoError(t, common.Unmarshal(page.Items, &invites))
|
||||||
|
require.Len(t, invites, 1)
|
||||||
|
require.Equal(t, 22, invites[0].Invitee.Id)
|
||||||
|
require.Equal(t, 21, invites[0].Inviter.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminGetAffiliateTransfersReturnsTransferLogs(t *testing.T) {
|
||||||
|
db := setupAffiliateControllerTestDB(t)
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Id: 31,
|
||||||
|
Username: "transfer_user",
|
||||||
|
Password: "password123",
|
||||||
|
Status: common.UserStatusEnabled,
|
||||||
|
AffCode: "aff31",
|
||||||
|
}).Error)
|
||||||
|
require.NoError(t, db.Create(&model.Log{
|
||||||
|
UserId: 31,
|
||||||
|
Username: "transfer_user",
|
||||||
|
CreatedAt: 300,
|
||||||
|
Type: model.LogTypeSystem,
|
||||||
|
Content: "邀请额度转入余额 $1.00",
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
recorder := performAffiliateRequest(t, AdminGetAffiliateTransfers, http.MethodGet, "/api/admin/affiliates/transfers?user_id=31", nil)
|
||||||
|
|
||||||
|
payload := decodeAffiliateAPIResponse(t, recorder)
|
||||||
|
require.True(t, payload.Success)
|
||||||
|
var page affiliatePageResponse
|
||||||
|
require.NoError(t, common.Unmarshal(payload.Data, &page))
|
||||||
|
require.Equal(t, 1, page.Total)
|
||||||
|
var logs []model.Log
|
||||||
|
require.NoError(t, common.Unmarshal(page.Items, &logs))
|
||||||
|
require.Len(t, logs, 1)
|
||||||
|
require.Equal(t, 31, logs[0].UserId)
|
||||||
|
require.Contains(t, logs[0].Content, "邀请额度转入余额")
|
||||||
|
}
|
||||||
@ -348,6 +348,7 @@ func TransferAffQuota(c *gin.Context) {
|
|||||||
common.ApiErrorI18n(c, i18n.MsgUserTransferFailed, map[string]any{"Error": err.Error()})
|
common.ApiErrorI18n(c, i18n.MsgUserTransferFailed, map[string]any{"Error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
model.RecordLog(id, model.LogTypeSystem, fmt.Sprintf("邀请额度转入余额 %s", logger.LogQuota(tran.Quota)))
|
||||||
common.ApiSuccessI18n(c, i18n.MsgUserTransferSuccess, nil)
|
common.ApiSuccessI18n(c, i18n.MsgUserTransferSuccess, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
model/log.go
63
model/log.go
@ -18,24 +18,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
Id int `json:"id" gorm:"index:idx_created_at_id,priority:1;index:idx_user_id_id,priority:2"`
|
Id int `json:"id" gorm:"index:idx_created_at_id,priority:1;index:idx_user_id_id,priority:2"`
|
||||||
UserId int `json:"user_id" gorm:"index;index:idx_user_id_id,priority:1"`
|
UserId int `json:"user_id" gorm:"index;index:idx_user_id_id,priority:1"`
|
||||||
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
|
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
|
||||||
Type int `json:"type" gorm:"index:idx_created_at_type"`
|
Type int `json:"type" gorm:"index:idx_created_at_type"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Username string `json:"username" gorm:"index;index:index_username_model_name,priority:2;default:''"`
|
Username string `json:"username" gorm:"index;index:index_username_model_name,priority:2;default:''"`
|
||||||
TokenName string `json:"token_name" gorm:"index;default:''"`
|
TokenName string `json:"token_name" gorm:"index;default:''"`
|
||||||
ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
|
ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
|
||||||
Quota int `json:"quota" gorm:"default:0"`
|
Quota int `json:"quota" gorm:"default:0"`
|
||||||
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
|
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
|
||||||
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
|
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
|
||||||
UseTime int `json:"use_time" gorm:"default:0"`
|
UseTime int `json:"use_time" gorm:"default:0"`
|
||||||
IsStream bool `json:"is_stream"`
|
IsStream bool `json:"is_stream"`
|
||||||
ChannelId int `json:"channel" gorm:"index"`
|
ChannelId int `json:"channel" gorm:"index"`
|
||||||
ChannelName string `json:"channel_name" gorm:"->"`
|
ChannelName string `json:"channel_name" gorm:"->"`
|
||||||
TokenId int `json:"token_id" gorm:"default:0;index"`
|
TokenId int `json:"token_id" gorm:"default:0;index"`
|
||||||
Group string `json:"group" gorm:"index"`
|
Group string `json:"group" gorm:"index"`
|
||||||
Ip string `json:"ip" gorm:"index;default:''"`
|
Ip string `json:"ip" gorm:"index;default:''"`
|
||||||
RequestId string `json:"request_id,omitempty" gorm:"type:varchar(64);index:idx_logs_request_id;default:''"`
|
RequestId string `json:"request_id,omitempty" gorm:"type:varchar(64);index:idx_logs_request_id;default:''"`
|
||||||
UpstreamRequestId string `json:"upstream_request_id,omitempty" gorm:"type:varchar(128);index:idx_logs_upstream_request_id;default:''"`
|
UpstreamRequestId string `json:"upstream_request_id,omitempty" gorm:"type:varchar(128);index:idx_logs_upstream_request_id;default:''"`
|
||||||
Other string `json:"other"`
|
Other string `json:"other"`
|
||||||
@ -382,6 +382,33 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName
|
|||||||
return logs, total, err
|
return logs, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAffiliateTransferLogs(page int, pageSize int, userId int, startTimestamp int64, endTimestamp int64) ([]*Log, int64, error) {
|
||||||
|
var logs []*Log
|
||||||
|
var total int64
|
||||||
|
query := LOG_DB.Model(&Log{}).
|
||||||
|
Where("logs.type = ? AND logs.content LIKE ?", LogTypeSystem, "%邀请额度转入余额%")
|
||||||
|
if userId > 0 {
|
||||||
|
query = query.Where("logs.user_id = ?", userId)
|
||||||
|
}
|
||||||
|
if startTimestamp > 0 {
|
||||||
|
query = query.Where("logs.created_at >= ?", startTimestamp)
|
||||||
|
}
|
||||||
|
if endTimestamp > 0 {
|
||||||
|
query = query.Where("logs.created_at <= ?", endTimestamp)
|
||||||
|
}
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
err := query.Order("logs.id desc").Limit(pageSize).Offset((page - 1) * pageSize).Find(&logs).Error
|
||||||
|
return logs, total, err
|
||||||
|
}
|
||||||
|
|
||||||
const logSearchCountLimit = 10000
|
const logSearchCountLimit = 10000
|
||||||
|
|
||||||
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string, requestId string, upstreamRequestId string) (logs []*Log, total int64, err error) {
|
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string, requestId string, upstreamRequestId string) (logs []*Log, total int64, err error) {
|
||||||
|
|||||||
@ -59,3 +59,83 @@ func GetRebateRecords(page int, pageSize int, status string, userId int) ([]Reba
|
|||||||
err := query.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error
|
err := query.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error
|
||||||
return records, total, err
|
return records, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AffiliateOverview struct {
|
||||||
|
TotalInvites int64 `json:"total_invites"`
|
||||||
|
TotalRebates int64 `json:"total_rebates"`
|
||||||
|
TotalRebateAmount int64 `json:"total_rebate_amount"`
|
||||||
|
FrozenRebateAmount int64 `json:"frozen_rebate_amount"`
|
||||||
|
ReleasedRebateAmount int64 `json:"released_rebate_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAffiliateOverview() (AffiliateOverview, error) {
|
||||||
|
var overview AffiliateOverview
|
||||||
|
if err := DB.Model(&User{}).Where("inviter_id > 0").Count(&overview.TotalInvites).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).Count(&overview.TotalRebates).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.TotalRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Where("status = ?", "frozen").
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.FrozenRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Where("status = ?", "released").
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.ReleasedRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
return overview, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AffiliateUserOverview struct {
|
||||||
|
User *User `json:"user"`
|
||||||
|
InvitedUsers int64 `json:"invited_users"`
|
||||||
|
TotalRebates int64 `json:"total_rebates"`
|
||||||
|
TotalRebateAmount int64 `json:"total_rebate_amount"`
|
||||||
|
FrozenRebateAmount int64 `json:"frozen_rebate_amount"`
|
||||||
|
ReleasedRebateAmount int64 `json:"released_rebate_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAffiliateUserOverview(userId int) (AffiliateUserOverview, error) {
|
||||||
|
var overview AffiliateUserOverview
|
||||||
|
user, err := GetUserById(userId, false)
|
||||||
|
if err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
overview.User = user
|
||||||
|
if err := DB.Model(&User{}).Where("inviter_id = ?", userId).Count(&overview.InvitedUsers).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
baseQuery := DB.Model(&RebateRecord{}).Where("inviter_id = ?", userId)
|
||||||
|
if err := baseQuery.Count(&overview.TotalRebates).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Where("inviter_id = ?", userId).
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.TotalRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Where("inviter_id = ? AND status = ?", userId, "frozen").
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.FrozenRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
if err := DB.Model(&RebateRecord{}).
|
||||||
|
Where("inviter_id = ? AND status = ?", userId, "released").
|
||||||
|
Select("COALESCE(SUM(rebate_amount), 0)").
|
||||||
|
Scan(&overview.ReleasedRebateAmount).Error; err != nil {
|
||||||
|
return overview, err
|
||||||
|
}
|
||||||
|
return overview, nil
|
||||||
|
}
|
||||||
|
|||||||
125
model/user.go
125
model/user.go
@ -294,6 +294,131 @@ func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User,
|
|||||||
return users, total, nil
|
return users, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAffiliateUsers(page int, pageSize int, keyword string) ([]*User, int64, error) {
|
||||||
|
var users []*User
|
||||||
|
var total int64
|
||||||
|
query := DB.Model(&User{})
|
||||||
|
keyword = strings.TrimSpace(keyword)
|
||||||
|
if keyword != "" {
|
||||||
|
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
||||||
|
if keywordInt, err := strconv.Atoi(keyword); err == nil {
|
||||||
|
query = query.Where("id = ? OR "+likeCondition, keywordInt, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
} else {
|
||||||
|
query = query.Where(likeCondition, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
err := query.Omit("password").Order("id desc").Limit(pageSize).Offset((page - 1) * pageSize).Find(&users).Error
|
||||||
|
return users, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type AffiliateInvite struct {
|
||||||
|
Invitee *User `json:"invitee"`
|
||||||
|
Inviter *User `json:"inviter"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAffiliateInvites(page int, pageSize int, userId int, startTimestamp int64, endTimestamp int64, keyword string) ([]AffiliateInvite, int64, error) {
|
||||||
|
var invitees []*User
|
||||||
|
var total int64
|
||||||
|
query := DB.Model(&User{}).Where("inviter_id > 0")
|
||||||
|
if userId > 0 {
|
||||||
|
query = query.Where("id = ? OR inviter_id = ?", userId, userId)
|
||||||
|
}
|
||||||
|
if startTimestamp > 0 {
|
||||||
|
query = query.Where("created_at >= ?", startTimestamp)
|
||||||
|
}
|
||||||
|
if endTimestamp > 0 {
|
||||||
|
query = query.Where("created_at <= ?", endTimestamp)
|
||||||
|
}
|
||||||
|
keyword = strings.TrimSpace(keyword)
|
||||||
|
if keyword != "" {
|
||||||
|
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
||||||
|
if keywordInt, err := strconv.Atoi(keyword); err == nil {
|
||||||
|
query = query.Where("id = ? OR inviter_id = ? OR "+likeCondition, keywordInt, keywordInt, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
} else {
|
||||||
|
query = query.Where(likeCondition, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
if err := query.Omit("password").Order("created_at desc, id desc").Limit(pageSize).Offset((page - 1) * pageSize).Find(&invitees).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inviterIds := make([]int, 0, len(invitees))
|
||||||
|
seen := make(map[int]struct{}, len(invitees))
|
||||||
|
for _, invitee := range invitees {
|
||||||
|
if invitee.InviterId <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[invitee.InviterId]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[invitee.InviterId] = struct{}{}
|
||||||
|
inviterIds = append(inviterIds, invitee.InviterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
inviterMap := make(map[int]*User, len(inviterIds))
|
||||||
|
if len(inviterIds) > 0 {
|
||||||
|
var inviters []*User
|
||||||
|
if err := DB.Model(&User{}).Omit("password").Where("id IN ?", inviterIds).Find(&inviters).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
for _, inviter := range inviters {
|
||||||
|
inviterMap[inviter.Id] = inviter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invites := make([]AffiliateInvite, 0, len(invitees))
|
||||||
|
for _, invitee := range invitees {
|
||||||
|
invites = append(invites, AffiliateInvite{
|
||||||
|
Invitee: invitee,
|
||||||
|
Inviter: inviterMap[invitee.InviterId],
|
||||||
|
CreatedAt: invitee.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return invites, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUserAffiliateSettings(userId int, ratePercent int, frozenDays int) (*User, error) {
|
||||||
|
if _, err := GetUserById(userId, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := DB.Model(&User{}).Where("id = ?", userId).Updates(map[string]interface{}{
|
||||||
|
"aff_rebate_rate_percent": ratePercent,
|
||||||
|
"aff_rebate_frozen_days": frozenDays,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return GetUserById(userId, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BatchUpdateUserAffiliateRate(userIds []int, ratePercent int) (int64, error) {
|
||||||
|
result := DB.Model(&User{}).Where("id IN ?", userIds).Update("aff_rebate_rate_percent", ratePercent)
|
||||||
|
return result.RowsAffected, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearUserAffiliateSettings(userId int) (*User, error) {
|
||||||
|
return UpdateUserAffiliateSettings(userId, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserById(id int, selectAll bool) (*User, error) {
|
func GetUserById(id int, selectAll bool) (*User, error) {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
return nil, errors.New("id 为空!")
|
return nil, errors.New("id 为空!")
|
||||||
|
|||||||
@ -315,6 +315,20 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
announcementAdminRoute.PUT("/:id", controller.UpdateAnnouncement)
|
announcementAdminRoute.PUT("/:id", controller.UpdateAnnouncement)
|
||||||
announcementAdminRoute.DELETE("/:id", controller.DeleteAnnouncement)
|
announcementAdminRoute.DELETE("/:id", controller.DeleteAnnouncement)
|
||||||
}
|
}
|
||||||
|
affiliateAdminRoute := apiRouter.Group("/admin/affiliates")
|
||||||
|
affiliateAdminRoute.Use(middleware.AdminAuth())
|
||||||
|
{
|
||||||
|
affiliateAdminRoute.GET("/overview", controller.AdminGetAffiliateOverview)
|
||||||
|
affiliateAdminRoute.GET("/invites", controller.AdminGetAffiliateInvites)
|
||||||
|
affiliateAdminRoute.GET("/rebates", controller.AdminGetAffiliateRebates)
|
||||||
|
affiliateAdminRoute.GET("/transfers", controller.AdminGetAffiliateTransfers)
|
||||||
|
affiliateAdminRoute.GET("/users", controller.AdminGetAffiliateUsers)
|
||||||
|
affiliateAdminRoute.GET("/users/:user_id/overview", controller.AdminGetAffiliateUserOverview)
|
||||||
|
affiliateAdminRoute.PUT("/users/:user_id", controller.AdminUpdateAffiliateUserSettings)
|
||||||
|
affiliateAdminRoute.POST("/users/batch-rate", controller.AdminBatchUpdateAffiliateRate)
|
||||||
|
affiliateAdminRoute.DELETE("/users/:user_id", controller.AdminClearAffiliateUserSettings)
|
||||||
|
affiliateAdminRoute.PUT("/config", controller.AdminUpdateAffiliateConfig)
|
||||||
|
}
|
||||||
channelMonitorAdminRoute := apiRouter.Group("/admin/channel-monitors")
|
channelMonitorAdminRoute := apiRouter.Group("/admin/channel-monitors")
|
||||||
channelMonitorAdminRoute.Use(middleware.AdminAuth())
|
channelMonitorAdminRoute.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user