Merge pull request #557 from james-6-23/main
feat(admin): 为账户和兑换码新增邮箱搜索及限流过滤功能
This commit is contained in:
@@ -202,7 +202,7 @@ func (h *RedeemHandler) Export(c *gin.Context) {
|
|||||||
writer := csv.NewWriter(&buf)
|
writer := csv.NewWriter(&buf)
|
||||||
|
|
||||||
// Write header
|
// Write header
|
||||||
if err := writer.Write([]string{"id", "code", "type", "value", "status", "used_by", "used_at", "created_at"}); err != nil {
|
if err := writer.Write([]string{"id", "code", "type", "value", "status", "used_by", "used_by_email", "used_at", "created_at"}); err != nil {
|
||||||
response.InternalError(c, "Failed to export redeem codes: "+err.Error())
|
response.InternalError(c, "Failed to export redeem codes: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -213,6 +213,10 @@ func (h *RedeemHandler) Export(c *gin.Context) {
|
|||||||
if code.UsedBy != nil {
|
if code.UsedBy != nil {
|
||||||
usedBy = fmt.Sprintf("%d", *code.UsedBy)
|
usedBy = fmt.Sprintf("%d", *code.UsedBy)
|
||||||
}
|
}
|
||||||
|
usedByEmail := ""
|
||||||
|
if code.User != nil {
|
||||||
|
usedByEmail = code.User.Email
|
||||||
|
}
|
||||||
usedAt := ""
|
usedAt := ""
|
||||||
if code.UsedAt != nil {
|
if code.UsedAt != nil {
|
||||||
usedAt = code.UsedAt.Format("2006-01-02 15:04:05")
|
usedAt = code.UsedAt.Format("2006-01-02 15:04:05")
|
||||||
@@ -224,6 +228,7 @@ func (h *RedeemHandler) Export(c *gin.Context) {
|
|||||||
fmt.Sprintf("%.2f", code.Value),
|
fmt.Sprintf("%.2f", code.Value),
|
||||||
code.Status,
|
code.Status,
|
||||||
usedBy,
|
usedBy,
|
||||||
|
usedByEmail,
|
||||||
usedAt,
|
usedAt,
|
||||||
code.CreatedAt.Format("2006-01-02 15:04:05"),
|
code.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -448,7 +448,12 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
|
|||||||
q = q.Where(dbaccount.TypeEQ(accountType))
|
q = q.Where(dbaccount.TypeEQ(accountType))
|
||||||
}
|
}
|
||||||
if status != "" {
|
if status != "" {
|
||||||
q = q.Where(dbaccount.StatusEQ(status))
|
switch status {
|
||||||
|
case "rate_limited":
|
||||||
|
q = q.Where(dbaccount.RateLimitResetAtGT(time.Now()))
|
||||||
|
default:
|
||||||
|
q = q.Where(dbaccount.StatusEQ(status))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if search != "" {
|
if search != "" {
|
||||||
q = q.Where(dbaccount.NameContainsFold(search))
|
q = q.Where(dbaccount.NameContainsFold(search))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
)
|
)
|
||||||
@@ -106,7 +107,12 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin
|
|||||||
q = q.Where(redeemcode.StatusEQ(status))
|
q = q.Where(redeemcode.StatusEQ(status))
|
||||||
}
|
}
|
||||||
if search != "" {
|
if search != "" {
|
||||||
q = q.Where(redeemcode.CodeContainsFold(search))
|
q = q.Where(
|
||||||
|
redeemcode.Or(
|
||||||
|
redeemcode.CodeContainsFold(search),
|
||||||
|
redeemcode.HasUserWith(user.EmailContainsFold(search)),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err := q.Count(ctx)
|
total, err := q.Count(ctx)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
dbuser "github.com/Wei-Shaw/sub2api/ent/user"
|
dbuser "github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
|
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
|
||||||
@@ -191,6 +192,7 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination.
|
|||||||
dbuser.EmailContainsFold(filters.Search),
|
dbuser.EmailContainsFold(filters.Search),
|
||||||
dbuser.UsernameContainsFold(filters.Search),
|
dbuser.UsernameContainsFold(filters.Search),
|
||||||
dbuser.NotesContainsFold(filters.Search),
|
dbuser.NotesContainsFold(filters.Search),
|
||||||
|
dbuser.HasAPIKeysWith(apikey.KeyContainsFold(filters.Search)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ const updateType = (value: string | number | boolean | null) => { emit('update:f
|
|||||||
const updateStatus = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, status: value }) }
|
const updateStatus = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, status: value }) }
|
||||||
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }])
|
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }])
|
||||||
const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }])
|
const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }])
|
||||||
const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }])
|
const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }])
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ export default {
|
|||||||
createUser: 'Create User',
|
createUser: 'Create User',
|
||||||
editUser: 'Edit User',
|
editUser: 'Edit User',
|
||||||
deleteUser: 'Delete User',
|
deleteUser: 'Delete User',
|
||||||
searchUsers: 'Search users...',
|
searchUsers: 'Search by email, username, notes, or API key...',
|
||||||
allRoles: 'All Roles',
|
allRoles: 'All Roles',
|
||||||
allStatus: 'All Status',
|
allStatus: 'All Status',
|
||||||
admin: 'Admin',
|
admin: 'Admin',
|
||||||
@@ -2136,7 +2136,7 @@ export default {
|
|||||||
title: 'Redeem Code Management',
|
title: 'Redeem Code Management',
|
||||||
description: 'Generate and manage redeem codes',
|
description: 'Generate and manage redeem codes',
|
||||||
generateCodes: 'Generate Codes',
|
generateCodes: 'Generate Codes',
|
||||||
searchCodes: 'Search codes...',
|
searchCodes: 'Search codes or email...',
|
||||||
allTypes: 'All Types',
|
allTypes: 'All Types',
|
||||||
allStatus: 'All Status',
|
allStatus: 'All Status',
|
||||||
balance: 'Balance',
|
balance: 'Balance',
|
||||||
|
|||||||
@@ -865,8 +865,8 @@ export default {
|
|||||||
editUser: '编辑用户',
|
editUser: '编辑用户',
|
||||||
deleteUser: '删除用户',
|
deleteUser: '删除用户',
|
||||||
deleteConfirmMessage: "确定要删除用户 '{email}' 吗?此操作无法撤销。",
|
deleteConfirmMessage: "确定要删除用户 '{email}' 吗?此操作无法撤销。",
|
||||||
searchPlaceholder: '搜索用户邮箱或用户名、备注、支持模糊查询...',
|
searchPlaceholder: '邮箱/用户名/备注/API Key 模糊搜索...',
|
||||||
searchUsers: '搜索用户邮箱或用户名、备注、支持模糊查询',
|
searchUsers: '邮箱/用户名/备注/API Key 模糊搜索',
|
||||||
roleFilter: '角色筛选',
|
roleFilter: '角色筛选',
|
||||||
allRoles: '全部角色',
|
allRoles: '全部角色',
|
||||||
allStatus: '全部状态',
|
allStatus: '全部状态',
|
||||||
@@ -2300,7 +2300,7 @@ export default {
|
|||||||
allStatus: '全部状态',
|
allStatus: '全部状态',
|
||||||
unused: '未使用',
|
unused: '未使用',
|
||||||
used: '已使用',
|
used: '已使用',
|
||||||
searchCodes: '搜索兑换码...',
|
searchCodes: '搜索兑换码或邮箱...',
|
||||||
exportCsv: '导出 CSV',
|
exportCsv: '导出 CSV',
|
||||||
deleteAllUnused: '删除全部未使用',
|
deleteAllUnused: '删除全部未使用',
|
||||||
deleteCodeConfirm: '确定要删除此兑换码吗?此操作无法撤销。',
|
deleteCodeConfirm: '确定要删除此兑换码吗?此操作无法撤销。',
|
||||||
|
|||||||
@@ -117,9 +117,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell-used_by="{ value }">
|
<template #cell-used_by="{ value, row }">
|
||||||
<span class="text-sm text-gray-500 dark:text-dark-400">
|
<span class="text-sm text-gray-500 dark:text-dark-400">
|
||||||
{{ value ? t('admin.redeem.userPrefix', { id: value }) : '-' }}
|
{{ row.user?.email || (value ? t('admin.redeem.userPrefix', { id: value }) : '-') }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user