Skip to content

添加openai缓存计费显示#20

Open
CSCITech wants to merge 6 commits into
mainfrom
cscitech
Open

添加openai缓存计费显示#20
CSCITech wants to merge 6 commits into
mainfrom
cscitech

Conversation

@CSCITech

Copy link
Copy Markdown
Contributor

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Channel connectivity tests now show clearer targets, response times, and refreshed results.
    • Notifications adapt automatically between desktop and mobile layouts.
    • Added safer internal redirects and improved OAuth binding flow handling.
    • Turnstile verification now supports secure request headers.
  • Bug Fixes

    • Improved quota, billing settlement, cache consistency, and token handling.
    • Corrected redemption searches containing special characters.
    • Prevented stale stream errors and improved OAuth deleted-account responses.
    • Expanded cache pricing details for supported requests.
  • Security

    • Restricted pricing expressions to approved request metadata.
    • External windows now use enhanced privacy and security protections.

Walkthrough

The 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.

Changes

Channel testing and frontend interaction updates

Layer / File(s) Summary
Channel test cache flow
web/default/src/features/channels/...
Channel tests now report response timing, patch React Query caches, consolidate batch refreshes, and validate cache helpers.
Channel test localization
web/default/src/i18n/locales/*
Locales add translated test-result, channel, model, and response-time labels.
Frontend display and navigation behavior
web/default/src/components/..., web/default/src/features/..., web/default/src/routes/...
Responsive notifications, cache billing rows, draft model state, OAuth binding state, safe redirects, stream lifecycle, and safe error rendering are updated.
Request and expression security
middleware/..., pkg/billingexpr/..., web/default/src/features/auth/...
Turnstile moves to headers, pricing expressions use allowlists and restricted evaluation, and external windows use noopener,noreferrer.

Backend consistency and settlement

Layer / File(s) Summary
Versioned Redis cache operations
common/redis.go, model/*cache.go, model/token.go, model/user.go
Redis cache writes and invalidations use generation fencing to reject stale asynchronous refills.
Quota types and database updates
model/..., service/..., controller/token.go
Token quota fields and storage use int64/bigint-compatible handling, with conditional quota updates and migration coverage.
OAuth and domain error handling
controller/oauth.go, controller/*oauth.go, model/user.go, model/errors.go
Provider lookup errors and soft-deleted users map through shared domain-specific handling.
Settlement and configuration validation
service/..., setting/config/..., model/usedata.go
Billing settlement retries and compensates funding failures, while configuration and quota-cache persistence propagate errors.
Search and expression cache behavior
model/redemption.go, pkg/billingexpr/compile.go
Redemption searches escape wildcard characters and expression caches evict entries incrementally.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit hops through caches bright,
Fences stale data day and night.
Quotas count and streams behave,
OAuth errors now are brave.
Tiny headers guard the gate!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive 描述内容仍是模板占位符,没有提供与代码变更相关的实际摘要。 请用人工撰写的简洁摘要替换模板内容,说明做了什么、为什么这样改及验证结果。
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了本次为 OpenAI 添加缓存计费显示的主要改动。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cscitech

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d07cf81 and 6b6e051.

📒 Files selected for processing (12)
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/default/src/features/channels/types.ts
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/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 中应合理使用 useMemouseCallbackReact.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.ts
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/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.tstypes.tsconstants.ts 等;通用组件应放在 src/components/,通用工具与类型应放在 src/lib/

Files:

  • web/default/src/features/channels/types.ts
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/channels/components/data-table-row-actions.tsx
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/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.ts
  • web/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 Quality

Wire node:test into the workspace test command
I couldn’t find a test script or workflow that runs these suites, so these node:test files 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

Comment thread web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx Outdated
Comment thread web/default/src/i18n/locales/ru.json Outdated
@CSCITech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CSCITech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Ignore 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 win

Configuration updates are not atomic. A later validation error leaves earlier fields modified despite LoadFromDB returning 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6e051 and 8ae50e9.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (77)
  • common/redis.go
  • controller/discord.go
  • controller/github.go
  • controller/linuxdo.go
  • controller/oauth.go
  • controller/oauth_test.go
  • controller/oidc.go
  • controller/telegram.go
  • controller/token.go
  • controller/wechat.go
  • go.mod
  • middleware/turnstile-check.go
  • middleware/turnstile_check_test.go
  • model/checkin.go
  • model/errors.go
  • model/main.go
  • model/main_migration_test.go
  • model/redemption.go
  • model/redemption_search_test.go
  • model/subscription.go
  • model/token.go
  • model/token_cache.go
  • model/usedata.go
  • model/usedata_test.go
  • model/user.go
  • model/user_cache.go
  • model/user_oauth_binding.go
  • model/user_update_test.go
  • model/utils.go
  • pkg/billingexpr/compile.go
  • pkg/billingexpr/expr.md
  • pkg/billingexpr/run.go
  • pkg/billingexpr/security_cache_test.go
  • service/billing_session.go
  • service/billing_session_test.go
  • service/funding_source.go
  • service/pre_consume_quota.go
  • service/quota.go
  • service/task_billing_test.go
  • setting/config/config.go
  • setting/config/config_test.go
  • tools/jsonwrapcheck/allowlist.txt
  • web/default/package.json
  • web/default/src/components/layout/components/chat-presets-item.tsx
  • web/default/src/components/layout/components/footer.tsx
  • web/default/src/features/auth/api.ts
  • web/default/src/features/auth/lib/turnstile-request.test.ts
  • web/default/src/features/auth/lib/turnstile-request.ts
  • web/default/src/features/channels/components/channels-columns.tsx
  • web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx
  • web/default/src/features/models/components/dialogs/view-details-dialog.tsx
  • web/default/src/features/playground/components/message-error.tsx
  • web/default/src/features/playground/hooks/use-stream-request.test.ts
  • web/default/src/features/playground/hooks/use-stream-request.ts
  • web/default/src/features/pricing/lib/tier-expr.test.ts
  • web/default/src/features/pricing/lib/tier-expr.ts
  • web/default/src/features/profile/api.ts
  • web/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsx
  • web/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsx
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/features/system-settings/models/tiered-pricing-support.tsx
  • web/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsx
  • web/default/src/features/wallet/hooks/use-creem-payment.ts
  • web/default/src/features/wallet/hooks/use-payment.ts
  • web/default/src/features/wallet/hooks/use-waffo-payment.ts
  • web/default/src/i18n/locales/ru.json
  • web/default/src/lib/handle-server-error.test.ts
  • web/default/src/lib/handle-server-error.ts
  • web/default/src/lib/oauth.ts
  • web/default/src/lib/safe-redirect.test.ts
  • web/default/src/lib/safe-redirect.ts
  • web/default/src/routes/__root.tsx
  • web/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 中应合理使用 useMemouseCallbackReact.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.ts
  • web/default/src/components/layout/components/chat-presets-item.tsx
  • web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx
  • web/default/src/features/playground/hooks/use-stream-request.test.ts
  • web/default/src/features/wallet/hooks/use-creem-payment.ts
  • web/default/src/lib/safe-redirect.test.ts
  • web/default/src/features/playground/components/message-error.tsx
  • web/default/src/features/auth/lib/turnstile-request.test.ts
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/lib/handle-server-error.test.ts
  • web/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsx
  • web/default/src/features/pricing/lib/tier-expr.test.ts
  • web/default/src/features/auth/lib/turnstile-request.ts
  • web/default/src/lib/safe-redirect.ts
  • web/default/src/features/channels/components/channels-columns.tsx
  • web/default/src/features/models/components/dialogs/view-details-dialog.tsx
  • web/default/src/features/wallet/hooks/use-payment.ts
  • web/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsx
  • web/default/src/features/system-settings/models/tiered-pricing-support.tsx
  • web/default/src/components/layout/components/footer.tsx
  • web/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsx
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/profile/api.ts
  • web/default/src/lib/handle-server-error.ts
  • web/default/src/features/auth/api.ts
  • web/default/src/lib/oauth.ts
  • web/default/src/routes/oauth/$provider.tsx
  • web/default/src/routes/__root.tsx
  • web/default/src/features/playground/hooks/use-stream-request.ts
  • web/default/src/features/pricing/lib/tier-expr.ts
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/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.tstypes.tsconstants.ts 等;通用组件应放在 src/components/,通用工具与类型应放在 src/lib/

Files:

  • web/default/src/features/wallet/hooks/use-waffo-payment.ts
  • web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx
  • web/default/src/features/playground/hooks/use-stream-request.test.ts
  • web/default/src/features/wallet/hooks/use-creem-payment.ts
  • web/default/src/features/playground/components/message-error.tsx
  • web/default/src/features/auth/lib/turnstile-request.test.ts
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/features/system-settings/integrations/ionet-deployment-settings-section.tsx
  • web/default/src/features/pricing/lib/tier-expr.test.ts
  • web/default/src/features/auth/lib/turnstile-request.ts
  • web/default/src/features/channels/components/channels-columns.tsx
  • web/default/src/features/models/components/dialogs/view-details-dialog.tsx
  • web/default/src/features/wallet/hooks/use-payment.ts
  • web/default/src/features/usage-logs/components/dialogs/audio-preview-dialog.tsx
  • web/default/src/features/system-settings/models/tiered-pricing-support.tsx
  • web/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsx
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/profile/api.ts
  • web/default/src/features/auth/api.ts
  • web/default/src/features/playground/hooks/use-stream-request.ts
  • web/default/src/features/pricing/lib/tier-expr.ts
  • web/default/src/features/system-settings/models/model-ratio-visual-editor.tsx
  • web/default/src/features/channels/lib/channel-actions.ts
  • web/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.ts
  • web/default/src/lib/safe-redirect.test.ts
  • web/default/src/features/auth/lib/turnstile-request.test.ts
  • web/default/src/lib/handle-server-error.test.ts
  • web/default/src/features/pricing/lib/tier-expr.test.ts
  • web/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 in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType) and must not directly call encoding/json for 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 direct AUTO_INCREMENT/SERIAL, use commonGroupCol/commonKeyCol and commonTrueVal/commonFalseVal for DB-specific SQL, branch with common.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 supports StreamOptions; if it does, add that channel to streamSupportedChannels.
For request structs parsed from client JSON and re-marshaled to upstream providers, optional scalar fields must use pointer types with omitempty so explicit zero/false values are preserved instead of dropped.
When working on tiered/dynamic billing expression code, read pkg/billingexpr/expr.md first 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 in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType) instead of directly importing or calling encoding/json for 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 the common.UsingPostgreSQL / common.UsingSQLite / common.UsingMySQL flags when need...

Files:

  • middleware/turnstile_check_test.go
  • model/redemption_search_test.go
  • model/user_oauth_binding.go
  • model/usedata_test.go
  • model/main_migration_test.go
  • model/checkin.go
  • controller/token.go
  • controller/telegram.go
  • setting/config/config_test.go
  • model/utils.go
  • controller/linuxdo.go
  • model/redemption.go
  • service/quota.go
  • pkg/billingexpr/security_cache_test.go
  • controller/oidc.go
  • controller/wechat.go
  • middleware/turnstile-check.go
  • controller/oauth_test.go
  • service/pre_consume_quota.go
  • controller/github.go
  • pkg/billingexpr/run.go
  • service/task_billing_test.go
  • pkg/billingexpr/compile.go
  • model/subscription.go
  • controller/oauth.go
  • controller/discord.go
  • model/main.go
  • model/token_cache.go
  • model/errors.go
  • service/billing_session_test.go
  • model/user_cache.go
  • model/usedata.go
  • common/redis.go
  • setting/config/config.go
  • model/token.go
  • service/funding_source.go
  • model/user_update_test.go
  • model/user.go
  • service/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.ts
  • web/default/src/features/pricing/lib/tier-expr.test.ts
  • web/default/src/features/auth/lib/turnstile-request.ts
  • web/default/src/features/channels/lib/channel-actions.test.ts
  • web/default/src/features/pricing/lib/tier-expr.ts
  • web/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.md and 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.go
  • pkg/billingexpr/expr.md
  • pkg/billingexpr/run.go
  • pkg/billingexpr/compile.go
web/default/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/default/AGENTS.md)

路由应使用 TanStack Router,并通过 createFileRoute 定义;搜索参数应使用 Zod schema + validateSearch 校验;认证与重定向应放在 beforeLoad 中;导航应优先使用 useNavigateLink,避免直接操作 window.location

Files:

  • web/default/src/routes/oauth/$provider.tsx
  • web/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 & Privacy

Backslash-prefixed targets already fall back to /dashboard. new URL('/\\evil.example/path', redirectBase) resolves to https://evil.example, so the parsed.origin !== redirectBase check 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!

Comment thread common/redis.go
Comment on lines +256 to +265
// 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Comment thread model/checkin.go
Comment on lines +116 to +117
// 事务成功后同步失效缓存,下一次读取以数据库结果回填。
invalidateUserQuotaCache(userId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment thread model/token_cache.go
Comment on lines +31 to +32
func invalidateTokenCache(key string) error {
return common.RedisInvalidateVersionedHash(getTokenCacheKey(key), getTokenCacheVersionKey(key))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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.

Comment thread model/usedata.go
Comment on lines +117 to +128
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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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

Comment thread model/user_cache.go
Comment on lines +71 to +82
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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*\(' \
  model

Repository: MAX-API-Next/MAX-API

Length of output: 7640


🏁 Script executed:

#!/bin/bash
sed -n '1,320p' model/user_cache.go | cat -n

Repository: 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*\(' model

Repository: 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 model

Repository: 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 controller

Repository: 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.

Comment thread model/user.go

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Suggested change
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.

Comment on lines +92 to +107
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +76 to +79
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()))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant