Skip to content

feat: allow existing users to bind inviter via aff code - #3249

Closed
hawken-im wants to merge 5396 commits into
QuantumNous:mainfrom
hawken-im:feat/bind-inviter-for-existing-users
Closed

feat: allow existing users to bind inviter via aff code#3249
hawken-im wants to merge 5396 commits into
QuantumNous:mainfrom
hawken-im:feat/bind-inviter-for-existing-users

Conversation

@hawken-im

@hawken-im hawken-im commented Mar 13, 2026

Copy link
Copy Markdown

Summary

Currently, invitation codes can only be applied during registration via URL parameter ?aff=XXXX. This PR adds the ability for existing users to bind an inviter after account creation, with an admin-controlled feature gate.

  • model/user.go: Add BindInviterByAffCode() — admin gate check, self-bind guard, optimistic lock (WHERE inviter_id = 0 prevents race conditions), quota rewards mirroring registration logic
  • controller/user.go: Add BindAffCode handler
  • router/api-router.go: Register POST /api/user/bind_aff under selfRoute (requires UserAuth)
  • common/constants.go: Add AllowExistingUserBindInviterEnabled flag (default false)
  • model/option.go: Wire new option into InitOptionMap and updateOptionMap
  • Frontend: Bind-inviter card in InvitationCard (only shown when inviter_id === 0), admin toggle in Operation Settings, i18n keys for all 6 locales (zh-CN, zh-TW, en, fr, ja, ru, vi)

Behaviour

Condition Result
Admin toggle off 管理员未开启此功能
User already has inviter 您已绑定过邀请人,无法重复绑定
User binds own aff code 不能绑定自己的邀请码
Invalid aff code 邀请码无效
Valid, first-time bind success: true, both parties receive quota rewards

Test plan

  • T1: Normal bind — success: true, inviter_id updated
  • T2: Duplicate bind — rejected with correct message
  • T3: Self-bind — rejected with correct message
  • T4: Invalid code — rejected with correct message
  • T5: Feature gate off — rejected with correct message
  • Existing registration flow unchanged (purely additive change)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Registered users can bind an existing referrer by entering an invitation code; both parties receive quota rewards on success.
    • New rate-limited API endpoint to submit invitation codes.
  • Settings

    • Admin toggle to enable/disable allowing existing users to bind a referrer.
  • UI

    • “Bind Inviter” input and action added to the referral/invitation card with loading and success/error handling.
  • Localization

    • Added translations for the binding flow in multiple locales.

Calcium-Ion and others added 30 commits February 6, 2026 17:54
fix: harden token search with pagination, rate limiting and input validation
- Change ESCAPE character from '\' to '!' for compatibility with MySQL/PostgreSQL/SQLite
- Adjust sanitization logic to escape '!' and '_' correctly, improving input validation for search queries
fix: /v1/chat/completions -> /v1/responses json_schema
将散落在多个文件中的预扣费/结算/退款逻辑抽象为统一的 BillingSession 生命周期管理:

- 新增 BillingSettler 接口 (relay/common/billing.go) 避免循环引用
- 新增 FundingSource 接口 + WalletFunding / SubscriptionFunding 实现 (service/funding_source.go)
- 新增 BillingSession 封装预扣/结算/退款原子操作 (service/billing_session.go)
- 新增 SettleBilling 统一结算辅助函数,替换各 handler 中的 quotaDelta 模式
- 重写 PreConsumeBilling 为 BillingSession 工厂入口
- controller/relay.go 退款守卫改用 BillingSession.Refund()

修复的 Bug:
- 令牌额度泄漏:PreConsumeTokenQuota 成功但 DecreaseUserQuota 失败时未回滚
- 订阅退款遗漏:FinalPreConsumedQuota=0 但 SubscriptionPreConsumed>0 时跳过退款
- 订阅多扣费:subConsume 强制为 1 但 FinalPreConsumedQuota 不同步
- 退款路径不统一:钱包/订阅退款逻辑现统一由 FundingSource.Refund 分派
- Settle 部分失败保护:新增 fundingSettled 标记,资金来源提交后
  令牌调整失败不再导致 Refund 误退已结算的资金
- 订阅多扣费修复:trySubscription 传 subConsume 而非 preConsumedQuota
  给 preConsume,保证三者(amount/preConsume/FinalPreConsumedQuota)一致
- 令牌回滚错误记录:preConsume 中 funding 失败时令牌回滚错误不再丢弃
- 移除钱包路径死代码:用户额度不足的 strings.Contains 匹配不可能命中
- WalletFunding.Refund 不重试:IncreaseUserQuota 非幂等,重试会多退
…e recharge card tabs

- Defaulting to subscriptions when available and avoiding initial flash when no plans exist.
- Adjust the wide-screen layout to place wallet and invite sections side by side, simplify the subscription header and controls, and add padding to prevent card borders from clipping.
- Update related i18n strings by adding the new tab label and removing the obsolete subscription blurb.
…iption-card-when-no-plans

✨ refactor(wallet): Top-up layout to embed subscription plans into the recharge card tabs
…-session

refactor: 抽象统一计费会话 BillingSession
Add a lightweight active-subscription check to skip subscription pre-consume when none exist, reducing unnecessary transactions and locks. In the subscription UI, disable subscription-first options when no active plan is available, show the effective fallback to wallet with a clear notice, and distinguish “invalidated” from “expired” states. Update i18n strings across supported locales to reflect the new messages and status labels.
Aligns the error variable types in the subscription-first path so that quota fallback checks use the correct NewAPIError.
This prevents build failures and preserves the intended wallet fallback when subscription pre-consume returns an insufficient quota error.
Routes quota alerts through a subscription-specific check when billing from subscriptions, preventing wallet-based thresholds from triggering false warnings.
Updates the notification settings description and localization keys to clarify that both wallet and subscription balances are monitored.
…n-quota-notify

🔔 feat: Add subscription-aware quota notifications and update UI copy
…-preference-fallback

✨ chore: Improve subscription billing fallback and UI states
…tumNous#2881)

当上游为 AWS Bedrock 时,message_delta 的 usage 可能缺少 input_tokens、
cache_creation_input_tokens、cache_read_input_tokens 等字段,导致与原生
Anthropic 格式不一致。从 message_start 积累的 claudeInfo 中补全这些字段后
重新序列化,确保客户端收到一致的 usage 格式。
Modified the formatUserLogs function to include a startIdx parameter, allowing for more flexible log ID assignment. Updated calls to this function in GetLogByTokenId and GetUserLogs to pass the appropriate starting index.
feat: add Codex channel disclaimer (i18n, OpenAI terms)
feat: Force beta=true parameter for Anthropic channel
feat(oauth): implement custom OAuth provider
CaIon and others added 20 commits March 6, 2026 21:36
Keep the model pricing editor wording aligned with the new price-based UI while exposing cache, image, and audio pricing in the marketplace so users can see the full configured pricing model.
Introduce a billing display mode feature allowing users to toggle between price and ratio views. Update relevant components and hooks to support this new functionality, ensuring consistent pricing information is displayed across the application.
Add siteDisplayType prop across various pricing components to conditionally render pricing information based on the selected display type. This update enhances the user experience by ensuring that pricing details are accurately represented according to the chosen display mode, particularly for token-based views.
为渠道参数覆盖可视化规则提供拖拽排序支持
…4f8a4248b0ab3b03ba703796ea3

fix: kling risk fail return openAIVideo error
fix: add explicit docker-compose networks
…ride-beta-header-append

feat:support $keep_only_declared and deduped $append for header override
chore: update model lists for frequently used channels
Currently invitation codes can only be applied during registration.
This adds the ability for existing users to bind an inviter post-signup,
controlled by an admin toggle (AllowExistingUserBindInviterEnabled).

Changes:
- model/user.go: Add BindInviterByAffCode() with admin gate, self-bind
  guard, optimistic lock (WHERE inviter_id = 0), and quota rewards
- controller/user.go: Add BindAffCode handler
- router/api-router.go: Register POST /api/user/bind_aff
- common/constants.go: Add AllowExistingUserBindInviterEnabled flag
- model/option.go: Wire option into InitOptionMap and updateOptionMap
- web: Add bind-inviter UI in InvitationCard (hidden once bound),
  admin toggle in SettingsCreditLimit, i18n for all 6 locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

ghost commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b5d1259-0476-448a-a039-fc20da1da77e

📥 Commits

Reviewing files that changed from the base of the PR and between d66438e and 8fe0c1a.

📒 Files selected for processing (1)
  • model/user.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • model/user.go

Walkthrough

Adds a feature allowing existing registered users to bind an inviter via an affiliate code: feature flag, API endpoint, backend binding logic with transaction/optimistic lock, frontend UI and settings, status field, and locale entries across multiple languages.

Changes

Cohort / File(s) Summary
Backend Configuration
common/constants.go, model/option.go
Add feature flag AllowExistingUserBindInviterEnabled (default false) and integrate it into OptionMap initialization and updates.
Backend API Handler & Routing
controller/user.go, router/api-router.go
Add BindAffCode HTTP handler for POST /api/user/bind_aff and register route with CriticalRateLimit middleware.
Backend Business Logic
model/user.go
Add BindInviterByAffCode(userId, affCode) error implementing feature-flag check, validations, aff-code resolution, optimistic-lock transaction to set inviter_id, quota updates, logs, and cache invalidation.
Status Endpoint
controller/misc.go
Expose allow_existing_user_bind_inviter in GetStatus response sourced from the feature flag.
Frontend UI Components
web/src/components/topup/InvitationCard.jsx, web/src/components/topup/index.jsx
Add binding UI (input, button, loading state) and pass new props (API, showSuccess, showError, reloadUser, allowBindInviter) to enable binding flow.
Frontend Settings
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx
Add AllowExistingUserBindInviterEnabled toggle in settings form and wire it into form state.
Localization
web/src/i18n/locales/*.json
Add six new translation keys for invitation/binding UI across en, fr, ja, ru, vi, zh-CN, zh-TW locales.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Handler as BindAffCode Handler
    participant Model as BindInviterByAffCode
    participant DB as Database

    Client->>Handler: POST /api/user/bind_aff {aff_code}
    Handler->>Handler: Trim & validate aff_code
    Handler->>Model: BindInviterByAffCode(userId, aff_code)

    Model->>Model: Check feature flag
    Model->>DB: Load user (by userId)
    DB-->>Model: user record
    Model->>Model: Resolve inviter_id from aff_code
    Model->>Model: Validate no self-bind and inviter exists

    Model->>DB: BEGIN TRANSACTION
    Model->>DB: UPDATE users SET inviter_id=? WHERE id=? AND inviter_id=0
    DB-->>Model: affected rows
    Model->>DB: UPDATE quotas/counters for inviter & invitee
    Model->>DB: COMMIT
    DB-->>Model: commit result

    Model-->>Handler: return success / error
    Handler-->>Client: JSON response (success or error)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • seefs001

Poem

🐰 I nibble code beneath the moon,
A bind, a click, an invite soon,
Two quotas grow, a ribbon tied,
Hop—aff codes bridge both sides,
Hooray, rewards for far and near! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature addition: allowing existing users to bind an inviter via affiliate code after account creation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

ghost left a comment

Copy link
Copy Markdown
Contributor

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 the current code and only fix it if needed.

Inline comments:
In `@controller/user.go`:
- Around line 348-350: After binding JSON with c.ShouldBindJSON(&req), trim
whitespace from req.AffCode (e.g. req.AffCode = strings.TrimSpace(req.AffCode))
before performing the empty check; then use the trimmed value in the if
condition that currently reads "if err := c.ShouldBindJSON(&req); err != nil ||
req.AffCode == "" { ... }" and call common.ApiErrorMsg(c, "邀请码不能为空") when the
trimmed AffCode is empty. Ensure you import the strings package if not already
present.

In `@model/user.go`:
- Around line 1064-1081: The bind + reward sequence must be made atomic: wrap
the optimistic update of inviter_id and both reward operations inside a DB
transaction (use DB.Transaction or tx := DB.Begin()/tx.Commit()/tx.Rollback()),
perform the WHERE "id = ? AND inviter_id = 0" Update on User via the transaction
(tx.Model(&User{}).Where(...).Update(...)) and return error if RowsAffected==0,
then within the same transaction call IncreaseUserQuota (or inline the quota
increment into the users/quotas table via tx to check its error) and write
RecordLog via tx, and for the inviter avoid calling inviteUser() (which does
RMW) and instead increment inviter counters in-place using a single atomic DB
update (e.g. tx.Model(&User{}).Where("id = ?",
inviterId).UpdateColumn("invite_count", gorm.Expr("invite_count + ?", 1)) and
any quota increment for inviter similarly), check errors after each tx operation
and let the transaction roll back on failure so the bind and rewards succeed or
fail together.

In `@web/src/components/topup/InvitationCard.jsx`:
- Around line 227-261: The bind-inviter card is currently shown based only on
userState?.user?.inviter_id === 0 which lets users see the form even when the
backend feature is disabled; update the render condition to also check the
feature flag (e.g., allowBindInviter) passed from the parent so the block inside
InvitationCard (the Card/Input/button and handleBindAff) is only
rendered/enabled when allowBindInviter is true and inviter_id === 0; ensure the
parent passes allowBindInviter through props/state and use that prop in
InvitationCard to hide or disable the input/button when the feature is off.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 872bf019-2c26-411e-81a8-64ef0ae09aeb

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b05e and cf95698.

📒 Files selected for processing (15)
  • common/constants.go
  • controller/user.go
  • model/option.go
  • model/user.go
  • router/api-router.go
  • web/src/components/topup/InvitationCard.jsx
  • web/src/components/topup/index.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Setting/Operation/SettingsCreditLimit.jsx

Comment thread controller/user.go Outdated
Comment thread model/user.go Outdated
Comment thread web/src/components/topup/InvitationCard.jsx Outdated
- Trim whitespace from aff_code before empty check in controller
- Wrap bind + reward operations in a DB transaction for atomicity
- Expose AllowExistingUserBindInviterEnabled via /api/status endpoint
- Pass allowBindInviter flag to InvitationCard and gate UI on it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
model/user.go (1)

1066-1085: ⚠️ Potential issue | 🔴 Critical

The reward path still escapes the transaction and can deadlock itself.

tx only covers the inviter_id write. Line 1079 calls IncreaseUserQuota() on the global DB, and Line 1083 calls inviteUser() which does its own read-modify-write on the global DB too; both errors are dropped. After Line 1068 updates the invitee row, the out-of-transaction quota update can block on that same row under MySQL/PostgreSQL, and even when it doesn’t, inviter_id can commit while quota/counter updates fail or lose increments.

Suggested fix
 return DB.Transaction(func(tx *gorm.DB) error {
 	result := tx.Model(&User{}).Where("id = ? AND inviter_id = 0", userId).
 		Update("inviter_id", inviterId)
 	if result.Error != nil {
 		return result.Error
 	}
 	if result.RowsAffected == 0 {
 		return errors.New("绑定失败,请重试")
 	}

 	if common.QuotaForInvitee > 0 {
-		_ = IncreaseUserQuota(userId, common.QuotaForInvitee, true)
-		RecordLog(userId, LogTypeSystem, fmt.Sprintf("绑定邀请码赠送 %s", logger.LogQuota(common.QuotaForInvitee)))
+		if err := tx.Model(&User{}).Where("id = ?", userId).
+			Update("quota", gorm.Expr("quota + ?", common.QuotaForInvitee)).Error; err != nil {
+			return err
+		}
 	}
 	if common.QuotaForInviter > 0 {
-		_ = inviteUser(inviterId)
-		RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("被邀请用户绑定赠送 %s", logger.LogQuota(common.QuotaForInviter)))
+		if err := tx.Model(&User{}).Where("id = ?", inviterId).Updates(map[string]interface{}{
+			"aff_count":   gorm.Expr("aff_count + ?", 1),
+			"aff_quota":   gorm.Expr("aff_quota + ?", common.QuotaForInviter),
+			"aff_history": gorm.Expr("aff_history + ?", common.QuotaForInviter),
+		}).Error; err != nil {
+			return err
+		}
 	}

 	return nil
 })

Then do cache/log side effects only after DB.Transaction returns success.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/user.go` around lines 1066 - 1085, The current transaction only updates
inviter_id via DB.Transaction(tx ...) while calling IncreaseUserQuota(userId,
...), inviteUser(inviterId) and RecordLog(...) outside the transaction (and
swallowing their errors), which can deadlock or leave inviter_id committed while
quota/counters fail; fix by performing quota and inviter counter mutations
inside the same transaction using the provided tx (i.e. replace
IncreaseUserQuota and inviteUser calls with transactional equivalents or pass tx
into those functions so they use tx for their read-modify-write operations) and
propagate/return any errors from those calls so the transaction can roll back on
failure; keep non-database side-effects (cache updates,
RecordLog/logging/notifications) only after the DB.Transaction returns nil
(success).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/user.go`:
- Around line 343-362: The BindAffCode endpoint (function BindAffCode) lacks
abuse controls; update the router to attach the CriticalRateLimit middleware so
authenticated users cannot brute-force 4-char affiliate codes — e.g., change the
route registration for "POST /bind_aff" to use middleware.CriticalRateLimit()
(selfRoute.POST("/bind_aff", middleware.CriticalRateLimit(),
controller.BindAffCode)) and ensure the middleware is imported/available in the
router setup.

---

Duplicate comments:
In `@model/user.go`:
- Around line 1066-1085: The current transaction only updates inviter_id via
DB.Transaction(tx ...) while calling IncreaseUserQuota(userId, ...),
inviteUser(inviterId) and RecordLog(...) outside the transaction (and swallowing
their errors), which can deadlock or leave inviter_id committed while
quota/counters fail; fix by performing quota and inviter counter mutations
inside the same transaction using the provided tx (i.e. replace
IncreaseUserQuota and inviteUser calls with transactional equivalents or pass tx
into those functions so they use tx for their read-modify-write operations) and
propagate/return any errors from those calls so the transaction can roll back on
failure; keep non-database side-effects (cache updates,
RecordLog/logging/notifications) only after the DB.Transaction returns nil
(success).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4185764-91d7-4172-a6b2-f9b2de95b3cb

📥 Commits

Reviewing files that changed from the base of the PR and between cf95698 and f4ea858.

📒 Files selected for processing (5)
  • controller/misc.go
  • controller/user.go
  • model/user.go
  • web/src/components/topup/InvitationCard.jsx
  • web/src/components/topup/index.jsx

Comment thread controller/user.go
Comment on lines +343 to +362
func BindAffCode(c *gin.Context) {
id := c.GetInt("id")
var req struct {
AffCode string `json:"aff_code"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.ApiErrorMsg(c, "邀请码不能为空")
return
}
req.AffCode = strings.TrimSpace(req.AffCode)
if req.AffCode == "" {
common.ApiErrorMsg(c, "邀请码不能为空")
return
}
if err := model.BindInviterByAffCode(id, req.AffCode); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, nil)
}

ghost Mar 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This endpoint needs abuse controls before it goes live.

The new bind flow is wired on an authenticated route without middleware.CriticalRateLimit(), while affiliate codes are still only 4 characters. Since a successful bind grants invitee quota, a logged-in user can script random attempts until one valid code hits and claim the reward. At minimum, this route should be rate-limited like the registration flow.

Suggested router hardening
selfRoute.POST("/bind_aff", middleware.CriticalRateLimit(), controller.BindAffCode)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/user.go` around lines 343 - 362, The BindAffCode endpoint
(function BindAffCode) lacks abuse controls; update the router to attach the
CriticalRateLimit middleware so authenticated users cannot brute-force 4-char
affiliate codes — e.g., change the route registration for "POST /bind_aff" to
use middleware.CriticalRateLimit() (selfRoute.POST("/bind_aff",
middleware.CriticalRateLimit(), controller.BindAffCode)) and ensure the
middleware is imported/available in the router setup.

- Use tx for all reward writes inside the transaction so invitee quota
  and inviter stats (aff_count/aff_quota/aff_history_quota) are updated
  atomically via SQL expressions, removing the read-modify-write race
  in inviteUser()
- Add CriticalRateLimit() middleware to POST /bind_aff to prevent
  brute-force enumeration of 4-char affiliate codes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model/user.go`:
- Around line 1084-1098: Move the non-transactional RecordLog calls out of the
transactional closure and only write logs after the tx commits; specifically,
remove or postpone the RecordLog(inviterId, LogTypeSystem, ...) and
RecordLog(userId, ...) calls from inside the tx flow and call them after a
successful tx.Commit. Also ensure the inviter update checks the update result's
RowsAffected: when executing tx.Model(&User{}).Where("id = ?",
inviterId).Updates(...), inspect the returned result.RowsAffected and return an
error (or handle it) if 0 to avoid silently skipping inviter credit when no row
was matched. Ensure these changes reference the existing tx update block and the
RecordLog and inviterId symbols so logs are only written on committed success
and updates verify RowsAffected.
- Around line 1079-1083: The direct DB update using tx.Model(&User{}).Where("id
= ?", userId).Update("quota", gorm.Expr("quota + ?", common.QuotaForInvitee))
bypasses the cache invalidation/refresh that IncreaseUserQuota performs and can
leave the invitee's Redis/cache stale; replace this direct update with a call to
IncreaseUserQuota (pass the current tx or context and userId and
common.QuotaForInvitee) or, if IncreaseUserQuota cannot be used, perform the DB
update then explicitly invalidate/refresh the user's cache via the same helper
used elsewhere (e.g., RefreshUserCache/InvalidateUserCache) so the
in-memory/Redis view is immediately consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31f1faff-ad70-46d2-af01-06def5209a01

📥 Commits

Reviewing files that changed from the base of the PR and between f4ea858 and d66438e.

📒 Files selected for processing (2)
  • model/user.go
  • router/api-router.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/api-router.go

Comment thread model/user.go
Comment thread model/user.go Outdated
- Move RecordLog calls outside the transaction closure so rollbacks
  cannot produce orphan log entries; use boolean flags to track what
  was rewarded and write logs only on successful commit
- Check RowsAffected == 1 on the inviter SQL UPDATE so a missing inviter
  surfaces as an error instead of silently skipping the credit
- Refresh invitee quota Redis cache via cacheIncrUserQuota after commit
  so the user sees the updated balance immediately
- Invalidate cache for both parties via invalidateUserCache post-commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.