- 14 Apr, 2026 40 commits
-
-
erio authored
smtp.SendMail automatically upgrades to STARTTLS when the server supports it. Our replacement sendMailPlain skipped this, causing credentials to be sent in plaintext on port 587. Add STARTTLS negotiation before Auth to restore the original security behavior.
-
erio authored
- Skip websearch provider when ProxyID is set but proxy not found (prevent silent direct connection bypass) - Fix sortByStableRandomWeight: pair factors with items so sort.Slice swap keeps weights aligned - Allow empty platform in account_stats_pricing_rules (wildcard matching), only force anthropic default for main model_pricing - Add channel_account_stats_pricing_intervals table and repo layer support for interval-based pricing in account stats rules - calculateTokenStatsCost now uses interval pricing when available - Replace smtp.SendMail/tls.Dial with net.Dialer timeout (10s dial, 20s IO) to prevent goroutine leak on SMTP hang - Fix gofmt formatting issues - Web Search label: black text with red warning hint
-
erio authored
- Fix web_search_emulation toggle: explicitly write false for disabled platforms instead of leaving stale true from cloned features_config - Extract validatePricingEntries from validateChannelConfig for reuse - Validate account_stats_pricing_rules[].pricing in both Create and Update paths (negative prices, bad intervals, missing per_request price)
-
erio authored
Security (HIGH): - Normalize all Redis cache keys to lowercase (verifyCode, passwordReset) - Fix verify code TTL renewal on failed attempts: use remaining TTL via ExpiresAt field instead of resetting to full 15-minute window - Add 3 missing fields to diffSettings audit log (promo_code, invitation_code, custom_endpoints) Code quality (MEDIUM): - Extract filterVerifiedEmails shared helper (balance_notify_service.go) - Add Pricing array non-empty validation for channel pricing rules - Add platform token semantics comment in gateway_service.go - Complete validatePlanPatch test coverage (+10 test cases) - Replace string types with QuotaThresholdType/QuotaResetMode across frontend - Remove duplicate getPlatformTextColor/getRateBadgeClass in ChannelsView - Return EMAIL_NOT_FOUND error on RemoveNotifyEmail miss UI improvements: - Reorder cost tooltip: user billing above separator, account billing below - Add NaN guard to accountBilled function - Move timezone selector inline into reset-mode row (no longer standalone)
-
erio authored
- Fix websearch provider failover: proxy error from provider-specific proxy now continues to next provider instead of aborting the entire loop - Fix SMTP failure locking users out: send email first, then write cache and increment rate counter - Fix notify email cache key case sensitivity: normalize to lowercase - Add OriginalPrice validation to validatePlanPatch and validatePlanRequired - Add empty scope validation for channel pricing rules (group_ids/account_ids) - Add platform color to account search dropdown in channel pricing rules
-
erio authored
M5: New composable frontend/src/composables/useQuotaNotifyState.ts - Replaces 9 individual refs in both Create/Edit modals with reactive state - Provides loadFromExtra/writeToExtra/reset helpers - Eliminates ~120 lines of duplicated code across the two modals H14: Vue file length violations fixed - AdminPaymentPlansView.vue: 325 → 183 lines (extracted PlanEditDialog.vue) - QuotaLimitCard.vue: 327 → 268 lines (extracted QuotaDimensionRow.vue) - PlanEditDialog.vue: 181 lines (new, plan create/edit form) - QuotaDimensionRow.vue: 108 lines (new, single quota dimension row)
-
erio authored
refactor: batch 3 — decompose CheckBalanceAfterDeduction, merge crossing checks, add QuotaNotifyConfig M1: CheckBalanceAfterDeduction (63→18 lines) decomposed into: canNotifyBalance, resolveUserEffectiveThreshold, crossedDownward, dispatchBalanceLowEmail M3: New Account.QuotaNotifyConfig(dim) method replaces 9 hardcoded getters (getters kept as thin wrappers for backward compatibility) M4: checkQuotaDimCrossings + checkQuotaDimCrossingsFromState merged into one function taking pre-built []quotaDim; caller builds dims conditionally -
erio authored
H5: diffSettings now tracks 5 balance/quota notify fields in audit log M15: log.Printf audit log migrated to slog.Info, removed "log" import M14: New frontend/src/constants/account.ts with shared constants QuotaNotifyToggle.vue uses QUOTA_THRESHOLD_TYPE_FIXED/PERCENTAGE L2: UsageTable.vue uses BILLING_MODE_TOKEN/IMAGE from billingMode.ts -
erio authored
fix: batch 1 audit fixes — quota SQL fixed mode, public recharge URL, WebSearch bool fallback, UpdatePlan validation H1: incrementUsageBillingAccountQuota now uses shared dailyExpiredExpr/weeklyExpiredExpr constants (supporting fixed reset mode) instead of hardcoded '24 hours'/'168 hours' H4: public settings endpoint now maps balance_low_notify_recharge_url H6: GetWebSearchEmulationMode tolerates legacy bool values (true→enabled) H7: UpdatePlan validates non-nil patch fields (rejects negative price, empty name, etc.) H8: UsageTable accountBilled() helper with total_cost ?? 0 null guard H9: AdminUsageLog TS type adds channel_id + billing_tier M2: account.go "fixed" literals replaced with thresholdTypeFixed constant M13: SystemSettings TS type adds web_search_emulation_enabled UI: QuotaLimitCard title labels now use flex-1 to align with flex-1 input boxes -
erio authored
balance_notify_service_test.go (27 tests): - resolveBalanceThreshold: fixed/percentage/zero recharged/empty type - quotaDim.resolvedThreshold: fixed normal/exceed/equal limit, percentage 0/30/100/>100, zero/negative limit - sanitizeEmailHeader: CRLF/CR/LF/clean/empty/multiple newlines - buildQuotaDims / buildQuotaDimsFromState: all dimensions, empty extra, state-vs-account precedence - collectBalanceNotifyRecipients: empty, filter disabled/unverified, case-insensitive dedup, skip empty, trim balance_notify_check_test.go (16 tests): - CheckBalanceAfterDeduction guard clauses: nil user/disabled/global-off/threshold=0/user-override/no-crossing - CheckAccountQuotaAfterIncrement guards: nil account/zero cost/negative cost/global-disabled - getBalanceNotifyConfig: all fields, disabled, invalid threshold - isAccountQuotaNotifyEnabled: missing/false/true - getSiteName: default fallback + configured balance_notify_email_body_test.go (10 tests): - Guards against fmt.Sprintf arg-count mismatches in email templates - Verifies HTML escaping of recharge URL - Verifies CSS %% escape produces literal % in output - Verifies unlimited/percentage/over-quota display branches payment_config_plans_validation_test.go (13 tests): - validatePlanRequired: all 5 validation branches + whitespace handling
-
erio authored
- EditAccountModal width changed from "normal" to "wide" (match CreateAccountModal) - CreateAccountModal now passes all quota notify props to QuotaLimitCard - QuotaLimitCard: when global notify disabled, hide title row, input takes full width - Quota alert email: show remaining quota + threshold (fixed/$, percentage/%) instead of usage trigger point
-
erio authored
-
erio authored
-
erio authored
-
erio authored
- Quota alert email now shows account ID and platform - Balance low email includes a "Top Up Now" button when recharge URL is configured - New setting: balance_low_notify_recharge_url in admin settings
-
erio authored
-
erio authored
Threshold now represents remaining quota instead of usage amount: - Fixed ($): threshold=400, limit=1000 → alert when remaining drops to $400 (i.e., usage reaches $600) - Percentage (%): threshold=30%, limit=1000 → alert when remaining drops to 30% (i.e., usage reaches $700) Also: - Rename 告警阈值 → 提醒阈值 in i18n - Widen type dropdown to w-16 for proper $ / % display
-
erio authored
Move QuotaNotifyToggle to the same row as the limit $ input for all three dimensions (daily/weekly/total), significantly reducing card height.
-
erio authored
- QuotaLimitCard: add collapse/expand toggle (chevron icon + click header) - QuotaNotifyToggle: show $ or % suffix in threshold input - Reduce vertical spacing between reset mode hint and notify toggle
-
erio authored
- QuotaNotifyToggle: add $ or % suffix to threshold input based on type - QuotaLimitCard: combine reset mode and notify toggle on same row to reduce vertical height for daily/weekly sections - Remove redundant ml-4 indentation from QuotaNotifyToggle
-
erio authored
Priority was wrong: - Before: custom rules → LiteLLM (when ApplyPricingToAccountStats) → nil - After: custom rules → totalCost (when ApplyPricingToAccountStats) → LiteLLM → nil When ApplyPricingToAccountStats is enabled, use the request's actual client billing cost (before multiplier) as account_stats_cost, instead of recalculating from LiteLLM per-token prices which produced incorrect values for per-request billing mode. LiteLLM model pricing is now the final fallback (priority 3), used only when neither custom rules nor ApplyPricingToAccountStats apply.
-
erio authored
The field was present in SystemSettings response DTO and service layer but missing from: - UpdateSettingsRequest (admin handler) - saves were silently ignored - GET/PUT response mapping in admin handler - UpdateSettingsRequest (non-admin dto) This caused the toggle to always revert to off after saving.
-
erio authored
- "默认(跟随渠道)" → "默认", "Default (follow channel)" → "Default" - Move "follows channel config" info to description text - Reduce select width from w-32 to w-24 in both Edit and Create modals
-
erio authored
QuotaLimitCard now requires quotaNotifyGlobalEnabled prop to control visibility of QuotaNotifyToggle components. When the global account quota notification is disabled in admin settings, per-account threshold toggles are hidden in both Edit and Create account modals.
-
erio authored
- Move sanitizeEmailHeader to SendEmailWithConfig entry point, covering all email senders (verify code, password reset, ops alerts, notifications) - Add panic recovery to UpdateBalance goroutine - Fix stale comment in getAccountQuotaNotifyEmails (email="" no longer used) - Log error instead of silently discarding verifyNotifyCode cache update failure
-
erio authored
P0: fix wildcard matching test assertion (config order, not longest prefix) P0: add TotalRecharged to auth cache snapshot (v5) for percentage threshold P1: move pricing rules into per-platform sections in ChannelsView P1: populate account name cache when editing existing channel rules P1: sanitize email subject headers to prevent SMTP injection P1: make Redis INCR+EXPIRE idempotent for rate limiting P1: deep copy FeaturesConfig in Channel.Clone() P2: clean up stale email="" placeholder comments P2: replace log.Printf with slog in email_service.go
-
erio authored
Backend fixes: - Fix balance notify ignoring percentage threshold type (was treating percentage value as fixed USD amount) - Remove dead code parseJSONStringArray - Add ImageOutputTokens to tryModelFilePricing calculation - Unify zero-value check: cost == 0 → cost <= 0 in calculateTokenStatsCost - Use MarshalNotifyEmails instead of json.Marshal for consistency - Rename quotaDim.oldUsed → currentUsed for clarity - Extract HTML email templates to const variables (function ≤30 lines) Test fixes: - Rewrite account_websearch_test.go for GetWebSearchEmulationMode tri-state - Add 6 tryModelFilePricing test cases Frontend fixes: - Replace hardcoded '未命名' with i18n key - Extract getBillingModeLabel/getBillingModeBadgeClass to shared utils - Replace inline type with imported NotifyEmailEntry - Pass platform to AccountStats pricing rules via inferRulePlatform() - Add billing mode constants (BILLING_MODE_TOKEN/PER_REQUEST/IMAGE)
-
erio authored
WebSearch tri-state switch: - Account-level web_search_emulation changed from bool to tri-state string: "default" (follow channel) / "enabled" / "disabled" - shouldEmulateWebSearch checks channel config when account is "default" - SQL migration converts old bool values - Frontend select replaces toggle in Edit/CreateAccountModal Account stats pricing: - resolveAccountStatsCost uses upstream model (post-mapping) for matching - Priority: custom rules → model pricing file (when toggle on) → default - Custom rules always configurable, independent of toggle - Account ID field changed to searchable selector filtered by platform - Description updated to reflect new behavior Quota notification cache fix: - CheckAccountQuotaAfterIncrement fetches real-time account from DB - Reconstructs pre-increment usage for accurate threshold crossing detection - New AccountQuotaReader interface (minimal: GetByID only) Usage tooltip: - Per-request/image billing shows per-request price instead of $0 token price - Token billing continues to show input/output price per million tokens
-
erio authored
- resolveAccountStatsCost now uses the final upstream model (after account-level mapping) to match custom pricing rules, fixing the issue where requested model (e.g. claude-sonnet-4-5) didn't match rules configured for upstream model (e.g. claude-opus-4-6) - Remove tryChannelPricing fallback — only custom rules are applied, unmatched requests use default formula (total_cost × rate) - Remove unused billingService and serviceTier parameters - Update description: "启用后将支持自定义账号统计的模型价格"
-
erio authored
- Add "verify" button next to saved unverified emails in ProfileBalanceNotifyCard (send code → enter code → verify) - Backend: VerifyAndAddNotifyEmail now marks existing unverified emails as verified instead of returning "already exists" - Inline verification UI with countdown timer and resend button
-
erio authored
- Fix cached balance causing threshold crossing to never trigger: read real-time balance from billingCacheService instead of stale API key auth snapshot - Remove email="" placeholder concept; all emails are user-managed - Only send notifications to verified && non-disabled emails - Frontend: pre-fill user's email in add input when list is empty - Remove FilterEnabledEmails/IsPrimaryDisabled helpers (no longer needed)
-
erio authored
- Change balance_notify_extra_emails and account_quota_notify_emails from []string to []NotifyEmailEntry{email, disabled, verified} - Add per-email enable/disable toggle for both user and admin notifications - Add PUT /user/notify-email/toggle API endpoint - Fix critical bug: API key auth cache snapshot missing balance notify fields (Email, Username, BalanceNotifyEnabled, etc.), causing notifications to never fire on cached request paths - Bump cache snapshot version 3→4 to invalidate stale entries - Add SQL migration 104 to convert old format data - Backward compatible: parseNotifyEmails auto-detects old/new format - User balance notify: max 3 emails (primary + 2 extra) - Admin quota notify: unlimited emails, each with toggle -
erio authored
Replace blur-based auto-save with an explicit Save button so users know when their threshold is persisted. Shows success toast on save.
-
erio authored
-
erio authored
- Show system default threshold as placeholder in custom threshold input - Display user's primary email with "Primary" badge - Support adding multiple pending emails before verification - Each pending email has independent send/verify/resend flow - Expose balance_low_notify_threshold in PublicSettings API - Clean up timers on unmount to prevent leaks
-
erio authored
The service layer correctly populated BalanceLowNotifyEnabled and AccountQuotaNotifyEnabled in PublicSettings, but the handler-to-DTO mapping was missing. Users could not see the balance notify card because the public settings API never returned these flags.
-
erio authored
Only show the inline eye/copy buttons when provider.api_key has a value. When only api_key_configured is true (saved key, not loaded), buttons are hidden since there's nothing to show/copy.
-
erio authored
-
erio authored
- Basic settings now only shows the global toggle - Custom pricing rules appear inside each platform tab when toggle is on - Group selector in rules scoped to the current platform's groups - Remove unused allFormGroupIds computed
-
erio authored
- Fix GetByKeyForAuth missing user.FieldEmail and user.FieldUsername (notifications sent to empty address) - Guard against empty email in collectBalanceNotifyRecipients - Remove non-atomic TotalRecharged read-modify-write in admin balance adjustment - HTML-escape userName/siteName/accountName in notification email templates - Fix timer leak in ProfileBalanceNotifyCard (add onUnmounted cleanup) - Add warning log on websearch proxy URL resolution failure
-