Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR updates channel testing, cache consistency, quota accounting, OAuth error handling, request validation, billing settlement, responsive UI behavior, and frontend security-related flows across backend and web components. ChangesChannel testing and frontend interaction updates
Backend consistency and settlement
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx`:
- Line 232: Add an explicit return type annotation to the getModelRowSignature
function, using the actual type of the value it returns and preserving its
current behavior.
- Around line 557-576: Replace the nested ternary used for billingMode in the
setEditData block with an explicitly typed variable using the exact allowed
billing-mode literals, and assign it through straightforward if-else logic:
tiered_expr for that billing mode, per-request when a price is present,
otherwise per-token. Use the variable in the existing setEditData call and leave
the surrounding editableModel mapping unchanged.
In `@web/default/src/i18n/locales/ru.json`:
- Line 67: Update the "{{target}} test failed" translation in the Russian locale
to explicitly indicate test failure, using wording such as "Тест {{target}} не
пройден" or "Тест {{target}} завершился с ошибкой" instead of language meaning
not performed or completed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: cb798633-29c4-418b-aeaf-6d7842260e8e
📒 Files selected for processing (12)
web/default/src/features/channels/components/data-table-row-actions.tsxweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsxweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/channels/types.tsweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.json
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Backend checks
🧰 Additional context used
📓 Path-based instructions (4)
web/default/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (web/default/AGENTS.md)
web/default/src/**/*.{ts,tsx}: 前端页面文本与组件内文案必须支持 i18n:React 组件中应使用useTranslation()取得t,并通过t()渲染用户可见文本;子组件也应自行使用useTranslation()保持独立性。
禁止使用两层及以上嵌套三元表达式;复杂逻辑应改用if-else、提前返回或抽取函数。
控制函数圈复杂度,复杂逻辑应拆成更小的函数;变量与函数命名应有意义并遵循驼峰等常规命名约定。
TypeScript 代码应避免使用any,优先使用具体类型或unknown;参数与返回值应显式标注类型;仅类型用途的导入应使用import type。
修改 TypeScript 或 TSX 代码后必须执行类型检查,并修复所有类型错误,不得遗留。
对象非必要不要解构,尤其是组件 props;优先直接使用props.xxx以保持代码清晰。
组件应使用函数式组件与 Hooks,遵循单一职责;组件 props 必须有明确类型(接口或类型别名)。
单文件超过约 200 行时应考虑拆分子组件或抽取自定义 Hooks;类型定义可与组件同文件或放在同模块的types中。
在 React 中应合理使用useMemo、useCallback、React.memo,避免在渲染路径中创建新对象或数组;必要时进行代码分割与动态import。
React Query 的数据获取应使用useQuery、变更应使用useMutation;每个查询需配置唯一queryKey,并在成功后对相关查询执行invalidateQueries;服务端错误应统一交给handleServerError。
Axios 请求应使用项目统一的api实例;GET 请求默认去重,特殊请求可显式关闭;认证与通用错误应在拦截器中统一处理。
服务端错误应统一使用handleServerError,展示层应使用toast.error等统一方式;文案需走 i18n;路由级错误应由errorComponent承接;表单错误应通过form.setError等方式映射到字段。
样式应以 Tailwind 工具类为主,动态类名使用cn()合并;非动态场景避免内联样式;响应式采用移动优先与 Tailwind 断点,主题与暗色模式通过 CSS 变量与dark:处理。
应使用语义化 HTML、正确关联label与输入、保证键盘可操作与合理焦点顺序;必要时添加 ARIA 属性,装饰性图标应使用aria-hidden="true"。
认证与权限应在路由与接口层校验;前后端都应做数据校验(如 Zod);敏感信息不得落前端存储;避免使用dangerouslySetInnerHTML;跨域与 Cookie 需配合withCredentials并按后端要求处理 CSRF。
组件测试应使用 React Testing Library,关注交互与行为,避免测试实现细节;关键流程可补充集成与 E2E 测试。
环境变量应通过.env读取,并使用VITE_前缀;代码中不得硬编码密钥。
Files:
web/default/src/features/channels/types.tsweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/channels/components/data-table-row-actions.tsxweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
web/default/src/features/**
📄 CodeRabbit inference engine (web/default/AGENTS.md)
功能模块应放在
src/features/<feature>/,并按需包含components/、lib/、hooks/、api.ts、types.ts、constants.ts等;通用组件应放在src/components/,通用工具与类型应放在src/lib/。
Files:
web/default/src/features/channels/types.tsweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/channels/components/data-table-row-actions.tsxweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
web/default/src/features/**/lib/**/*.ts
📄 CodeRabbit inference engine (web/default/AGENTS.md)
表单应使用 React Hook Form + Zod:在功能模块的
lib/下定义 schema,并用z.infer导出表单类型;useForm应配合@hookform/resolvers/zod进行校验。
Files:
web/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/channels/lib/channel-actions.ts
web/default/src/**/*.test.ts
📄 CodeRabbit inference engine (web/default/AGENTS.md)
工具函数与纯逻辑应优先编写单元测试;测试文件应命名为
*.test.ts。
Files:
web/default/src/features/channels/lib/channel-actions.test.ts
🔇 Additional comments (12)
web/default/src/features/channels/lib/channel-actions.ts (2)
60-183: LGTM!
342-411: LGTM!web/default/src/features/channels/types.ts (1)
191-191: LGTM!web/default/src/features/channels/components/data-table-row-actions.tsx (1)
56-62: LGTM!Also applies to: 94-112
web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx (1)
348-418: LGTM!Also applies to: 438-506
web/default/src/i18n/locales/en.json (1)
67-68: LGTM!Also applies to: 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/i18n/locales/fr.json (1)
67-68: LGTM!Also applies to: 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/features/channels/lib/channel-actions.test.ts (1)
19-25: 📐 Maintainability & Code QualityWire
node:testinto the workspace test command
I couldn’t find atestscript or workflow that runs these suites, so thesenode:testfiles may not execute automatically. Add a test command/CI job for them.web/default/src/i18n/locales/ja.json (1)
14-15: LGTM!Also applies to: 67-68, 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/i18n/locales/ru.json (1)
68-68: LGTM!Also applies to: 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/i18n/locales/vi.json (1)
14-15: LGTM!Also applies to: 67-68, 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/i18n/locales/zh.json (1)
14-15: LGTM!Also applies to: 67-68, 84-86, 801-802, 2701-2701, 3895-3895
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx (1)
361-408: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winIgnore results from tests belonging to a closed or replaced dialog.
Closing the dialog only resets state; active requests continue and can update
testResults, progress, and selection after another channel is opened. Add an operation-generation guard/abort signal, or prevent closing until testing finishes.Also applies to: 421-514
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx` around lines 361 - 408, Update the dialog test lifecycle around testSingleModel and the related test handlers to invalidate or abort in-flight operations when the dialog closes or the tested channel is replaced. Guard all asynchronous callbacks and completion updates, including testResults, progress, selection, and refreshChannelLists, so results from stale generations are ignored while current-dialog tests continue to work.setting/config/config.go (1)
203-269: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winConfiguration updates are not atomic. A later validation error leaves earlier fields modified despite
LoadFromDBreturning failure.
setting/config/config.go#L203-L269: apply changes to a copied struct and publish it only after all fields validate.setting/config/config_test.go#L101-L113: add a valid earlier field plus an invalid later field and assert the whole config remains unchanged.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@setting/config/config.go` around lines 203 - 269, Make LoadFromDB apply all decoded field updates to a copy of the configuration, using the existing reflection switch for validation, and publish the copied struct only after every field succeeds; leave the original unchanged on any error. In setting/config/config_test.go:101-113, add a valid earlier update followed by an invalid later value and assert that LoadFromDB fails and the entire configuration, including the earlier field, remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@common/redis.go`:
- Around line 256-265: Add lifecycle cleanup for persistent version keys used by
RedisInvalidateVersionedHash, deleting the corresponding versionKey whenever the
associated user or token entity is removed. Do not apply a normal TTL to
versionKey, since generation resets could permit stale cache refills, and ensure
the cleanup path targets both the cached hash key and its version key.
In `@model/checkin.go`:
- Around line 116-117: Update the post-transaction cache invalidation call in
the check-in flow to handle failures from invalidateUserQuotaCache(userId)
instead of discarding them. After the database commit, log the invalidation
error and enqueue a retry, without returning the error to the caller or
triggering another check-in attempt.
In `@model/token_cache.go`:
- Around line 31-32: Update RedisInvalidateVersionedHash, used by
invalidateTokenCache, to assign an expiration to each incremented cache-version
key; choose a TTL that exceeds the asynchronous refill/cache-fence window and
apply it consistently in the shared helper so invalidated token version keys can
expire.
In `@model/usedata.go`:
- Around line 117-128: The quota snapshot retry flow around saveQuotaData must
persist a stable idempotency identifier and transactionally deduplicate
snapshots before applying counters, preventing ambiguous commits from
incrementing totals twice; add the corresponding cross-database constraint and
migration compatible with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+. In
model/usedata.go lines 117-128, update the failed-snapshot requeue and save path
to retain and reuse this identifier. In model/usedata_test.go lines 12-48, add
coverage for an ambiguous commit followed by a repeated snapshot and assert
counters are applied exactly once.
In `@model/user_cache.go`:
- Around line 71-82: Update the direct HSET mutation paths for user group,
status, role, name, and setting caches to participate in the same version fence
as populateUserCacheIfVersion. Reuse the existing versioned cache helper and
appropriate user cache version key, or explicitly advance that generation on
every mutation, ensuring stale DB refills cannot overwrite newer auth-sensitive
updates.
In `@model/user.go`:
- Line 1274: Update the OIDC identity existence check in the surrounding user
lookup to return true when RowsAffected is greater than zero, so any matching
record is treated as already taken rather than requiring exactly one row.
In `@service/billing_session.go`:
- Around line 92-107: Update the compensation handling in the billing settlement
flow so a partial result from funding.Settle records the residual committed
funding amount instead of retaining the original appliedFundingDelta. Before a
later token-only retry, reconcile or reapply that missing funding portion before
settling the token, while preserving compensationFailed state and existing
full-compensation behavior. Add a test covering a nonzero partial compensation
result and verifying the retry uses the corrected funding amount.
In `@service/pre_consume_quota.go`:
- Around line 76-79: Update the rollback handling around IncreaseTokenQuota in
the pre-consume failure path so a failed compensation is not only logged: make
the token deduction and quota restoration transactional, or persist a durable
retryable compensation record containing the token and amount details. Ensure
the failed request cannot leave the user’s token balance permanently reduced
without a recovery path.
---
Outside diff comments:
In `@setting/config/config.go`:
- Around line 203-269: Make LoadFromDB apply all decoded field updates to a copy
of the configuration, using the existing reflection switch for validation, and
publish the copied struct only after every field succeeds; leave the original
unchanged on any error. In setting/config/config_test.go:101-113, add a valid
earlier update followed by an invalid later value and assert that LoadFromDB
fails and the entire configuration, including the earlier field, remains
unchanged.
In
`@web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx`:
- Around line 361-408: Update the dialog test lifecycle around testSingleModel
and the related test handlers to invalidate or abort in-flight operations when
the dialog closes or the tested channel is replaced. Guard all asynchronous
callbacks and completion updates, including testResults, progress, selection,
and refreshChannelLists, so results from stale generations are ignored while
current-dialog tests continue to work.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 9bbc065d-de7f-420b-b5ea-3e15b313d12d
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.sumweb/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (77)
common/redis.gocontroller/discord.gocontroller/github.gocontroller/linuxdo.gocontroller/oauth.gocontroller/oauth_test.gocontroller/oidc.gocontroller/telegram.gocontroller/token.gocontroller/wechat.gogo.modmiddleware/turnstile-check.gomiddleware/turnstile_check_test.gomodel/checkin.gomodel/errors.gomodel/main.gomodel/main_migration_test.gomodel/redemption.gomodel/redemption_search_test.gomodel/subscription.gomodel/token.gomodel/token_cache.gomodel/usedata.gomodel/usedata_test.gomodel/user.gomodel/user_cache.gomodel/user_oauth_binding.gomodel/user_update_test.gomodel/utils.gopkg/billingexpr/compile.gopkg/billingexpr/expr.mdpkg/billingexpr/run.gopkg/billingexpr/security_cache_test.goservice/billing_session.goservice/billing_session_test.goservice/funding_source.goservice/pre_consume_quota.goservice/quota.goservice/task_billing_test.gosetting/config/config.gosetting/config/config_test.gotools/jsonwrapcheck/allowlist.txtweb/default/package.jsonweb/default/src/components/layout/components/chat-presets-item.tsxweb/default/src/components/layout/components/footer.tsxweb/default/src/features/auth/api.tsweb/default/src/features/auth/lib/turnstile-request.test.tsweb/default/src/features/auth/lib/turnstile-request.tsweb/default/src/features/channels/components/channels-columns.tsxweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsxweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/keys/components/data-table-row-actions.tsxweb/default/src/features/keys/components/dialogs/cc-switch-dialog.tsxweb/default/src/features/models/components/dialogs/view-details-dialog.tsxweb/default/src/features/playground/components/message-error.tsxweb/default/src/features/playground/hooks/use-stream-request.test.tsweb/default/src/features/playground/hooks/use-stream-request.tsweb/default/src/features/pricing/lib/tier-expr.test.tsweb/default/src/features/pricing/lib/tier-expr.tsweb/default/src/features/profile/api.tsweb/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsxweb/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsxweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/system-settings/models/tiered-pricing-support.tsxweb/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsxweb/default/src/features/wallet/hooks/use-creem-payment.tsweb/default/src/features/wallet/hooks/use-payment.tsweb/default/src/features/wallet/hooks/use-waffo-payment.tsweb/default/src/i18n/locales/ru.jsonweb/default/src/lib/handle-server-error.test.tsweb/default/src/lib/handle-server-error.tsweb/default/src/lib/oauth.tsweb/default/src/lib/safe-redirect.test.tsweb/default/src/lib/safe-redirect.tsweb/default/src/routes/__root.tsxweb/default/src/routes/oauth/$provider.tsx
💤 Files with no reviewable changes (1)
- tools/jsonwrapcheck/allowlist.txt
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Backend checks
🧰 Additional context used
📓 Path-based instructions (8)
web/default/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (web/default/AGENTS.md)
web/default/src/**/*.{ts,tsx}: 前端页面文本与组件内文案必须支持 i18n:React 组件中应使用useTranslation()取得t,并通过t()渲染用户可见文本;子组件也应自行使用useTranslation()保持独立性。
禁止使用两层及以上嵌套三元表达式;复杂逻辑应改用if-else、提前返回或抽取函数。
控制函数圈复杂度,复杂逻辑应拆成更小的函数;变量与函数命名应有意义并遵循驼峰等常规命名约定。
TypeScript 代码应避免使用any,优先使用具体类型或unknown;参数与返回值应显式标注类型;仅类型用途的导入应使用import type。
修改 TypeScript 或 TSX 代码后必须执行类型检查,并修复所有类型错误,不得遗留。
对象非必要不要解构,尤其是组件 props;优先直接使用props.xxx以保持代码清晰。
组件应使用函数式组件与 Hooks,遵循单一职责;组件 props 必须有明确类型(接口或类型别名)。
单文件超过约 200 行时应考虑拆分子组件或抽取自定义 Hooks;类型定义可与组件同文件或放在同模块的types中。
在 React 中应合理使用useMemo、useCallback、React.memo,避免在渲染路径中创建新对象或数组;必要时进行代码分割与动态import。
React Query 的数据获取应使用useQuery、变更应使用useMutation;每个查询需配置唯一queryKey,并在成功后对相关查询执行invalidateQueries;服务端错误应统一交给handleServerError。
Axios 请求应使用项目统一的api实例;GET 请求默认去重,特殊请求可显式关闭;认证与通用错误应在拦截器中统一处理。
服务端错误应统一使用handleServerError,展示层应使用toast.error等统一方式;文案需走 i18n;路由级错误应由errorComponent承接;表单错误应通过form.setError等方式映射到字段。
样式应以 Tailwind 工具类为主,动态类名使用cn()合并;非动态场景避免内联样式;响应式采用移动优先与 Tailwind 断点,主题与暗色模式通过 CSS 变量与dark:处理。
应使用语义化 HTML、正确关联label与输入、保证键盘可操作与合理焦点顺序;必要时添加 ARIA 属性,装饰性图标应使用aria-hidden="true"。
认证与权限应在路由与接口层校验;前后端都应做数据校验(如 Zod);敏感信息不得落前端存储;避免使用dangerouslySetInnerHTML;跨域与 Cookie 需配合withCredentials并按后端要求处理 CSRF。
组件测试应使用 React Testing Library,关注交互与行为,避免测试实现细节;关键流程可补充集成与 E2E 测试。
环境变量应通过.env读取,并使用VITE_前缀;代码中不得硬编码密钥。
Files:
web/default/src/features/wallet/hooks/use-waffo-payment.tsweb/default/src/components/layout/components/chat-presets-item.tsxweb/default/src/features/keys/components/dialogs/cc-switch-dialog.tsxweb/default/src/features/playground/hooks/use-stream-request.test.tsweb/default/src/features/wallet/hooks/use-creem-payment.tsweb/default/src/lib/safe-redirect.test.tsweb/default/src/features/playground/components/message-error.tsxweb/default/src/features/auth/lib/turnstile-request.test.tsweb/default/src/features/keys/components/data-table-row-actions.tsxweb/default/src/lib/handle-server-error.test.tsweb/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsxweb/default/src/features/pricing/lib/tier-expr.test.tsweb/default/src/features/auth/lib/turnstile-request.tsweb/default/src/lib/safe-redirect.tsweb/default/src/features/channels/components/channels-columns.tsxweb/default/src/features/models/components/dialogs/view-details-dialog.tsxweb/default/src/features/wallet/hooks/use-payment.tsweb/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsxweb/default/src/features/system-settings/models/tiered-pricing-support.tsxweb/default/src/components/layout/components/footer.tsxweb/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsxweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/profile/api.tsweb/default/src/lib/handle-server-error.tsweb/default/src/features/auth/api.tsweb/default/src/lib/oauth.tsweb/default/src/routes/oauth/$provider.tsxweb/default/src/routes/__root.tsxweb/default/src/features/playground/hooks/use-stream-request.tsweb/default/src/features/pricing/lib/tier-expr.tsweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
web/default/src/features/**
📄 CodeRabbit inference engine (web/default/AGENTS.md)
功能模块应放在
src/features/<feature>/,并按需包含components/、lib/、hooks/、api.ts、types.ts、constants.ts等;通用组件应放在src/components/,通用工具与类型应放在src/lib/。
Files:
web/default/src/features/wallet/hooks/use-waffo-payment.tsweb/default/src/features/keys/components/dialogs/cc-switch-dialog.tsxweb/default/src/features/playground/hooks/use-stream-request.test.tsweb/default/src/features/wallet/hooks/use-creem-payment.tsweb/default/src/features/playground/components/message-error.tsxweb/default/src/features/auth/lib/turnstile-request.test.tsweb/default/src/features/keys/components/data-table-row-actions.tsxweb/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsxweb/default/src/features/pricing/lib/tier-expr.test.tsweb/default/src/features/auth/lib/turnstile-request.tsweb/default/src/features/channels/components/channels-columns.tsxweb/default/src/features/models/components/dialogs/view-details-dialog.tsxweb/default/src/features/wallet/hooks/use-payment.tsweb/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsxweb/default/src/features/system-settings/models/tiered-pricing-support.tsxweb/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsxweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/profile/api.tsweb/default/src/features/auth/api.tsweb/default/src/features/playground/hooks/use-stream-request.tsweb/default/src/features/pricing/lib/tier-expr.tsweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/channels/lib/channel-actions.tsweb/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
web/default/src/**/*.test.ts
📄 CodeRabbit inference engine (web/default/AGENTS.md)
工具函数与纯逻辑应优先编写单元测试;测试文件应命名为
*.test.ts。
Files:
web/default/src/features/playground/hooks/use-stream-request.test.tsweb/default/src/lib/safe-redirect.test.tsweb/default/src/features/auth/lib/turnstile-request.test.tsweb/default/src/lib/handle-server-error.test.tsweb/default/src/features/pricing/lib/tier-expr.test.tsweb/default/src/features/channels/lib/channel-actions.test.ts
**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.go: In Go business code, all JSON marshal/unmarshal operations must use the wrapper functions incommon/json.go(common.Marshal,common.Unmarshal,common.UnmarshalJsonStr,common.DecodeJson,common.GetJsonType) and must not directly callencoding/jsonfor actual encode/decode work.
All database code must be compatible with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+; prefer GORM abstractions over raw SQL, avoid directAUTO_INCREMENT/SERIAL, usecommonGroupCol/commonKeyColandcommonTrueVal/commonFalseValfor DB-specific SQL, branch withcommon.UsingPostgreSQL/common.UsingSQLite/common.UsingMySQL, avoid unsupported DB-specific functions/operators without fallback, and make migrations work across all three databases.
When implementing a new relay channel, confirm whether the provider supportsStreamOptions; if it does, add that channel tostreamSupportedChannels.
For request structs parsed from client JSON and re-marshaled to upstream providers, optional scalar fields must use pointer types withomitemptyso explicit zero/false values are preserved instead of dropped.
When working on tiered/dynamic billing expression code, readpkg/billingexpr/expr.mdfirst and follow its documented expression language, architecture, token normalization, quota conversion, and versioning patterns.
**/*.go: All JSON marshal/unmarshal operations in Go business code must use the wrapper functions incommon/json.go(common.Marshal,common.Unmarshal,common.UnmarshalJsonStr,common.DecodeJson,common.GetJsonType) instead of directly importing or callingencoding/jsonfor actual marshal/unmarshal work.
All database code in Go must remain compatible with SQLite, MySQL >= 5.7.8, and PostgreSQL >= 9.6; prefer GORM abstractions, avoid raw SQL unless necessary, use the shared DB helper variables for reserved words and boolean literals, branch with thecommon.UsingPostgreSQL/common.UsingSQLite/common.UsingMySQLflags when need...
Files:
middleware/turnstile_check_test.gomodel/redemption_search_test.gomodel/user_oauth_binding.gomodel/usedata_test.gomodel/main_migration_test.gomodel/checkin.gocontroller/token.gocontroller/telegram.gosetting/config/config_test.gomodel/utils.gocontroller/linuxdo.gomodel/redemption.goservice/quota.gopkg/billingexpr/security_cache_test.gocontroller/oidc.gocontroller/wechat.gomiddleware/turnstile-check.gocontroller/oauth_test.goservice/pre_consume_quota.gocontroller/github.gopkg/billingexpr/run.goservice/task_billing_test.gopkg/billingexpr/compile.gomodel/subscription.gocontroller/oauth.gocontroller/discord.gomodel/main.gomodel/token_cache.gomodel/errors.goservice/billing_session_test.gomodel/user_cache.gomodel/usedata.gocommon/redis.gosetting/config/config.gomodel/token.goservice/funding_source.gomodel/user_update_test.gomodel/user.goservice/billing_session.go
web/default/src/features/**/lib/**/*.ts
📄 CodeRabbit inference engine (web/default/AGENTS.md)
表单应使用 React Hook Form + Zod:在功能模块的
lib/下定义 schema,并用z.infer导出表单类型;useForm应配合@hookform/resolvers/zod进行校验。
Files:
web/default/src/features/auth/lib/turnstile-request.test.tsweb/default/src/features/pricing/lib/tier-expr.test.tsweb/default/src/features/auth/lib/turnstile-request.tsweb/default/src/features/channels/lib/channel-actions.test.tsweb/default/src/features/pricing/lib/tier-expr.tsweb/default/src/features/channels/lib/channel-actions.ts
web/default/**/package.json
📄 CodeRabbit inference engine (web/default/AGENTS.md)
脚本与包管理以
package.json为准;发布前应执行 typecheck、lint、format、生产构建,并检查产物体积与环境变量配置。
Files:
web/default/package.json
pkg/billingexpr/**/*.{go,md}
📄 CodeRabbit inference engine (AGENTS.md)
When working on tiered/dynamic billing expression pricing, first read
pkg/billingexpr/expr.mdand ensure all code changes in the billing expression system follow the documented design, architecture, token normalization, quota conversion, and versioning patterns.
Files:
pkg/billingexpr/security_cache_test.gopkg/billingexpr/expr.mdpkg/billingexpr/run.gopkg/billingexpr/compile.go
web/default/src/routes/**/*.{ts,tsx}
📄 CodeRabbit inference engine (web/default/AGENTS.md)
路由应使用 TanStack Router,并通过
createFileRoute定义;搜索参数应使用 Zod schema +validateSearch校验;认证与重定向应放在beforeLoad中;导航应优先使用useNavigate或Link,避免直接操作window.location。
Files:
web/default/src/routes/oauth/$provider.tsxweb/default/src/routes/__root.tsx
🔇 Additional comments (71)
web/default/src/features/playground/hooks/use-stream-request.test.ts (1)
1-32: LGTM!web/default/src/features/pricing/lib/tier-expr.test.ts (1)
1-58: LGTM!web/default/src/features/system-settings/models/tiered-pricing-support.tsx (1)
458-464: LGTM!go.mod (1)
64-69: LGTM!web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx (1)
98-117: LGTM!Also applies to: 245-359, 995-999
web/default/src/features/channels/lib/channel-actions.test.ts (1)
1-93: LGTM!web/default/src/features/channels/lib/channel-actions.ts (1)
40-218: LGTM!Also applies to: 379-441
web/default/src/i18n/locales/ru.json (1)
67-68: LGTM!Also applies to: 84-86, 801-802, 2701-2701, 3895-3895
web/default/src/features/profile/api.ts (1)
20-20: LGTM!Also applies to: 107-116, 187-194
pkg/billingexpr/expr.md (1)
79-105: LGTM!pkg/billingexpr/run.go (1)
72-88: LGTM!Also applies to: 118-158
pkg/billingexpr/security_cache_test.go (1)
1-59: LGTM!web/default/src/features/pricing/lib/tier-expr.ts (1)
19-19: LGTM!Also applies to: 272-321, 357-361
web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx (1)
131-132: LGTM!Also applies to: 234-248, 488-503, 560-566, 577-577
web/default/src/lib/handle-server-error.test.ts (1)
1-41: LGTM!web/default/package.json (1)
44-44: LGTM!middleware/turnstile-check.go (1)
17-25: LGTM!Also applies to: 35-35, 60-60
middleware/turnstile_check_test.go (1)
1-37: LGTM!web/default/src/features/auth/api.ts (1)
20-20: LGTM!Also applies to: 39-47, 72-75, 84-87, 109-114, 123-126
web/default/src/features/auth/lib/turnstile-request.test.ts (1)
1-37: LGTM!web/default/src/features/auth/lib/turnstile-request.ts (1)
1-26: LGTM!web/default/src/lib/oauth.ts (1)
21-67: LGTM!Also applies to: 154-154, 165-165, 179-179, 190-190
web/default/src/routes/oauth/$provider.tsx (1)
31-32: LGTM!Also applies to: 52-61, 63-77, 89-99
web/default/src/components/layout/components/footer.tsx (1)
26-26: LGTM!Also applies to: 236-238
web/default/src/components/layout/components/chat-presets-item.tsx (1)
213-213: LGTM!web/default/src/features/channels/components/channels-columns.tsx (1)
660-667: LGTM!web/default/src/features/keys/components/data-table-row-actions.tsx (1)
150-150: LGTM!web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx (1)
149-149: LGTM!web/default/src/features/models/components/dialogs/view-details-dialog.tsx (1)
245-251: LGTM!web/default/src/features/playground/components/message-error.tsx (1)
60-64: LGTM!web/default/src/lib/safe-redirect.test.ts (1)
19-42: LGTM!web/default/src/routes/__root.tsx (1)
63-65: LGTM!Also applies to: 76-77, 90-93
web/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsx (1)
117-119: LGTM!web/default/src/features/wallet/hooks/use-creem-payment.ts (1)
39-39: LGTM!web/default/src/features/wallet/hooks/use-payment.ts (1)
104-108: LGTM!web/default/src/features/wallet/hooks/use-waffo-payment.ts (1)
64-64: LGTM!web/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsx (1)
125-125: LGTM!Also applies to: 147-147, 359-362
web/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsx (1)
224-228: LGTM!web/default/src/lib/safe-redirect.ts (1)
20-38: 🔒 Security & PrivacyBackslash-prefixed targets already fall back to
/dashboard.new URL('/\\evil.example/path', redirectBase)resolves tohttps://evil.example, so theparsed.origin !== redirectBasecheck rejects it.> Likely an incorrect or invalid review comment.common/redis.go (1)
84-254: LGTM!model/user_cache.go (1)
53-62: LGTM!Also applies to: 119-148
model/utils.go (1)
85-85: LGTM!controller/telegram.go (1)
95-97: LGTM!model/errors.go (1)
7-12: LGTM!Also applies to: 17-21, 26-27, 31-31, 34-34
setting/config/config.go (1)
4-4: LGTM!Also applies to: 42-62, 93-161, 165-202
model/usedata.go (1)
4-4: LGTM!Also applies to: 54-54, 103-116, 129-157
model/redemption_search_test.go (1)
1-20: LGTM!pkg/billingexpr/compile.go (1)
36-38: LGTM!Also applies to: 105-115, 182-182
model/token_cache.go (1)
10-29: LGTM!Also applies to: 46-50
model/token.go (1)
23-28: LGTM!Also applies to: 285-299, 310-339, 385-461, 497-497, 535-535
model/subscription.go (1)
838-839: LGTM!model/main.go (1)
285-287: LGTM!Also applies to: 344-346, 680-756
model/main_migration_test.go (1)
5-20: LGTM!controller/token.go (1)
188-188: LGTM!Also applies to: 272-272
service/quota.go (1)
146-146: LGTM!Also applies to: 417-417
service/task_billing_test.go (1)
100-100: LGTM!Also applies to: 168-175
service/billing_session.go (1)
8-8: LGTM!Also applies to: 27-80, 112-117, 335-336, 348-348, 428-430
service/billing_session_test.go (1)
1-166: LGTM!service/funding_source.go (1)
70-102: LGTM!service/pre_consume_quota.go (1)
55-56: LGTM!model/user.go (1)
582-593: LGTM!Also applies to: 1160-1174, 1189-1234, 1358-1382, 1467-1535, 1665-1665
model/user_update_test.go (1)
12-19: LGTM!Also applies to: 28-146, 148-203, 205-326
controller/oauth.go (1)
38-49: LGTM!Also applies to: 245-247, 262-264
controller/oauth_test.go (1)
4-29: LGTM!Also applies to: 54-64
controller/discord.go (1)
135-135: LGTM!controller/github.go (1)
115-120: LGTM!controller/linuxdo.go (1)
212-212: LGTM!controller/oidc.go (1)
137-137: LGTM!controller/wechat.go (1)
78-78: LGTM!model/user_oauth_binding.go (1)
51-60: LGTM!model/redemption.go (1)
80-84: LGTM!
| // RedisInvalidateVersionedHash advances the shared generation and removes the | ||
| // cached hash atomically, preventing older DB snapshots from refilling it. | ||
| func RedisInvalidateVersionedHash(key, versionKey string) error { | ||
| ctx := context.Background() | ||
| _, err := RDB.TxPipelined(ctx, func(pipe redis.Pipeliner) error { | ||
| pipe.Incr(ctx, versionKey) | ||
| pipe.Del(ctx, key) | ||
| return nil | ||
| }) | ||
| return err |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial
Define a lifecycle for persistent cache-version keys.
Unlike the cached hashes, versionKey never expires or gets deleted. Redis will retain one key for every user/token ever invalidated. Add deletion during entity cleanup and monitor this keyspace; applying a normal cache TTL would be unsafe because resetting the generation can admit stale refills.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@common/redis.go` around lines 256 - 265, Add lifecycle cleanup for persistent
version keys used by RedisInvalidateVersionedHash, deleting the corresponding
versionKey whenever the associated user or token entity is removed. Do not apply
a normal TTL to versionKey, since generation resets could permit stale cache
refills, and ensure the cleanup path targets both the cached hash key and its
version key.
| // 事务成功后同步失效缓存,下一次读取以数据库结果回填。 | ||
| invalidateUserQuotaCache(userId) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Do not silently discard cache-invalidation failures.
A failed invalidation leaves the committed quota award hidden behind stale Redis data. Since the database transaction has already committed, log and enqueue a retry rather than returning an error that could trigger a duplicate check-in attempt.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@model/checkin.go` around lines 116 - 117, Update the post-transaction cache
invalidation call in the check-in flow to handle failures from
invalidateUserQuotaCache(userId) instead of discarding them. After the database
commit, log the invalidation error and enqueue a retry, without returning the
error to the caller or triggering another check-in attempt.
| func invalidateTokenCache(key string) error { | ||
| return common.RedisInvalidateVersionedHash(getTokenCacheKey(key), getTokenCacheVersionKey(key)) |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Expire version keys after the cache-fence window.
RedisInvalidateVersionedHash increments cache-version:token:* without assigning an expiration. Every invalidated or deleted token therefore leaves a permanent Redis key, causing unbounded growth. Set a TTL long enough to outlive pending asynchronous refills, preferably in the shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@model/token_cache.go` around lines 31 - 32, Update
RedisInvalidateVersionedHash, used by invalidateTokenCache, to assign an
expiration to each incremented cache-version key; choose a TTL that exceeds the
asynchronous refill/cache-fence window and apply it consistently in the shared
helper so invalidated token version keys can expire.
| for _, quotaData := range snapshot { | ||
| if err := saveQuotaData(quotaData); err != nil { | ||
| common.SysLog(fmt.Sprintf("saveQuotaData error: %s", err)) | ||
| failed = append(failed, quotaData) | ||
| } | ||
| } | ||
| CacheQuotaData = make(map[string]*QuotaData) | ||
| common.SysLog(fmt.Sprintf("保存数据看板数据成功,共保存%d条数据", size)) | ||
|
|
||
| if len(failed) > 0 { | ||
| CacheQuotaDataLock.Lock() | ||
| for _, quotaData := range failed { | ||
| logQuotaDataCache(quotaData) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Quota snapshots are retried without idempotency. A write may commit before returning an error, causing the requeued snapshot to increment the same counters twice.
model/usedata.go#L117-L128: add a persistent idempotency identifier and deduplicate retries transactionally.model/usedata_test.go#L12-L48: cover an ambiguous-commit/repeated-snapshot scenario and assert counters are applied exactly once.
As per coding guidelines, the database constraint and migration must support SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+.
📍 Affects 2 files
model/usedata.go#L117-L128(this comment)model/usedata_test.go#L12-L48
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@model/usedata.go` around lines 117 - 128, The quota snapshot retry flow
around saveQuotaData must persist a stable idempotency identifier and
transactionally deduplicate snapshots before applying counters, preventing
ambiguous commits from incrementing totals twice; add the corresponding
cross-database constraint and migration compatible with SQLite, MySQL 5.7.8+,
and PostgreSQL 9.6+. In model/usedata.go lines 117-128, update the
failed-snapshot requeue and save path to retain and reuse this identifier. In
model/usedata_test.go lines 12-48, add coverage for an ambiguous commit followed
by a repeated snapshot and assert counters are applied exactly once.
Source: Coding guidelines
| func populateUserCacheIfVersion(user User, version int64) error { | ||
| if !common.RedisEnabled { | ||
| return nil | ||
| } | ||
|
|
||
| return common.RedisHSetObj( | ||
| _, err := common.RedisHSetObjIfVersion( | ||
| getUserCacheKey(user.Id), | ||
| getUserCacheVersionKey(user.Id), | ||
| version, | ||
| user.ToBaseUser(), | ||
| time.Duration(common.RedisKeyCacheSeconds())*time.Second, | ||
| ) | ||
| return err |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline model/user_cache.go --items all \
--match 'updateUser.*Cache|invalidateUser.*Cache|populateUserCacheIfVersion'
rg -nP --type=go -C3 \
'\b(updateUserCache|updateUser(Role|Group|Email|Status|Name|Setting)Cache)\s*\(' \
modelRepository: MAX-API-Next/MAX-API
Length of output: 7640
🏁 Script executed:
#!/bin/bash
sed -n '1,320p' model/user_cache.go | cat -nRepository: MAX-API-Next/MAX-API
Length of output: 9737
🏁 Script executed:
#!/bin/bash
rg -n --type=go -C 3 '\b(updateUser(Status|Group|Role|Email|Name|Setting)Cache|invalidateUserCache|InvalidateUserCache)\s*\(' modelRepository: MAX-API-Next/MAX-API
Length of output: 7544
🏁 Script executed:
#!/bin/bash
rg -n --type=go -C 3 'RedisHSetObjIfVersion|RedisHSetFieldIfVersion|RedisInvalidateVersionedHash|RedisGetCacheVersion|RedisHSetField\(' common modelRepository: MAX-API-Next/MAX-API
Length of output: 8206
🏁 Script executed:
#!/bin/bash
rg -n --type=go -C 2 'GetUserCache\(|getUserStatusCache\(|getUserRoleCache\(|ContextKeyUserStatus|ContextKeyUserRole|RoleAdmin|UserStatusDisabled|UserStatusEnabled' model common middleware controllerRepository: MAX-API-Next/MAX-API
Length of output: 32480
Make the narrow user-cache writes participate in the version fence. populateUserCacheIfVersion is guarded, but the direct HSET paths for group/status/role/name/setting do not advance cache-version:user:*. A stale DB refill can still clobber a newer auth-sensitive update; use the versioned helper or bump the generation on every mutation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@model/user_cache.go` around lines 71 - 82, Update the direct HSET mutation
paths for user group, status, role, name, and setting caches to participate in
the same version fence as populateUserCacheIfVersion. Reuse the existing
versioned cache helper and appropriate user cache version key, or explicitly
advance that generation on every mutation, ensuring stale DB refills cannot
overwrite newer auth-sensitive updates.
|
|
||
| func IsOidcIdAlreadyTaken(oidcId string) bool { | ||
| return DB.Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1 | ||
| return DB.Unscoped().Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Treat any matching OIDC record as already taken.
RowsAffected == 1 returns false when duplicate rows exist, allowing another account to be created with the same OIDC identity. The column is indexed but not unique, so use > 0.
Proposed fix
func IsOidcIdAlreadyTaken(oidcId string) bool {
- return DB.Unscoped().Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
+ return DB.Unscoped().Where("oidc_id = ?", oidcId).Limit(1).Find(&User{}).RowsAffected > 0
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return DB.Unscoped().Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1 | |
| func IsOidcIdAlreadyTaken(oidcId string) bool { | |
| return DB.Unscoped().Where("oidc_id = ?", oidcId).Limit(1).Find(&User{}).RowsAffected > 0 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@model/user.go` at line 1274, Update the OIDC identity existence check in the
surrounding user lookup to return true when RowsAffected is greater than zero,
so any matching record is treated as already taken rather than requiring exactly
one row.
| if s.compensationFailed { | ||
| return tokenErr | ||
| } | ||
| if s.appliedFundingDelta == 0 { | ||
| s.fundingSettled = false | ||
| } else { | ||
| compensated, compensationErr := s.funding.Settle(-int(s.appliedFundingDelta)) | ||
| if compensationErr != nil || compensated != -s.appliedFundingDelta { | ||
| s.compensationFailed = true | ||
| common.SysLog(fmt.Sprintf("error compensating funding after token settlement failure (userId=%d, tokenId=%d, delta=%d, applied=%d, compensated=%d): %v", | ||
| s.relayInfo.UserId, s.relayInfo.TokenId, delta, s.appliedFundingDelta, compensated, compensationErr)) | ||
| } else { | ||
| s.fundingSettled = false | ||
| s.appliedFundingDelta = 0 | ||
| s.compensationFailed = false | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Track partial compensation before retrying token settlement.
When compensation applies only part of -appliedFundingDelta, the code sets compensationFailed but retains the original committed delta. A later token-only retry then applies the full token delta despite funding having been partially reversed. Record the residual committed amount and reconcile/reapply the missing funding portion before settling the token.
Add a test where compensation returns a nonzero partial amount.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@service/billing_session.go` around lines 92 - 107, Update the compensation
handling in the billing settlement flow so a partial result from funding.Settle
records the residual committed funding amount instead of retaining the original
appliedFundingDelta. Before a later token-only retry, reconcile or reapply that
missing funding portion before settling the token, while preserving
compensationFailed state and existing full-compensation behavior. Add a test
covering a nonzero partial compensation result and verifying the retry uses the
corrected funding amount.
| if rollbackErr := model.IncreaseTokenQuota(relayInfo.TokenId, relayInfo.TokenKey, preConsumedQuota); rollbackErr != nil { | ||
| common.SysLog(fmt.Sprintf("error rolling back token quota after user pre-consume failure (userId=%d, tokenId=%d, amount=%d): %s", | ||
| relayInfo.UserId, relayInfo.TokenId, preConsumedQuota, rollbackErr.Error())) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not abandon failed token-quota rollbacks.
If IncreaseTokenQuota fails, the request returns an error while the token deduction remains committed. Logging alone cannot restore the user's quota. Persist a retryable compensation record or make the two database mutations transactional so this failure cannot leave balances inconsistent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@service/pre_consume_quota.go` around lines 76 - 79, Update the rollback
handling around IncreaseTokenQuota in the pre-consume failure path so a failed
compensation is not only logged: make the token deduction and quota restoration
transactional, or persist a durable retryable compensation record containing the
token and amount details. Ensure the failed request cannot leave the user’s
token balance permanently reduced without a recovery path.
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)