feat(checkin): add check-in functionality - #2565
Conversation
…ser quota rewards
WalkthroughThis 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 setsMaxQuotato a value less thanMinQuota, 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-levelcheckinSettingvariable, 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
useEffecthook callsrefForm.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 unusedenabledfield fromcheckinDatastate
checkinDataincludes anenabledflag that is never read; effective enablement is driven bystatus?.checkin_enabledinstead.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 fromdateGridRendercallbackThe Semi UI Calendar's
dateGridRendercallback receives bothdateStringand aDateobject as arguments(dateString: string, date: Date). The current code unnecessarily reconstructs the Date vianew Date(dateString). Use the provideddateparameter 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
📒 Files selected for processing (16)
controller/checkin.gocontroller/misc.gomodel/checkin.gomodel/main.gorouter/api-router.gosetting/operation_setting/checkin_setting.goweb/src/components/settings/OperationSetting.jsxweb/src/components/settings/PersonalSetting.jsxweb/src/components/settings/personal/cards/CheckinCalendar.jsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh.jsonweb/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_enabledto 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 themonthparameter 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
monthparameter without format validation. Consider validating that the month parameter matches the expectedYYYY-MMformat 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 consistentThe new
CheckinCalendarimport and conditional render behindstatus?.checkin_enabledare wired cleanly and align with the status payload. Passingtandstatuskeeps 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 theAutoMigratecall, 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
DoCheckinhandler 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
quotaAwardedis derived directly fromsetting.MinQuota/MaxQuotawithout validation, then used to update user quota. In the MySQL/PostgreSQL transactional path (lines 104-105), the quota update uses rawgorm.Expr("quota + ?", quotaAwarded), bypassing the non-negative check inIncreaseUserQuota. 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 >= 0andMaxQuota >= MinQuotainGetCheckinSetting()or validate inUserCheckin- Clamp
quotaAwardedto a non-negative value before persisting and updating quotaThis prevents silent quota deduction from bad config.
Likely an incorrect or invalid review comment.
| 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, | ||
| }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the checkin.go controller file
find . -name "checkin.go" -type fRepository: 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 goRepository: 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 goRepository: 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 goRepository: 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.goRepository: 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.
| 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".
| 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 |
There was a problem hiding this comment.
🧩 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 15Repository: QuantumNous/new-api
Length of output: 1224
🏁 Script executed:
# Search for gopool usage in the codebase
rg "gopool\.Go" --context 3Repository: 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 2Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Look for all cacheIncrUserQuota usages
rg "cacheIncrUserQuota" --context 2Repository: 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.
| // 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 |
There was a problem hiding this comment.
Propagate DB errors in GetUserCheckinStats instead of silently ignoring them
In GetUserCheckinStats:
HasCheckedInToday(userId)is called and its error is ignored.- The
CountandSUM(quota_awarded)queries are executed without checkingError.
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 tofalse.
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'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, locate SettingsCheckin and examine its implementation
find . -name "SettingsCheckin.jsx" -o -name "SettingsCheckin.js" | head -5Repository: 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.jsxRepository: 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.jsxRepository: 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.jsxRepository: 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: changeconst currentInputs = {};toconst currentInputs = { ...inputs };before the loop - Or initialize
newInputsingetOptions()from the existinginputsstate 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "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" |
There was a problem hiding this comment.
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.
| "每日签到": "毎日のチェックイン", | ||
| "今日已签到,累计签到": "本日チェックイン済み、累計チェックイン", | ||
| "天": "日", | ||
| "每日签到可获得随机额度奖励": "毎日のチェックインでランダムなクォータ報酬を獲得できます", | ||
| "今日已签到": "本日チェックイン済み", | ||
| "立即签到": "今すぐチェックイン", | ||
| "获取签到状态失败": "チェックイン状態の取得に失敗しました", | ||
| "签到成功!获得": "チェックイン成功!獲得", | ||
| "签到失败": "チェックインに失敗しました", | ||
| "获得": "獲得", | ||
| "累计签到": "累計チェックイン", | ||
| "本月获得": "今月の獲得", | ||
| "累计获得": "累計獲得", | ||
| "签到奖励将直接添加到您的账户余额": "チェックイン報酬は直接アカウント残高に追加されます", | ||
| "每日仅可签到一次,请勿重复签到": "1日1回のみチェックイン可能です。重複チェックインはしないでください", | ||
| "签到设置": "チェックイン設定", | ||
| "签到功能允许用户每日签到获取随机额度奖励": "チェックイン機能により、ユーザーは毎日チェックインしてランダムなクォータ報酬を獲得できます", | ||
| "启用签到功能": "チェックイン機能を有効にする", | ||
| "签到最小额度": "チェックイン最小クォータ", | ||
| "签到奖励的最小额度": "チェックイン報酬の最小クォータ", | ||
| "签到最大额度": "チェックイン最大クォータ", | ||
| "签到奖励的最大额度": "チェックイン報酬の最大クォータ", | ||
| "保存签到设置": "チェックイン設定を保存" |
There was a problem hiding this comment.
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).
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку", | ||
| "每日签到": "Ежедневная регистрация", | ||
| "今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций", | ||
| "天": "дней", | ||
| "每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой", | ||
| "今日已签到": "Зарегистрирован сегодня", | ||
| "立即签到": "Зарегистрироваться сейчас", | ||
| "获取签到状态失败": "Не удалось получить статус регистрации", | ||
| "签到成功!获得": "Регистрация успешна! Получено", | ||
| "签到失败": "Регистрация не удалась", | ||
| "获得": "Получено", | ||
| "累计签到": "Всего регистраций", | ||
| "本月获得": "В этом месяце", | ||
| "累计获得": "Всего получено", | ||
| "签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета", | ||
| "每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно", | ||
| "签到设置": "Настройки регистрации", | ||
| "签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты", | ||
| "启用签到功能": "Включить функцию регистрации", | ||
| "签到最小额度": "Минимальная квота регистрации", | ||
| "签到奖励的最小额度": "Минимальная квота для наград за регистрацию", | ||
| "签到最大额度": "Максимальная квота регистрации", | ||
| "签到奖励的最大额度": "Максимальная квота для наград за регистрацию", | ||
| "保存签到设置": "Сохранить настройки регистрации" |
There was a problem hiding this comment.
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.
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку", | |
| "每日签到": "Ежедневная регистрация", | |
| "今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций", | |
| "天": "дней", | |
| "每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой", | |
| "今日已签到": "Зарегистрирован сегодня", | |
| "立即签到": "Зарегистрироваться сейчас", | |
| "获取签到状态失败": "Не удалось получить статус регистрации", | |
| "签到成功!获得": "Регистрация успешна! Получено", | |
| "签到失败": "Регистрация не удалась", | |
| "获得": "Получено", | |
| "累计签到": "Всего регистраций", | |
| "本月获得": "В этом месяце", | |
| "累计获得": "Всего получено", | |
| "签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета", | |
| "每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно", | |
| "签到设置": "Настройки регистрации", | |
| "签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты", | |
| "启用签到功能": "Включить функцию регистрации", | |
| "签到最小额度": "Минимальная квота регистрации", | |
| "签到奖励的最小额度": "Минимальная квота для наград за регистрацию", | |
| "签到最大额度": "Максимальная квота регистрации", | |
| "签到奖励的最大额度": "Максимальная квота для наград за регистрацию", | |
| "保存签到设置": "Сохранить настройки регистрации" | |
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку", | |
| "每日签到": "Ежедневная регистрация", | |
| "今日已签到,累计签到": "Зарегистрирован сегодня, всего регистраций", | |
| "每日签到可获得随机额度奖励": "Ежедневная регистрация награждает случайной квотой", | |
| "今日已签到": "Зарегистрирован сегодня", | |
| "立即签到": "Зарегистрироваться сейчас", | |
| "获取签到状态失败": "Не удалось получить статус регистрации", | |
| "签到成功!获得": "Регистрация успешна! Получено", | |
| "签到失败": "Регистрация не удалась", | |
| "获得": "Получено", | |
| "累计签到": "Всего регистраций", | |
| "本月获得": "В этом месяце", | |
| "累计获得": "Всего получено", | |
| "签到奖励将直接添加到您的账户余额": "Награды за регистрацию будут напрямую добавлены на баланс вашего счета", | |
| "每日仅可签到一次,请勿重复签到": "Только одна регистрация в день, пожалуйста, не регистрируйтесь повторно", | |
| "签到设置": "Настройки регистрации", | |
| "签到功能允许用户每日签到获取随机额度奖励": "Функция регистрации позволяет пользователям регистрироваться ежедневно для получения случайных наград в виде квоты", | |
| "启用签到功能": "Включить функцию регистрации", | |
| "签到最小额度": "Минимальная квота регистрации", | |
| "签到奖励的最小额度": "Минимальная квота для наград за регистрацию", | |
| "签到最大额度": "Максимальная квота регистрации", | |
| "签到奖励的最大额度": "Максимальная квота для наград за регистрацию", | |
| "保存签到设置": "Сохранить настройки регистрации" |
🤖 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.
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道", | ||
| "每日签到": "每日签到", | ||
| "今日已签到,累计签到": "今日已签到,累计签到", | ||
| "天": "天", | ||
| "每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励", | ||
| "今日已签到": "今日已签到", | ||
| "立即签到": "立即签到", | ||
| "获取签到状态失败": "获取签到状态失败", | ||
| "签到成功!获得": "签到成功!获得", | ||
| "签到失败": "签到失败", | ||
| "获得": "获得", | ||
| "累计签到": "累计签到", | ||
| "本月获得": "本月获得", | ||
| "累计获得": "累计获得", | ||
| "签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额", | ||
| "每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到", | ||
| "签到设置": "签到设置", | ||
| "签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励", | ||
| "启用签到功能": "启用签到功能", | ||
| "签到最小额度": "签到最小额度", | ||
| "签到奖励的最小额度": "签到奖励的最小额度", | ||
| "签到最大额度": "签到最大额度", | ||
| "签到奖励的最大额度": "签到奖励的最大额度", | ||
| "保存签到设置": "保存签到设置" |
There was a problem hiding this comment.
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.
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道", | |
| "每日签到": "每日签到", | |
| "今日已签到,累计签到": "今日已签到,累计签到", | |
| "天": "天", | |
| "每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励", | |
| "今日已签到": "今日已签到", | |
| "立即签到": "立即签到", | |
| "获取签到状态失败": "获取签到状态失败", | |
| "签到成功!获得": "签到成功!获得", | |
| "签到失败": "签到失败", | |
| "获得": "获得", | |
| "累计签到": "累计签到", | |
| "本月获得": "本月获得", | |
| "累计获得": "累计获得", | |
| "签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额", | |
| "每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到", | |
| "签到设置": "签到设置", | |
| "签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励", | |
| "启用签到功能": "启用签到功能", | |
| "签到最小额度": "签到最小额度", | |
| "签到奖励的最小额度": "签到奖励的最小额度", | |
| "签到最大额度": "签到最大额度", | |
| "签到奖励的最大额度": "签到奖励的最大额度", | |
| "保存签到设置": "保存签到设置" | |
| "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道", | |
| "每日签到": "每日签到", | |
| "今日已签到,累计签到": "今日已签到,累计签到", | |
| "每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励", | |
| "今日已签到": "今日已签到", | |
| "立即签到": "立即签到", | |
| "获取签到状态失败": "获取签到状态失败", | |
| "签到成功!获得": "签到成功!获得", | |
| "签到失败": "签到失败", | |
| "获得": "获得", | |
| "累计签到": "累计签到", | |
| "本月获得": "本月获得", | |
| "累计获得": "累计获得", | |
| "签到奖励将直接添加到您的账户余额": "签到奖励将直接添加到您的账户余额", | |
| "每日仅可签到一次,请勿重复签到": "每日仅可签到一次,请勿重复签到", | |
| "签到设置": "签到设置", | |
| "签到功能允许用户每日签到获取随机额度奖励": "签到功能允许用户每日签到获取随机额度奖励", | |
| "启用签到功能": "启用签到功能", | |
| "签到最小额度": "签到最小额度", | |
| "签到奖励的最小额度": "签到奖励的最小额度", | |
| "签到最大额度": "签到最大额度", | |
| "签到奖励的最大额度": "签到奖励的最大额度", | |
| "保存签到设置": "保存签到设置" |
🤖 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.
| 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); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
feat(checkin): add check-in functionality
#1227
#2529
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.