Skip to content

feat(checkin): add check-in functionality - #2565

Merged
Calcium-Ion merged 1 commit into
mainfrom
feat/check-in
Jan 2, 2026
Merged

feat(checkin): add check-in functionality#2565
Calcium-Ion merged 1 commit into
mainfrom
feat/check-in

Conversation

@Calcium-Ion

@Calcium-Ion Calcium-Ion commented Jan 2, 2026

Copy link
Copy Markdown
Member

#1227
#2529

Summary by CodeRabbit

Release Notes

  • New Features
    • Daily check-in system with randomized quota rewards for user accounts
    • Check-in calendar view displaying sign-in history, monthly statistics, and total earned quotas
    • Administrator settings to enable/disable check-in feature and configure minimum/maximum reward quotas
    • Multi-language support added for all check-in functionality

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces a daily check-in feature enabling users to perform daily sign-ins and receive random quota rewards. The implementation spans backend services (data models, HTTP handlers, configuration), frontend components (calendar UI, personal settings, admin configuration), and multilingual localization across six languages.

Changes

Cohort / File(s) Summary
Backend – Core Check-in Model
model/checkin.go, model/main.go
Introduces Checkin and CheckinRecord types with ORM mapping. Implements UserCheckin with database-specific transaction handling (SQLite non-transactional, MySQL/PostgreSQL transactional), duplicate-check prevention, random quota calculation, and post-transaction cache updates. Adds helper functions for quota retrieval and monthly stats aggregation. Database migration updated to include Checkin table.
Backend – API Layer
controller/checkin.go, router/api-router.go, controller/misc.go
Adds GetCheckinStatus and DoCheckin HTTP handlers with feature-enabled checks and consistent JSON error responses. Registers GET/POST /checkin routes. Updates GetStatus endpoint to include checkin_enabled flag from settings.
Backend – Settings Configuration
setting/operation_setting/checkin_setting.go
Defines CheckinSetting config type with Enabled, MinQuota, MaxQuota fields. Exports accessors GetCheckinSetting, IsCheckinEnabled, and GetCheckinQuotaRange. Defaults: enabled=false, min=1000, max=10000.
Frontend – User Check-in Calendar
web/src/components/settings/personal/cards/CheckinCalendar.jsx, web/src/components/settings/PersonalSetting.jsx
New CheckinCalendar component displays collapsible card with KPI tiles (total/monthly check-ins, quota earned) and interactive calendar showing sign-in days with tooltips. Fetches status via GET /api/user/checkin?month=, posts check-in action via POST /api/user/checkin, and handles success/error notifications. Conditionally renders based on checkin_enabled flag.
Frontend – Admin Settings
web/src/pages/Setting/Operation/SettingsCheckin.jsx, web/src/components/settings/OperationSetting.jsx
New SettingsCheckin admin form with toggle for enabled state and numeric inputs for min/max quota bounds. Computes field-level changes and issues PUT requests for each modified key. Integrated into OperationSetting UI with state initialization from props.
Localization
web/src/i18n/locales/{en,fr,ja,ru,vi,zh}.json
Adds 20+ translation keys for check-in feature across all supported languages: daily check-in prompts, status messages, reward descriptions, quota labels, and settings text. Minor syntax adjustment to existing "分组渠道" key in en.json.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as User
    participant Ctrl as Controller
    participant Model as Model Layer
    participant DB as Database
    participant Cache as Cache/Logger
    
    rect rgb(220, 240, 250)
    note over User,Cache: DoCheckin Flow
    User->>Ctrl: POST /checkin
    activate Ctrl
    Ctrl->>Ctrl: Check if enabled
    Ctrl->>Ctrl: Extract user ID
    
    Ctrl->>Model: UserCheckin(userId)
    activate Model
    Model->>Model: HasCheckedInToday()
    alt Already checked in
        Model-->>Ctrl: Error: Duplicate
        Ctrl-->>User: {"success": false}
    else Not checked in
        Model->>Model: Calculate random quota
        
        alt SQLite
            Model->>DB: Create checkin record
            DB-->>Model: ✓ Created
            Model->>DB: Update user quota
            alt Quota update fails
                Model->>DB: Rollback (delete checkin)
                Model-->>Ctrl: Error
            else Success
                Model->>Cache: Async cache update
                Model-->>Ctrl: Checkin record
            end
        else MySQL/PostgreSQL
            Model->>DB: BEGIN TRANSACTION
            Model->>DB: Create checkin record
            Model->>DB: Update user quota
            Model->>DB: COMMIT
            DB-->>Model: ✓ Success
            Model->>Cache: Async cache update
            Model-->>Ctrl: Checkin record
        end
    end
    deactivate Model
    
    Ctrl->>Cache: LogQuota (system log)
    Ctrl-->>User: {"success": true, "quota_awarded": N}
    deactivate Ctrl
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #1232: Modifies controller/misc.go GetStatus implementation to add status flags alongside checkin_enabled addition in this PR, suggesting parallel endpoint enhancement efforts.
  • PR #1923: Updates controller/misc.go status payload structure with additional field additions (quota_display_type), indicating concurrent status endpoint extensions requiring coordination.

Suggested reviewers

  • seefs001

🐰 Hoppy hop, a new check-in day!
Calendar marks your path, quota gained along the way,
Transaction safe, cache swift and bright,
Users sign each morn for rewards so right! ✨📅

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'feat(checkin): add check-in functionality' accurately reflects the main changes in the pull request, which introduce a complete check-in system across backend and frontend components.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitai coderabbitai Bot 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: 10

🧹 Nitpick comments (5)
setting/operation_setting/checkin_setting.go (2)

6-17: Consider adding validation for MinQuota and MaxQuota relationship.

Currently, there's no validation ensuring that MinQuota <= MaxQuota. If an administrator sets MaxQuota to a value less than MinQuota, it could cause unexpected behavior in the quota calculation logic.

Consider adding a validation method or enforcing this constraint when the settings are updated via the API.

🔎 Suggested validation approach

Add a validation method to the CheckinSetting struct:

// Validate checks if the setting values are valid
func (s *CheckinSetting) Validate() error {
    if s.Enabled && s.MaxQuota < s.MinQuota {
        return errors.New("MaxQuota must be greater than or equal to MinQuota")
    }
    return nil
}

Then call this validation when settings are updated through the API endpoint.


24-37: Consider returning a copy instead of pointer to prevent external mutations.

The GetCheckinSetting() function returns a pointer to the package-level checkinSetting variable, which could allow external code to modify it directly. While this pattern appears consistent with the codebase, returning a copy would prevent accidental mutations.

🔎 Alternative implementation
// GetCheckinSetting 获取签到配置副本
func GetCheckinSetting() CheckinSetting {
	return checkinSetting
}

This returns a copy instead of a pointer, preventing external mutations.

web/src/pages/Setting/Operation/SettingsCheckin.jsx (1)

83-93: Add null check for form reference before calling setValues.

The useEffect hook calls refForm.current.setValues() without checking if the form ref has been initialized. While unlikely in practice, this could cause a runtime error if the form hasn't mounted yet.

🔎 Suggested fix
 useEffect(() => {
   const currentInputs = {};
   for (let key in props.options) {
     if (Object.keys(inputs).includes(key)) {
       currentInputs[key] = props.options[key];
     }
   }
   setInputs(currentInputs);
   setInputsRow(structuredClone(currentInputs));
-  refForm.current.setValues(currentInputs);
+  if (refForm.current) {
+    refForm.current.setValues(currentInputs);
+  }
 }, [props.options]);
web/src/components/settings/personal/cards/CheckinCalendar.jsx (2)

43-52: Remove unused enabled field from checkinData state

checkinData includes an enabled flag that is never read; effective enablement is driven by status?.checkin_enabled instead.

To reduce noise and potential confusion, either wire this flag into the UI or drop it from the state shape.


138-172: Use the Date object directly from dateGridRender callback

The Semi UI Calendar's dateGridRender callback receives both dateString and a Date object as arguments (dateString: string, date: Date). The current code unnecessarily reconstructs the Date via new Date(dateString). Use the provided date parameter directly to simplify logic and avoid format/locale edge cases:

Suggested refactor
-  const dateRender = (dateString) => {
-    const date = new Date(dateString);
+  const dateRender = (_, date) => {
-    if (isNaN(date.getTime())) {
-      return null;
-    }
     // 使用本地时间格式化,避免时区问题
     const year = date.getFullYear();
     const month = String(date.getMonth() + 1).padStart(2, '0');
     const day = String(date.getDate()).padStart(2, '0');
     const formattedDate = `${year}-${month}-${day}`; // YYYY-MM-DD
     const quotaAwarded = checkinRecordsMap[formattedDate];
     const isCheckedIn = quotaAwarded !== undefined;

     if (isCheckedIn) {
       return (
         <Tooltip
           content={`${t('获得')} ${renderQuota(quotaAwarded)}`}
           position='top'
         >
           <div className='absolute inset-0 flex flex-col items-center justify-center cursor-pointer'>
             <div className='w-6 h-6 rounded-full bg-green-500 flex items-center justify-center mb-0.5 shadow-sm'>
               <Check size={14} className='text-white' strokeWidth={3} />
             </div>
             <div className='text-[10px] font-medium text-green-600 dark:text-green-400 leading-none'>
               {renderQuota(quotaAwarded)}
             </div>
           </div>
         </Tooltip>
       );
     }
     return null;
   };
@@
             <Calendar
               mode='month'
               onChange={handleMonthChange}
-              dateGridRender={(dateString, date) => dateRender(dateString)}
+              dateGridRender={dateRender}
             />

Also remove the isNaN(date.getTime()) check since the Date object from Calendar is guaranteed to be valid.

Also applies to: 298-302

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a195e88 and 8abfbe3.

📒 Files selected for processing (16)
  • controller/checkin.go
  • controller/misc.go
  • model/checkin.go
  • model/main.go
  • router/api-router.go
  • setting/operation_setting/checkin_setting.go
  • web/src/components/settings/OperationSetting.jsx
  • web/src/components/settings/PersonalSetting.jsx
  • web/src/components/settings/personal/cards/CheckinCalendar.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.json
  • web/src/pages/Setting/Operation/SettingsCheckin.jsx
🧰 Additional context used
🧬 Code graph analysis (9)
controller/misc.go (1)
setting/operation_setting/checkin_setting.go (1)
  • GetCheckinSetting (25-27)
web/src/pages/Setting/Operation/SettingsCheckin.jsx (7)
web/src/components/settings/PersonalSetting.jsx (2)
  • loading (70-70)
  • inputs (53-61)
web/src/components/settings/OperationSetting.jsx (2)
  • loading (80-80)
  • inputs (33-78)
web/src/components/settings/personal/cards/CheckinCalendar.jsx (1)
  • loading (41-41)
web/src/helpers/render.jsx (2)
  • value (1010-1010)
  • key (460-460)
web/src/components/setup/SetupWizard.jsx (1)
  • onSubmit (166-227)
web/src/helpers/utils.jsx (2)
  • compareObjects (306-323)
  • showError (122-151)
web/src/components/table/tokens/TokensColumnDefs.jsx (1)
  • item (326-326)
controller/checkin.go (5)
setting/operation_setting/checkin_setting.go (1)
  • GetCheckinSetting (25-27)
common/gin.go (1)
  • ApiErrorMsg (148-153)
model/checkin.go (2)
  • GetUserCheckinStats (144-179)
  • UserCheckin (55-92)
model/log.go (2)
  • RecordLog (81-97)
  • LogTypeSystem (48-48)
logger/logger.go (1)
  • LogQuota (99-124)
router/api-router.go (1)
controller/checkin.go (2)
  • GetCheckinStatus (16-44)
  • DoCheckin (47-72)
web/src/components/settings/personal/cards/CheckinCalendar.jsx (3)
web/src/helpers/render.jsx (1)
  • renderQuota (1001-1042)
web/src/helpers/secureApiCall.js (1)
  • data (55-55)
web/src/helpers/utils.jsx (2)
  • showError (122-151)
  • showSuccess (157-159)
model/main.go (10)
model/channel.go (1)
  • Channel (21-58)
model/token.go (1)
  • Token (13-31)
model/user.go (1)
  • User (20-50)
model/passkey.go (1)
  • PasskeyCredential (23-42)
model/ability.go (1)
  • Ability (16-24)
model/log.go (1)
  • Log (20-40)
model/vendor_meta.go (1)
  • Vendor (15-24)
model/prefill_group.go (1)
  • PrefillGroup (76-85)
model/twofa.go (2)
  • TwoFA (16-27)
  • TwoFABackupCode (30-38)
model/checkin.go (2)
  • Checkin (14-20)
  • Checkin (28-30)
setting/operation_setting/checkin_setting.go (1)
setting/config/config.go (1)
  • GlobalConfig (19-19)
web/src/components/settings/OperationSetting.jsx (1)
web/src/pages/Setting/Operation/SettingsCheckin.jsx (2)
  • SettingsCheckin (31-152)
  • inputs (34-38)
model/checkin.go (4)
model/main.go (1)
  • DB (64-64)
setting/operation_setting/checkin_setting.go (1)
  • GetCheckinSetting (25-27)
common/database.go (1)
  • UsingSQLite (9-9)
model/user.go (1)
  • IncreaseUserQuota (766-781)
🔇 Additional comments (10)
web/src/i18n/locales/vi.json (1)

2747-2770: LGTM - Vietnamese localization for check-in feature added.

The Vietnamese translations for the daily check-in feature have been properly added. The translation keys cover all necessary UI elements including:

  • Daily check-in status and actions
  • Reward information and statistics
  • Settings for the check-in feature
controller/misc.go (1)

117-117: LGTM - Check-in feature flag properly exposed to frontend.

The addition of checkin_enabled to the status response correctly follows the existing pattern for feature flags. This allows the frontend to conditionally display check-in UI elements based on the admin's configuration.

router/api-router.go (1)

96-99: Add input validation for the month parameter in GET /checkin.

The POST /checkin endpoint is already well-protected with a daily check-in constraint that prevents multiple check-ins per user per day, enforced both at the application level and via database unique constraint on (user_id, checkin_date).

However, the GET /checkin endpoint accepts a month parameter without format validation. Consider validating that the month parameter matches the expected YYYY-MM format before querying the database, even though the current implementation won't fail with invalid input.

web/src/components/settings/PersonalSetting.jsx (1)

42-42: Check‑in calendar integration in personal settings looks consistent

The new CheckinCalendar import and conditional render behind status?.checkin_enabled are wired cleanly and align with the status payload. Passing t and status keeps it in line with the rest of the settings components.

Also applies to: 451-456

web/src/i18n/locales/fr.json (1)

2237-2260: LGTM! French translations for daily check-in feature added correctly.

The new translations are properly formatted and follow the existing pattern. All necessary keys for the check-in feature UI are present and grammatically correct.

model/main.go (2)

251-271: LGTM! Checkin model added to database migration.

The &Checkin{} model has been correctly added to the AutoMigrate call, following the same pattern as other models in the codebase.


278-305: LGTM! Checkin model added to fast migration.

The Checkin model is correctly added to the concurrent migration slice and will be processed in parallel with other models. The implementation follows the established pattern.

web/src/pages/Setting/Operation/SettingsCheckin.jsx (1)

121-140: LGTM! Form fields properly disabled when feature is off.

The minimum and maximum quota input fields are correctly disabled when the check-in feature is not enabled (lines 128, 138). This prevents users from configuring quota values when the feature isn't active.

controller/checkin.go (1)

47-72: LGTM! Check-in handler properly implements the feature.

The DoCheckin handler correctly:

  • Verifies the feature is enabled
  • Retrieves the authenticated user ID from context
  • Delegates check-in logic to the model layer
  • Records the system log with properly formatted quota
  • Returns a consistent JSON response structure
model/checkin.go (1)

70-82: Validate and clamp quota rewards to non-negative values

quotaAwarded is derived directly from setting.MinQuota/MaxQuota without validation, then used to update user quota. In the MySQL/PostgreSQL transactional path (lines 104-105), the quota update uses raw gorm.Expr("quota + ?", quotaAwarded), bypassing the non-negative check in IncreaseUserQuota. If settings are misconfigured with negative values, this would silently reduce user quota below zero, with no database constraint to prevent it.

To harden against admin misconfiguration:

  • Enforce MinQuota >= 0 and MaxQuota >= MinQuota in GetCheckinSetting() or validate in UserCheckin
  • Clamp quotaAwarded to a non-negative value before persisting and updating quota

This prevents silent quota deduction from bad config.

Likely an incorrect or invalid review comment.

Comment thread controller/checkin.go
Comment on lines +16 to +44
func GetCheckinStatus(c *gin.Context) {
setting := operation_setting.GetCheckinSetting()
if !setting.Enabled {
common.ApiErrorMsg(c, "签到功能未启用")
return
}
userId := c.GetInt("id")
// 获取月份参数,默认为当前月份
month := c.DefaultQuery("month", time.Now().Format("2006-01"))

stats, err := model.GetUserCheckinStats(userId, month)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"enabled": setting.Enabled,
"min_quota": setting.MinQuota,
"max_quota": setting.MaxQuota,
"stats": stats,
},
})
}

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

🧩 Analysis chain

🏁 Script executed:

# First, find the checkin.go controller file
find . -name "checkin.go" -type f

Repository: QuantumNous/new-api

Length of output: 105


🏁 Script executed:

# Search for GetUserCheckinStats function to check if it validates month
rg -A 15 "func GetUserCheckinStats" --type go

Repository: QuantumNous/new-api

Length of output: 802


🏁 Script executed:

# Also check if there's any validation of month parameter in the controller
rg -B 5 -A 10 "month.*:=.*DefaultQuery" --type go

Repository: QuantumNous/new-api

Length of output: 741


🏁 Script executed:

# Check GetUserCheckinRecords function to see if it validates date format
rg -A 20 "func GetUserCheckinRecords" --type go

Repository: QuantumNous/new-api

Length of output: 1048


🏁 Script executed:

# Check the complete GetUserCheckinStats function to see full implementation
rg -A 30 "func GetUserCheckinStats" --type go model/checkin.go

Repository: QuantumNous/new-api

Length of output: 991


Add validation for month parameter format.

The month query parameter is obtained via c.DefaultQuery("month", ...) but lacks validation. The model layer directly concatenates it with "-01" and "-31" to form date strings for database queries without checking format. Invalid formats like "2024-13" or "invalid" would be passed to the database, potentially causing errors or unexpected behavior.

🔎 Suggested validation

Add format validation before calling the model function:

 func GetCheckinStatus(c *gin.Context) {
 	setting := operation_setting.GetCheckinSetting()
 	if !setting.Enabled {
 		common.ApiErrorMsg(c, "签到功能未启用")
 		return
 	}
 	userId := c.GetInt("id")
 	// 获取月份参数,默认为当前月份
 	month := c.DefaultQuery("month", time.Now().Format("2006-01"))
+	
+	// Validate month format
+	_, err := time.Parse("2006-01", month)
+	if err != nil {
+		common.ApiErrorMsg(c, "月份格式无效,请使用 YYYY-MM 格式")
+		return
+	}

 	stats, err := model.GetUserCheckinStats(userId, month)
📝 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
func GetCheckinStatus(c *gin.Context) {
setting := operation_setting.GetCheckinSetting()
if !setting.Enabled {
common.ApiErrorMsg(c, "签到功能未启用")
return
}
userId := c.GetInt("id")
// 获取月份参数,默认为当前月份
month := c.DefaultQuery("month", time.Now().Format("2006-01"))
stats, err := model.GetUserCheckinStats(userId, month)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"enabled": setting.Enabled,
"min_quota": setting.MinQuota,
"max_quota": setting.MaxQuota,
"stats": stats,
},
})
}
func GetCheckinStatus(c *gin.Context) {
setting := operation_setting.GetCheckinSetting()
if !setting.Enabled {
common.ApiErrorMsg(c, "签到功能未启用")
return
}
userId := c.GetInt("id")
// 获取月份参数,默认为当前月份
month := c.DefaultQuery("month", time.Now().Format("2006-01"))
// Validate month format
_, err := time.Parse("2006-01", month)
if err != nil {
common.ApiErrorMsg(c, "月份格式无效,请使用 YYYY-MM 格式")
return
}
stats, err := model.GetUserCheckinStats(userId, month)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"enabled": setting.Enabled,
"min_quota": setting.MinQuota,
"max_quota": setting.MaxQuota,
"stats": stats,
},
})
}
🤖 Prompt for AI Agents
In controller/checkin.go around lines 16 to 44, the month query param is taken
via c.DefaultQuery("month", ...) and passed to model.GetUserCheckinStats without
validation; add validation to ensure the month is in "YYYY-MM" format (e.g. use
time.Parse("2006-01", month) or a strict regex) and return a 400 JSON error if
parsing fails; only call model.GetUserCheckinStats when the month is valid
(optionally normalize the parsed time back to month string) so the model never
receives malformed values like "2024-13" or "invalid".

Comment thread model/checkin.go
Comment on lines +95 to +121
func userCheckinWithTransaction(checkin *Checkin, userId int, quotaAwarded int) (*Checkin, error) {
err := DB.Transaction(func(tx *gorm.DB) error {
// 步骤1: 创建签到记录
// 数据库有唯一约束 (user_id, checkin_date),可以防止并发重复签到
if err := tx.Create(checkin).Error; err != nil {
return errors.New("签到失败,请稍后重试")
}

// 步骤2: 在事务中增加用户额度
if err := tx.Model(&User{}).Where("id = ?", userId).
Update("quota", gorm.Expr("quota + ?", quotaAwarded)).Error; err != nil {
return errors.New("签到失败:更新额度出错")
}

return nil
})

if err != nil {
return nil, err
}

// 事务成功后,异步更新缓存
go func() {
_ = cacheIncrUserQuota(userId, int64(quotaAwarded))
}()

return checkin, nil

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the model/user.go file for IncreaseUserQuota
fd -t f "user.go" -x wc -l {}

Repository: QuantumNous/new-api

Length of output: 137


🏁 Script executed:

# Search for IncreaseUserQuota function
rg "func.*IncreaseUserQuota" --context 15

Repository: QuantumNous/new-api

Length of output: 1224


🏁 Script executed:

# Search for gopool usage in the codebase
rg "gopool\.Go" --context 3

Repository: QuantumNous/new-api

Length of output: 10948


🏁 Script executed:

# Check what gopool is and where it's imported from
rg "import.*gopool|from.*gopool" --context 2

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Look for all cacheIncrUserQuota usages
rg "cacheIncrUserQuota" --context 2

Repository: QuantumNous/new-api

Length of output: 920


🏁 Script executed:

# Verify the checkin.go file exists and check surrounding context
fd -t f "checkin.go" -x wc -l {}

Repository: QuantumNous/new-api

Length of output: 112


🌐 Web query:

Go goroutine worker pool pattern concurrency limits vs spawning raw goroutines best practices

💡 Result:

Short guidance — when to use a worker-pool vs spawning raw goroutines, and best practices.

When to prefer a worker pool

  • Unbounded/high-volume input or long-running jobs where spawning one goroutine per task could exhaust memory, file descriptors or backend capacity. Use a pool to apply backpressure and keep resource use predictable. [1][2]
  • When you need predictable throughput or to limit concurrency to avoid overwhelming external systems (APIs, DBs, disks). [1][2]

When raw goroutines are fine

  • Low, bounded or latency‑critical workloads where queueing would add unacceptable delay. For simple short-lived tasks and light load, go func() is simplest and usually fine. [1][2]

Sizing and tuning

  • CPU‑bound work: set workers ≈ number of logical CPUs (runtime.NumCPU() / GOMAXPROCS). Slightly fewer can reduce contention. [1]
  • I/O‑bound work: a larger pool is acceptable because goroutines block on I/O; tune by benchmarking. [1][2]

Best practices and patterns

  • Use a bounded jobs channel to provide backpressure rather than an unbounded queue. [1][2]
  • Propagate cancellation and timeouts with context.Context; stop workers cleanly (select on ctx.Done()). [2][3]
  • Use sync.WaitGroup (or worker lifecycle management) to avoid leaks and to know when workers exit. [2][3]
  • Fail fast / reject when queue is full if you must protect resources (return error instead of blocking). [1]
  • Monitor goroutine counts, latency, memory and FD usage; benchmark different pool sizes under realistic load. [1][2]

When not to use a pool

  • Very small fixed workloads where complexity outweighs benefit.
  • When strict per-task isolation is required and queuing delays are unacceptable. [1]

Quick rule of thumb

  • If input is unbounded or can spike → use a bounded worker pool with backpressure. If input is small and latency-critical → spawn goroutines directly. Always benchmark and add cancellation/observability. [1][2][3]

Sources: practical guides and pattern writeups on Go worker pools and concurrency. [1][goperf.dev Goroutine Worker Pools] [2][opcito Practical concurrency patterns in Go] [3][advanced patterns / examples].


Use the same async pattern as IncreaseUserQuota for cache updates

After a successful transaction, userCheckinWithTransaction spawns a bare goroutine:

go func() {
    _ = cacheIncrUserQuota(userId, int64(quotaAwarded))
}()

The codebase consistently uses gopool.Go for async operations, including in IncreaseUserQuota (model/user.go), which dispatches cache updates via the pool with error logging:

gopool.Go(func() {
    err := cacheIncrUserQuota(id, int64(quota))
    if err != nil {
        common.SysLog("failed to increase user quota: " + err.Error())
    }
})

To align with the standard pattern and ensure cache failures are logged, use:

gopool.Go(func() {
    if err := cacheIncrUserQuota(userId, int64(quotaAwarded)); err != nil {
        common.SysLog("failed to increase user quota in cache: " + err.Error())
    }
})

This ensures consistent resource management and centralized error handling across the codebase.

🤖 Prompt for AI Agents
In model/checkin.go around lines 95 to 121, replace the bare goroutine that
calls cacheIncrUserQuota with the project's async pattern: use gopool.Go to
dispatch the cache increment and capture/log any error with common.SysLog (e.g.,
call cacheIncrUserQuota(userId, int64(quotaAwarded)) inside gopool.Go and if it
returns an error, call common.SysLog with a clear message like "failed to
increase user quota in cache: "+err.Error()); this keeps async work pooled and
ensures cache failures are logged.

Comment thread model/checkin.go
Comment on lines +143 to +178
// GetUserCheckinStats 获取用户签到统计信息
func GetUserCheckinStats(userId int, month string) (map[string]interface{}, error) {
// 获取指定月份的所有签到记录
startDate := month + "-01"
endDate := month + "-31"

records, err := GetUserCheckinRecords(userId, startDate, endDate)
if err != nil {
return nil, err
}

// 转换为不包含敏感字段的记录
checkinRecords := make([]CheckinRecord, len(records))
for i, r := range records {
checkinRecords[i] = CheckinRecord{
CheckinDate: r.CheckinDate,
QuotaAwarded: r.QuotaAwarded,
}
}

// 检查今天是否已签到
hasCheckedToday, _ := HasCheckedInToday(userId)

// 获取用户所有时间的签到统计
var totalCheckins int64
var totalQuota int64
DB.Model(&Checkin{}).Where("user_id = ?", userId).Count(&totalCheckins)
DB.Model(&Checkin{}).Where("user_id = ?", userId).Select("COALESCE(SUM(quota_awarded), 0)").Scan(&totalQuota)

return map[string]interface{}{
"total_quota": totalQuota, // 所有时间累计获得的额度
"total_checkins": totalCheckins, // 所有时间累计签到次数
"checkin_count": len(records), // 本月签到次数
"checked_in_today": hasCheckedToday, // 今天是否已签到
"records": checkinRecords, // 本月签到记录详情(不含id和user_id)
}, nil

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

Propagate DB errors in GetUserCheckinStats instead of silently ignoring them

In GetUserCheckinStats:

  • HasCheckedInToday(userId) is called and its error is ignored.
  • The Count and SUM(quota_awarded) queries are executed without checking Error.

If any of these queries fail (transient DB issue, migration problem), you still return a stats map with zeroed counts/quotas and checked_in_today possibly wrong, which is misleading to both UI and callers.

Recommend:

  • Checking and propagating errors for the total count and sum queries.
  • Either propagating HasCheckedInToday’s error or at least logging it and defaulting to false.

For example:

- hasCheckedToday, _ := HasCheckedInToday(userId)
+ hasCheckedToday, err := HasCheckedInToday(userId)
+ if err != nil {
+     return nil, err
+ }
@@
- DB.Model(&Checkin{}).Where("user_id = ?", userId).Count(&totalCheckins)
- DB.Model(&Checkin{}).Where("user_id = ?", userId).Select("COALESCE(SUM(quota_awarded), 0)").Scan(&totalQuota)
+ if err := DB.Model(&Checkin{}).Where("user_id = ?", userId).Count(&totalCheckins).Error; err != nil {
+     return nil, err
+ }
+ if err := DB.Model(&Checkin{}).Where("user_id = ?", userId).
+     Select("COALESCE(SUM(quota_awarded), 0)").Scan(&totalQuota).Error; err != nil {
+     return nil, err
+ }

This keeps the API contract clear: either you get correct stats, or an explicit error.

import SettingsLog from '../../pages/Setting/Operation/SettingsLog';
import SettingsMonitoring from '../../pages/Setting/Operation/SettingsMonitoring';
import SettingsCreditLimit from '../../pages/Setting/Operation/SettingsCreditLimit';
import SettingsCheckin from '../../pages/Setting/Operation/SettingsCheckin';

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate SettingsCheckin and examine its implementation
find . -name "SettingsCheckin.jsx" -o -name "SettingsCheckin.js" | head -5

Repository: QuantumNous/new-api

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Also examine OperationSetting.jsx to see the full context of getOptions and inputs
wc -l web/src/components/settings/OperationSetting.jsx

Repository: QuantumNous/new-api

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Read the OperationSetting.jsx file to see the full context
cat -n web/src/components/settings/OperationSetting.jsx

Repository: QuantumNous/new-api

Length of output: 6137


🏁 Script executed:

#!/bin/bash
# Read the SettingsCheckin.jsx file to examine how it handles the options
cat -n ./web/src/pages/Setting/Operation/SettingsCheckin.jsx

Repository: QuantumNous/new-api

Length of output: 5986


SettingsCheckin defaults are lost when API options are missing on fresh installs

The concern is valid. On a fresh system without existing checkin_setting.* keys in the database, getOptions() rebuilds inputs from only the keys returned by the API (lines 82–99 of OperationSetting.jsx), completely replacing the initial defaults. SettingsCheckin then receives props.options without these keys, and its useEffect (lines 83–93 of SettingsCheckin.jsx) filters currentInputs to only include keys present in props.options, dropping the defaults again. The result is that 'checkin_setting.min_quota' and 'checkin_setting.max_quota' become undefined in the form until a user saves a value.

To fix this, either:

  • Merge missing keys with defaults in SettingsCheckin's useEffect: change const currentInputs = {}; to const currentInputs = { ...inputs }; before the loop
  • Or initialize newInputs in getOptions() from the existing inputs state to preserve defaults across API calls
🤖 Prompt for AI Agents
In web/src/components/settings/OperationSetting.jsx (import at line 29) and
web/src/pages/Setting/Operation/SettingsCheckin.jsx (useEffect around lines
83–93), props.options from getOptions() can omit keys on fresh installs causing
defaults to be dropped; to fix, update SettingsCheckin's useEffect to initialize
currentInputs by cloning the full inputs defaults (i.e., set currentInputs = {
...inputs } before filtering) so missing API-returned keys are preserved, then
merge/overwrite those with values from props.options as currently done.

Comment on lines +211 to +223
<Button
type='primary'
theme='solid'
icon={<Gift size={16} />}
onClick={doCheckin}
loading={checkinLoading}
disabled={checkinData.stats?.checked_in_today}
className='!bg-green-600 hover:!bg-green-700'
>
{checkinData.stats?.checked_in_today
? t('今日已签到')
: t('立即签到')}
</Button>

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 | 🟡 Minor

Also disable the check-in button while a request is in flight

The button is disabled only when checked_in_today is true. During an active POST /api/user/checkin call (checkinLoading), it’s still clickable unless the UI library implicitly blocks it.

To avoid accidental double submissions and relying on library behavior, explicitly disable on loading as well:

Suggested change
-        <Button
+        <Button
           type='primary'
           theme='solid'
           icon={<Gift size={16} />}
           onClick={doCheckin}
           loading={checkinLoading}
-          disabled={checkinData.stats?.checked_in_today}
+          disabled={checkinData.stats?.checked_in_today || checkinLoading}
           className='!bg-green-600 hover:!bg-green-700'
         >
📝 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
<Button
type='primary'
theme='solid'
icon={<Gift size={16} />}
onClick={doCheckin}
loading={checkinLoading}
disabled={checkinData.stats?.checked_in_today}
className='!bg-green-600 hover:!bg-green-700'
>
{checkinData.stats?.checked_in_today
? t('今日已签到')
: t('立即签到')}
</Button>
<Button
type='primary'
theme='solid'
icon={<Gift size={16} />}
onClick={doCheckin}
loading={checkinLoading}
disabled={checkinData.stats?.checked_in_today || checkinLoading}
className='!bg-green-600 hover:!bg-green-700'
>
{checkinData.stats?.checked_in_today
? t('今日已签到')
: t('立即签到')}
</Button>
🤖 Prompt for AI Agents
In web/src/components/settings/personal/cards/CheckinCalendar.jsx around lines
211 to 223, the check-in Button is only disabled when
checkinData.stats?.checked_in_today is true so it remains clickable while a
check-in request is in flight; update the Button props to also set disabled when
checkinLoading is true (e.g., disabled={checkinLoading ||
checkinData.stats?.checked_in_today}) so the button is explicitly disabled
during the POST call to prevent double submissions and accidental clicks.

Comment on lines +2188 to +2211
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "After enabling, when the current group channel fails, it will try the next group's channel in order",
"每日签到": "Daily Check-in",
"今日已签到,累计签到": "Checked in today, total check-ins",
"天": "days",
"每日签到可获得随机额度奖励": "Daily check-in rewards random quota",
"今日已签到": "Checked in today",
"立即签到": "Check in now",
"获取签到状态失败": "Failed to get check-in status",
"签到成功!获得": "Check-in successful! Received",
"签到失败": "Check-in failed",
"获得": "Received",
"累计签到": "Total check-ins",
"本月获得": "This month",
"累计获得": "Total received",
"签到奖励将直接添加到您的账户余额": "Check-in rewards will be directly added to your account balance",
"每日仅可签到一次,请勿重复签到": "Only one check-in per day, please do not check in repeatedly",
"签到设置": "Check-in Settings",
"签到功能允许用户每日签到获取随机额度奖励": "Check-in feature allows users to check in daily to receive random quota rewards",
"启用签到功能": "Enable check-in feature",
"签到最小额度": "Minimum check-in quota",
"签到奖励的最小额度": "Minimum quota for check-in rewards",
"签到最大额度": "Maximum check-in quota",
"签到奖励的最大额度": "Maximum quota for check-in rewards",
"保存签到设置": "Save check-in settings"

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

Resolve conflicting duplicate "天" translation in en.json

There are now two "天" keys in this file:

  • Earlier: "天": "day"
  • New (Line 2191): "天": "days"

Duplicate JSON keys are ambiguous; most parsers keep only one (typically the last), meaning you may unintentionally change all existing usages of "天" across the app to "days".

Please consolidate to a single "天" entry with the intended English form, and remove the duplicate. If different contexts need singular vs plural, use distinct keys instead of overloading "天".

🤖 Prompt for AI Agents
In web/src/i18n/locales/en.json around lines 2188 to 2211 there is a duplicate
"天" key (earlier mapped to "day" and at line ~2191 mapped to "days"), which
causes JSON parsers to keep only one and may unintentionally change usages;
remove the duplicate so there is only one "天" entry with the intended English
value, and if you need both singular and plural usages create distinct keys
(e.g., separate keys for singular vs plural or context-specific keys) and update
code references accordingly.

Comment on lines +2137 to +2159
"每日签到": "毎日のチェックイン",
"今日已签到,累计签到": "本日チェックイン済み、累計チェックイン",
"天": "日",
"每日签到可获得随机额度奖励": "毎日のチェックインでランダムなクォータ報酬を獲得できます",
"今日已签到": "本日チェックイン済み",
"立即签到": "今すぐチェックイン",
"获取签到状态失败": "チェックイン状態の取得に失敗しました",
"签到成功!获得": "チェックイン成功!獲得",
"签到失败": "チェックインに失敗しました",
"获得": "獲得",
"累计签到": "累計チェックイン",
"本月获得": "今月の獲得",
"累计获得": "累計獲得",
"签到奖励将直接添加到您的账户余额": "チェックイン報酬は直接アカウント残高に追加されます",
"每日仅可签到一次,请勿重复签到": "1日1回のみチェックイン可能です。重複チェックインはしないでください",
"签到设置": "チェックイン設定",
"签到功能允许用户每日签到获取随机额度奖励": "チェックイン機能により、ユーザーは毎日チェックインしてランダムなクォータ報酬を獲得できます",
"启用签到功能": "チェックイン機能を有効にする",
"签到最小额度": "チェックイン最小クォータ",
"签到奖励的最小额度": "チェックイン報酬の最小クォータ",
"签到最大额度": "チェックイン最大クォータ",
"签到奖励的最大额度": "チェックイン報酬の最大クォータ",
"保存签到设置": "チェックイン設定を保存"

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 | 🟡 Minor

Avoid duplicate "天" key in ja.json

"天" is already defined earlier in this file and is added again at Line 2139. Duplicate JSON keys reduce maintainability and can confuse tooling; only one value will actually be used.

Recommend keeping a single "天" entry and removing the duplicate. The new check‑in‑related keys themselves look consistent with the English locale.

🤖 Prompt for AI Agents
In web/src/i18n/locales/ja.json around lines 2137 to 2159 there is a duplicate
"天" key (the second occurrence at ~2139); remove this repeated entry so only the
original "天" definition earlier in the file remains, ensuring the rest of the
check-in translation block is left intact and the JSON contains no duplicate
keys (run a JSON/locale linter after the change).

Comment on lines +2247 to +2270
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку",
"每日签到": "Ежедневная регистрация",
"今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций",
"天": "дней",
"每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой",
"今日已签到": "Зарегистрирован сегодня",
"立即签到": "Зарегистрироваться сейчас",
"获取签到状态失败": "Не удалось получить статус регистрации",
"签到成功!获得": "Регистрация успешна! Получено",
"签到失败": "Регистрация не удалась",
"获得": "Получено",
"累计签到": "Всего регистраций",
"本月获得": "В этом месяце",
"累计获得": "Всего получено",
"签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета",
"每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно",
"签到设置": "Настройки регистрации",
"签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты",
"启用签到功能": "Включить функцию регистрации",
"签到最小额度": "Минимальная квота регистрации",
"签到奖励的最小额度": "Минимальная квота для наград за регистрацию",
"签到最大额度": "Максимальная квота регистрации",
"签到奖励的最大额度": "Максимальная квота для наград за регистрацию",
"保存签到设置": "Сохранить настройки регистрации"

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

Fix duplicate key and keep locale JSON valid

The new check‑in block redefines the key "天" (Line 2250), but "天" already exists earlier in this file with a different Russian value. Duplicate keys make the JSON formally invalid and can confuse tooling; at runtime, only the last value is kept, which may break other strings that rely on the original translation.

Recommend keeping a single "天" entry and, if you need a different grammatical form for the check‑in UI, introduce a more specific key (e.g., "签到天" or similar) rather than reusing "天".

Suggested minimal fix (remove duplicate)
@@
-    "天": "дней",
📝 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
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку",
"每日签到": "Ежедневная регистрация",
"今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций",
"天": "дней",
"每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой",
"今日已签到": "Зарегистрирован сегодня",
"立即签到": "Зарегистрироваться сейчас",
"获取签到状态失败": "Не удалось получить статус регистрации",
"签到成功!获得": "Регистрация успешна! Получено",
"签到失败": "Регистрация не удалась",
"获得": "Получено",
"累计签到": "Всего регистраций",
"本月获得": "В этом месяце",
"累计获得": "Всего получено",
"签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета",
"每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно",
"签到设置": "Настройки регистрации",
"签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты",
"启用签到功能": "Включить функцию регистрации",
"签到最小额度": "Минимальная квота регистрации",
"签到奖励的最小额度": "Минимальная квота для наград за регистрацию",
"签到最大额度": "Максимальная квота регистрации",
"签到奖励的最大额度": "Максимальная квота для наград за регистрацию",
"保存签到设置": "Сохранить настройки регистрации"
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку",
"每日签到": "Ежедневная регистрация",
"今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций",
"每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой",
"今日已签到": "Зарегистрирован сегодня",
"立即签到": "Зарегистрироваться сейчас",
"获取签到状态失败": "Не удалось получить статус регистрации",
"签到成功!获得": "Регистрация успешна! Получено",
"签到失败": "Регистрация не удалась",
"获得": "Получено",
"累计签到": "Всего регистраций",
"本月获得": "В этом месяце",
"累计获得": "Всего получено",
"签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета",
"每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно",
"签到设置": "Настройки регистрации",
"签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты",
"启用签到功能": "Включить функцию регистрации",
"签到最小额度": "Минимальная квота регистрации",
"签到奖励的最小额度": "Минимальная квота для наград за регистрацию",
"签到最大额度": "Максимальная квота регистрации",
"签到奖励的最大额度": "Максимальная квота для наград за регистрацию",
"保存签到设置": "Сохранить настройки регистрации"
🤖 Prompt for AI Agents
In web/src/i18n/locales/ru.json around lines 2247 to 2270 there is a duplicate
key "天" (redefined at ~2250) which makes the JSON ambiguous/invalid; remove the
duplicate entry or rename it to a specific key (e.g., "签到天" or "checkin_days")
and update any UI code that references the new key accordingly so only one "天"
key exists and the locale file remains valid JSON.

Comment on lines +2214 to +2237
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道",
"每日签到": "每日签到",
"今日已签到,累计签到": "今日已签到,累计签到",
"天": "天",
"每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励",
"今日已签到": "今日已签到",
"立即签到": "立即签到",
"获取签到状态失败": "获取签到状态失败",
"签到成功!获得": "签到成功!获得",
"签到失败": "签到失败",
"获得": "获得",
"累计签到": "累计签到",
"本月获得": "本月获得",
"累计获得": "累计获得",
"签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额",
"每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到",
"签到设置": "签到设置",
"签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励",
"启用签到功能": "启用签到功能",
"签到最小额度": "签到最小额度",
"签到奖励的最小额度": "签到奖励的最小额度",
"签到最大额度": "签到最大额度",
"签到奖励的最大额度": "签到奖励的最大额度",
"保存签到设置": "保存签到设置"

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

Remove duplicate key in zh locale to keep JSON well‑formed

The new check‑in strings look consistent, but this block re‑introduces the key "天" (Line 2217), which already exists earlier in the same translation object. Even though both map to "天", duplicate keys make the JSON formally invalid and can interfere with tools that validate or transform these locale files.

Recommend dropping the new "天" entry here and relying on the existing one.

Suggested minimal fix
@@
-    "天": "天",
📝 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
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道",
"每日签到": "每日签到",
"今日已签到,累计签到": "今日已签到,累计签到",
"天": "",
"每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励",
"今日已签到": "今日已签到",
"立即签到": "立即签到",
"获取签到状态失败": "获取签到状态失败",
"签到成功!获得": "签到成功!获得",
"签到失败": "签到失败",
"获得": "获得",
"累计签到": "累计签到",
"本月获得": "本月获得",
"累计获得": "累计获得",
"签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额",
"每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到",
"签到设置": "签到设置",
"签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励",
"启用签到功能": "启用签到功能",
"签到最小额度": "签到最小额度",
"签到奖励的最小额度": "签到奖励的最小额度",
"签到最大额度": "签到最大额度",
"签到奖励的最大额度": "签到奖励的最大额度",
"保存签到设置": "保存签到设置"
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道",
"每日签到": "每日签到",
"今日已签到,累计签到": "今日已签到,累计签到",
"每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励",
"今日已签到": "今日已签到",
"立即签到": "立即签到",
"获取签到状态失败": "获取签到状态失败",
"签到成功!获得": "签到成功!获得",
"签到失败": "签到失败",
"获得": "获得",
"累计签到": "累计签到",
"本月获得": "本月获得",
"累计获得": "累计获得",
"签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额",
"每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到",
"签到设置": "签到设置",
"签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励",
"启用签到功能": "启用签到功能",
"签到最小额度": "签到最小额度",
"签到奖励的最小额度": "签到奖励的最小额度",
"签到最大额度": "签到最大额度",
"签到奖励的最大额度": "签到奖励的最大额度",
"保存签到设置": "保存签到设置"
🤖 Prompt for AI Agents
In web/src/i18n/locales/zh.json around lines 2214 to 2237, there is a duplicate
key "天" at ~line 2217 which duplicates an earlier "天" entry in the same
translation object; remove this duplicate entry from this block so the file
contains only the original "天" key, ensure trailing commas remain valid and the
JSON parses correctly after removal.

Comment on lines +48 to +81
function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => {
let value = '';
if (typeof inputs[item.key] === 'boolean') {
value = String(inputs[item.key]);
} else {
value = String(inputs[item.key]);
}
return API.put('/api/option/', {
key: item.key,
value,
});
});
setLoading(true);
Promise.all(requestQueue)
.then((res) => {
if (requestQueue.length === 1) {
if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) {
if (res.includes(undefined))
return showError(t('部分保存失败,请重试'));
}
showSuccess(t('保存成功'));
props.refresh();
})
.catch(() => {
showError(t('保存失败,请重试'));
})
.finally(() => {
setLoading(false);
});
}

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.

🛠️ Refactor suggestion | 🟠 Major

Add validation to ensure minimum quota doesn't exceed maximum quota.

The onSubmit function doesn't validate that min_quota <= max_quota before submitting. Users could configure invalid settings where the minimum is greater than the maximum, which would cause issues in the backend quota calculation.

🔎 Suggested validation

Add validation before the compareObjects call:

 function onSubmit() {
+  // Validate min/max quota relationship
+  if (inputs['checkin_setting.enabled']) {
+    const minQuota = inputs['checkin_setting.min_quota'];
+    const maxQuota = inputs['checkin_setting.max_quota'];
+    if (minQuota > maxQuota) {
+      showError(t('签到最小额度不能大于最大额度'));
+      return;
+    }
+  }
+
   const updateArray = compareObjects(inputs, inputsRow);
   if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
📝 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
function onSubmit() {
const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => {
let value = '';
if (typeof inputs[item.key] === 'boolean') {
value = String(inputs[item.key]);
} else {
value = String(inputs[item.key]);
}
return API.put('/api/option/', {
key: item.key,
value,
});
});
setLoading(true);
Promise.all(requestQueue)
.then((res) => {
if (requestQueue.length === 1) {
if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) {
if (res.includes(undefined))
return showError(t('部分保存失败,请重试'));
}
showSuccess(t('保存成功'));
props.refresh();
})
.catch(() => {
showError(t('保存失败,请重试'));
})
.finally(() => {
setLoading(false);
});
}
function onSubmit() {
// Validate min/max quota relationship
if (inputs['checkin_setting.enabled']) {
const minQuota = inputs['checkin_setting.min_quota'];
const maxQuota = inputs['checkin_setting.max_quota'];
if (minQuota > maxQuota) {
showError(t('签到最小额度不能大于最大额度'));
return;
}
}
const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
const requestQueue = updateArray.map((item) => {
let value = '';
if (typeof inputs[item.key] === 'boolean') {
value = String(inputs[item.key]);
} else {
value = String(inputs[item.key]);
}
return API.put('/api/option/', {
key: item.key,
value,
});
});
setLoading(true);
Promise.all(requestQueue)
.then((res) => {
if (requestQueue.length === 1) {
if (res.includes(undefined)) return;
} else if (requestQueue.length > 1) {
if (res.includes(undefined))
return showError(t('部分保存失败,请重试'));
}
showSuccess(t('保存成功'));
props.refresh();
})
.catch(() => {
showError(t('保存失败,请重试'));
})
.finally(() => {
setLoading(false);
});
}
🤖 Prompt for AI Agents
In web/src/pages/Setting/Operation/SettingsCheckin.jsx around lines 48 to 81,
add a pre-submit validation to ensure the configured minimum quota does not
exceed the maximum quota: read the relevant fields from inputs (e.g.
inputs.min_quota and inputs.max_quota, or the actual keys used for min/max quota
in this form), convert them to numeric values, and if min > max call
showError(t('最小配额不能大于最大配额')) (or an appropriate translated message) and return
early before computing compareObjects or sending requests; keep existing flow
otherwise.

@Calcium-Ion
Calcium-Ion merged commit 3402d09 into main Jan 2, 2026
1 check passed
@Calcium-Ion
Calcium-Ion deleted the feat/check-in branch January 29, 2026 17:10
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
feat(checkin): add check-in functionality
@coderabbitai coderabbitai Bot mentioned this pull request Jun 3, 2026
11 tasks
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