1. 30 Apr, 2026 1 commit
  2. 28 Apr, 2026 1 commit
    • DaydreamCoding's avatar
      feat(openai): OpenAI Fast/Flex Policy 完整实现(HTTP + WebSocket + Admin) · 30f55a1f
      DaydreamCoding authored
      
      
      对称参照 Claude BetaPolicy 的 fast-mode 过滤实现,新增针对 OpenAI 上游
      service_tier 字段(priority / flex,含客户端 "fast" → "priority" 归一化)的
      pass / filter / block 三态策略,覆盖全部 OpenAI 入口 + admin 配置入口。
      
      后端核心
      - 新增 SettingKeyOpenAIFastPolicySettings、OpenAIFastPolicyRule、
        OpenAIFastPolicySettings 配置模型,含规则的 service_tier × action × scope
        × 模型白名单 × fallback action 维度。
      - SettingService.Get/SetOpenAIFastPolicySettings;缺失时返回内置默认策略
        (所有模型的 priority 走 filter,whitelist 为空,fallback=pass)。设计
        依据:service_tier=fast 是用户级开关,与 model 字段正交,默认锁定特定
        model slug 会留下"用 gpt-4 + fast 透传 priority 上游"的绕过路径。JSON
        解析失败不再静默 fallback,slog.Warn 记录脏数据,便于运维定位。
      - service_tier 归一化(trim + ToLower + fast→priority + 白名单 priority/flex)
        与策略评估(evaluateOpenAIFastPolicy)作为唯一真实来源,HTTP / WS 共用。
        抽出纯函数 evaluateOpenAIFastPolicyWithSettings,配合 ctx-bound settings
        快照(withOpenAIFastPolicyContext / openAIFastPolicySettingsFromContext),
        WS 长会话入口预取一次后所有帧复用,避免每帧打到 settingService。
      
      HTTP 入口(4 个)
      - Chat Completions、Anthropic 兼容(Messages,含 BetaFastMode→priority 二次
        命中)、原生 Responses、Passthrough Responses 全部接入
        applyOpenAIFastPolicyToBody,filter 走 sjson 顶层删除 service_tier,block
        返回 403 forbidden_error JSON。
      - 4 入口统一使用 upstream 视角的 model(GetMappedModel +
        normalizeOpenAIModelForUpstream + Codex OAuth normalize 后的 slug),
        避免 chat/messages/native /responses/passthrough 因为 model 维度不同
        造成 whitelist 命中差异。
      - 在 pass 路径也把客户端 "fast" 别名归一化为 "priority" 写回 body,
        否则 native /responses 与 passthrough 入口会把 "fast" 原样透传给上游
        导致 400/拒绝(chat-completions 入口的 normalizeResponsesBodyServiceTier
        此前已具备同等行为)。
      
      WebSocket 入口
      - 新增 applyOpenAIFastPolicyToWSResponseCreate:严格匹配
        type="response.create",仅处理顶层 service_tier;filter 用 sjson 删字段,
        block 返回 typed *OpenAIFastBlockedError。
      - ingress 路径在 parseClientPayload 内调用,block 命中先 Write Realtime
        风格 error event 再返回 OpenAIWSClientCloseError(StatusPolicyViolation
        =1008),依赖底层 WebSocket Conn.Write 的同步 flush 保证 error 先于
        close。
      - passthrough 路径在 RunEntry 前对 firstClientMessage 应用策略,并通过
        openAIWSPolicyEnforcingFrameConn 包装 ReadFrame 对每个 client→upstream
        帧执行策略;后续帧无 model 字段时回退到 capturedSessionModel。
        filter 闭包内同时侦测 session.update / session.created 帧的 session.model
        字段刷新 capturedSessionModel,封堵"首帧 model=gpt-4o(pass)→
        session.update 改为 gpt-5.5 → 不带 model 的 response.create fallback
        到 gpt-4o"的 mid-session 绕过路径。
      - passthrough billing:requestServiceTier 在策略 filter 之后再从
        firstClientMessage 提取,filter 命中时 OpenAIForwardResult.ServiceTier
        上报 nil(default tier),与 HTTP 入口(reqBody 来自 post-filter map)
        / WS ingress(payload 来自 post-filter bytes)的语义一致。
      - 错误事件 schema:{event_id: "evt_<32hex>", type: "error",
        error: {type: "forbidden_error", code: "policy_violation", message}},
        与 OpenAI codex 客户端 error event 解析兼容。
      
      Admin / Frontend
      - dto.SystemSettings / UpdateSettingsRequest 新增
        openai_fast_policy_settings 字段(omitempty),bulk GET/PUT 接入。
      - Settings 页 Gateway 页签新增 Fast/Flex Policy 表单卡片:
        service_tier × action × scope × 模型白名单 × fallback action 全字段配置。
      - 前端守门:openaiFastPolicyLoaded 标志仅在 GET 真带回字段时才允许回写,
        避免 rollout/错误把默认规则覆盖成空;saveSettings 回写循环 skip 该字段,
        由专用刷新逻辑处理;仅 action=block 时发送 error_message,匹配后端
        omitempty 行为。
      
      测试
      - HTTP 路径:openai_fast_policy_test.go 覆盖默认配置(whitelist=[],所有
        模型 priority filter)/ block 自定义错误 / scope 区分 / filter 删字段 /
        block 不改 body / block 短路上游 / Anthropic BetaFastMode 触发 OpenAI
        fast policy 等场景。
      - WebSocket 路径:openai_fast_policy_ws_test.go 覆盖
          helper 单元(filter / fast→priority 归一化 / flex 透传 / block typed
          error / 无 service_tier 字节不变 / 非 response.create 帧不动 / 空 type
          帧不动 / event_id+code 字段断言 / 非字符串 service_tier 容错)+
          pass 路径 fast 别名归一化回归 +
          ingress 端到端(filter 后上游不含 service_tier / block 后客户端先收
          error event 再收 close 1008 且上游 0 写)+
          passthrough capturedSessionModel fallback 用例(whitelist 策略下首帧
          建立、缺 model 命中 fallback、缺少 fallback 时的 leak 文档化)+
          passthrough session.update / session.created 旋转 capturedSessionModel
          的 mid-session 绕过回归 +
          passthrough billing post-filter ServiceTier 与 idempotent filter 回归。
      Co-Authored-By: default avatarClaude Opus 4.7 (1M context) <noreply@anthropic.com>
      30f55a1f
  3. 27 Apr, 2026 1 commit
  4. 26 Apr, 2026 1 commit
    • shaw's avatar
      feat(affiliate): 完善邀请返利系统 · 9b6dcc57
      shaw authored
        - 修复返利不到账的根因:tryClaimAffiliateRebateAudit 中 PostgreSQL 参数类型推断冲突
        - 补全 OAuth 注册路径(LinuxDo/OIDC/WeChat/Pending Flow)的邀请码绑定
        - 前端 OAuth 注册页面传递 aff_code 参数
        - 新增返利冻结期机制:可配置冻结时间,到期后自动解冻(懒解冻)
        - 新增返利有效期:绑定后 N 天内有效,过期不再产生返利
        - 新增单人返利上限:超出上限部分精确截断
        - 增强返利流程 slog 结构化日志,便于排查问题
        - 已邀请用户列表增加返利明细列
      9b6dcc57
  5. 25 Apr, 2026 1 commit
    • shaw's avatar
      feat(affiliate): add feature toggle and per-user custom invite settings · 4e1bb2b4
      shaw authored
      - 在系统设置「功能开关」中新增邀请返利总开关,默认关闭;
        关闭态:菜单隐藏、注册忽略 aff、新充值不返利,但已有 quota 仍可转余额
      - 支持管理员为指定用户设置专属邀请码(覆盖随机码,全局唯一)
      - 支持管理员为指定用户设置专属返利比例(覆盖全局比例,可单条/批量调整)
      - 在系统设置邀请返利卡片内嵌入专属用户管理表格(搜索/编辑/批量/删除),
        删除采用项目通用 ConfirmDialog,会同时清除专属比例并把邀请码重置为系统随机码
      - /affiliate 用户页新增「我的返利比例」卡片与动态使用说明,让用户直观看到
        分享后能拿到多少(同源 resolveRebateRatePercent 计算,与实际充值一致)
      - 新增数据库迁移 132 添加 aff_rebate_rate_percent 与 aff_code_custom 列
      - 新增 admin 路由组 /api/v1/admin/affiliates/users/* 共 5 个端点
      - AffiliateService 改为只依赖 *SettingService,去除冗余的 SettingRepository
      - 邀请码格式校验放宽到 [A-Z0-9_-]{4,32},兼容旧 12 位系统码与新自定义码
      - 补充单元测试与集成测试覆盖新方法、冲突路径与边界值
      4e1bb2b4
  6. 24 Apr, 2026 1 commit
  7. 23 Apr, 2026 3 commits
    • erio's avatar
      revert: remove fork-only changes from release sync · 67518a59
      erio authored
      Revert payment/wechat, sora/claude-max cleanup, fork-only migrations,
      and cosmetic changes that were brought in by the release sync commit.
      Keep only channel-monitor related improvements:
      - PublicSettingsInjectionPayload named struct with drift test
      - ChannelMonitorRunner graceful shutdown in wire
      - image_output_price in SupportedModelChip
      - Simplified buildSelfNavItems in AppSidebar
      - Gateway WARN logs for 503 branches
      67518a59
    • erio's avatar
      sync: bring over remaining release/custom-0.1.115 changes · 748a84d8
      erio authored
      - Extract PublicSettingsInjectionPayload named struct with drift test
      - Add channel_monitor_default_interval_seconds to SSR injection
      - Add image_output_price to SupportedModelChip
      - Simplify AppSidebar buildSelfNavItems (admins see available channels)
      - Add gateway WARN logs for 503 no-available-accounts branches
      - Wire ChannelMonitorRunner into provideCleanup for graceful shutdown
      - Add migrations 130/131 (CC template userid fix + mimicry field cleanup)
      - Clean up fork-only features (sora, claude max simulation, client affinity)
      - Remove ~320 obsolete i18n keys
      - Add codexUsage utility, WechatServiceButton, BulkEditAccountModal
      - Tidy go.sum
      748a84d8
    • james-6-23's avatar
      feat(rpm): RPM 限流模块优化 · dc5d42ad
      james-6-23 authored
      P0:
      - rpm_override 嵌入 Auth Cache Snapshot,消除每请求 DB 查询 (snapshot v6→v7)
      - 429 RPM 响应返回 Retry-After 头(当前分钟剩余秒数)
      
      P1:
      - ClearAll 按钮直连 DELETE API,带 loading 防重复
      - 新增 GET /admin/users/:id/rpm-status 管理员 RPM 用量查询端点
      
      优化:
      - checkRPM 从级联互斥改为并行取最严,user.rpm_limit 作为全局硬上限始终生效
      - Override/Group 变更后自动失效 auth cache
      - fail-open 语义不变,Redis 故障不阻塞业务
      dc5d42ad
  8. 22 Apr, 2026 1 commit
  9. 21 Apr, 2026 6 commits
  10. 20 Apr, 2026 4 commits
    • IanShaw027's avatar
      ebe75244
    • erio's avatar
      refactor(channels): consolidate pricing index, tighten types, polish DTOs · 365ef1fd
      erio authored
      Follow-up to the available-channels review pass. No behavior change for
      end users; tightens internals based on three independent code reviews.
      
      Backend
      - service/channel.go: collapse buildPricingLookup + pricedNamesFor
        into a single platformPricingIndex (byLower + originalCase + ordered
        names), built once per SupportedModels call. Fixes a casing-
        consistency bug where the same logical model appeared with mapping
        case in the exact branch but pricing case in the wildcard branch —
        pricing's original case now wins everywhere.
      - service/channel.go: doc that a mapping key of just "*" expands to
        every priced model on the platform (intentional "passthrough all").
      - service/channel_available.go: normalize empty BillingModelSource to
        channel_mapped at construction time, removing the same fallback
        duplicated in the admin DTO mapper and the admin Vue template.
      - handler/admin/available_channel_handler.go: unexport
        availableChannelToAdminResponse (same-package usage only); mapper
        is now a pure passthrough.
      - handler/available_channel_handler.go: drop the middleware2 alias
        (no name collision in this file).
      
      Frontend
      - utils/pricing.ts: extract formatScaled, used by SupportedModelChip
        and PricingRow.
      - api/admin/channels.ts: re-export BillingMode from constants/channel;
        tighten Channel.status / billing_model_source to ChannelStatus /
        BillingModelSource (and same for AvailableChannel).
      - components/channels/AvailableChannelsTable.vue: drop dead
        withDefaults wrapper (loading is required, both call sites pass it).
      - views/admin/AvailableChannelsView.vue: drop the redundant
        || BILLING_MODEL_SOURCE_CHANNEL_MAPPED fallback (now applied in
        service layer); remove unused import.
      - i18n zh + en: delete unused tierLabel and tokenRange keys from
        both availableChannels.pricing and admin.availableChannels.pricing.
      
      Tests
      - New: SupportedModels_ExactKeyUsesPricedCaseWhenAvailable locks the
        pricing-case-wins rule.
      - New: SupportedModels_AsteriskOnlyMappingExpandsAllPriced documents
        the "*" expansion rule.
      - Admin handler: existing tests adjusted to pass an explicit
        BillingModelSource (default-fill is now exercised by service tests).
      365ef1fd
    • IanShaw027's avatar
      fix: close admin settings review gaps · 030da8c2
      IanShaw027 authored
      030da8c2
    • erio's avatar
      feat(channels): add "Available Channels" aggregate view · 654cfb64
      erio authored
      Add a read-only aggregate view per channel: its linked groups and a
      deterministic wildcard-free supported-model list with pricing details.
      
      Backend
      - service.Channel.SupportedModels(): combine ModelMapping keys with
        same-platform ModelPricing.Models; trailing "*" keys expand via
        pricing prefix match; platforms without a mapping produce no
        entries (intentional "no mapping = not shown" rule).
      - Extract splitWildcardSuffix() shared with toModelEntry.
      - Build a per-call pricing lookup map (platform+lowerName -> *pricing)
        to avoid O(N*M) scans in SupportedModels.
      - ChannelService.ListAvailable() aggregates channels + active groups;
        filters out group IDs no longer active.
      - Admin route GET /api/v1/admin/channels/available returns the full
        DTO (id, status, billing_model_source, restrict_models, groups,
        supported_models).
      - User route GET /api/v1/channels/available applies three filters:
        Status==active, visible-group intersection, and platform filter
        on supported_models (prevents cross-platform leak when a channel
        links to both a user-accessible group and an inaccessible one on
        another platform). Response is a plain array (matches the
        /groups/available sibling shape). Field whitelist omits
        billing_model_source, restrict_models, ids, status, sort_order.
      
      Frontend
      - New /admin/available-channels and /available-channels views backed
        by a shared AvailableChannelsTable component (admin adds status +
        billing-source columns via slots).
      - PricingRow extracted to its own SFC; SupportedModelChip references
        shared billing-mode constants in constants/channel.ts.
      - Sidebar: new entry above "渠道管理" for admin; matching entry in
        user nav.
      - i18n: zh + en coverage for both namespaces.
      
      Tests
      - SupportedModels: wildcard-only pricing skipped, prefix-matches-
        nothing, cross-platform bleed, case-insensitive dedup, empty
        platform mapping.
      - ListAvailable: nil groupRepo, inactive-group-ID dropped, stable
        case-insensitive name sort.
      - User handler: 401 on unauthenticated, visible-group intersection,
        platform filter on supported_models, JSON whitelist.
      - Admin handler: full DTO including default BillingModelSource
        fallback.
      
      Refs: issue #1729
      654cfb64
  11. 21 Apr, 2026 2 commits
    • erio's avatar
      feat(channel-monitor): apply template via subset picker; CC 2.1.114 baseline doc · 6925ac25
      erio authored
      Apply flow:
      - POST /admin/channel-monitor-templates/:id/apply now requires monitor_ids
        (non-empty array). Service applies the template only to the selected
        subset, gated by AND template_id = :id (so users can't sneak in
        unrelated monitor IDs).
      - New GET /admin/channel-monitor-templates/:id/monitors returns the
        associated monitor briefs (id/name/provider/enabled) for the picker.
      - ApplyToMonitors signature gains monitorIDs []int64; empty list returns
        ErrChannelMonitorTemplateApplyEmpty.
      
      Frontend:
      - New MonitorTemplateApplyPickerDialog.vue: list of associated monitors
        with checkboxes (default all checked), 全选 / 全不选 shortcuts, live
        selected/total count. Submit calls apply(id, ids).
      - MonitorTemplateManagerDialog replaces the old ConfirmDialog flow with
        the picker; onApplied refetches the list to refresh associated counts.
      
      i18n: applyPicker* + common.selectAll keys.
      
      chore: bump version to 0.1.114.33
      
      The CC 2.1.114 (sdk-cli) UA / APIKeyBetaHeader / JSON metadata.user_id
      baseline (already verified working via the in-process apply on prod
      template id=1) is documented in internal/pkg/claude/constants.go and
      is what the seed template in the manager UI should follow.
      6925ac25
    • erio's avatar
      feat(channel-monitor): request templates with snapshot apply + headers/body override · a2964259
      erio authored
      Problem:
      Upstream channels can reject monitor probes based on client fingerprint
      (e.g. "only Claude Code clients allowed"). The monitor had no way to
      customize the outgoing request to bypass such restrictions.
      
      Solution:
      Introduce reusable request templates that carry extra_headers plus an
      optional body override; monitors reference a template and receive a
      snapshot copy on apply. Template edits do NOT auto-propagate — users
      must click "apply to associated monitors" to refresh snapshots, so a
      bad template edit cannot instantly break all production monitors.
      
      Data model (migration 112):
      - channel_monitor_request_templates: id, name, provider, description,
        extra_headers jsonb, body_override_mode ('off'|'merge'|'replace'),
        body_override jsonb. Unique (provider, name).
      - channel_monitors: +template_id (FK, ON DELETE SET NULL), +extra_headers,
        +body_override_mode, +body_override (the three runtime snapshot fields).
      
      Checker (channel_monitor_checker.go):
      - callProvider + runCheckForModel accept a CheckOptions carrying the
        snapshot fields. mergeHeaders applies user headers on top of adapter
        defaults (forbidden list: Host / Content-Length / Transfer-Encoding /
        Connection / Content-Encoding).
      - buildRequestBody:
          off     -> adapter default body
          merge   -> shallow-merge over default; per-provider deny list
                     (model/messages/contents) protects the challenge contract
          replace -> user body verbatim
      - Replace mode skips challenge validation; instead HTTP 2xx + non-empty
        extracted response text = operational, empty = failed.
      - 4 new unit tests cover all three modes + replace/empty-response case.
      
      Admin API:
      - /admin/channel-monitor-templates CRUD + /:id/apply (overwrite snapshot
        on all template_id=id monitors, returns affected count).
      - channel_monitor request/response DTOs gain the 4 new fields.
      
      Frontend:
      - channelMonitorTemplate.ts API client.
      - MonitorAdvancedRequestConfig.vue shared component for headers textarea
        + body mode radio + body JSON editor; used by both template and monitor
        forms.
      - MonitorTemplateManagerDialog.vue: provider tabs, list/create/edit/
        delete/apply, live "associated monitors" count per row.
      - MonitorFiltersBar: new 模板管理 button next to 新增监控.
      - MonitorFormDialog: collapsible 高级 section with template dropdown
        (filtered by form.provider, clears on provider change) + embedded
        AdvancedRequestConfig. Picking a template copies its fields into the
        form (snapshot semantics mirrored on the client).
      - i18n zh/en entries for all new copy.
      
      chore: bump version to 0.1.114.32
      a2964259
  12. 20 Apr, 2026 5 commits
    • erio's avatar
      feat(channel-monitor): add feature switch settings + fix extra_models save · 7da51240
      erio authored
      Settings:
      - New "功能开关" tab between 通用设置 and 安全与认证
      - ChannelMonitorEnabled toggle: runner skips scheduling when false,
        user-facing list returns empty
      - ChannelMonitorDefaultIntervalSeconds (15-3600): pre-fills interval
        when creating a new monitor; each monitor can still override
      
      Bug fix:
      - ModelTagInput now commits pending input on blur, not just Enter/Tab.
        Previously clicking "save" with an un-Enter'd extra model would drop
        the value (DB stored extra_models=[] even when user typed entries).
      
      Backend:
      - domain_constants: SettingKeyChannelMonitor{Enabled,DefaultIntervalSeconds}
      - SettingService.GetChannelMonitorRuntime: lightweight getter used by
        runner tick + user handler per-request (fail-open on DB error)
      - Runner tickDueChecks: bails early when feature disabled
      - ChannelMonitorUserHandler: checks feature flag before serving
      - Comment on runner doc: scheduler state is implicit (every tick re-reads
        ListEnabled from DB), so CRUD ops on monitors self-maintain the schedule
      
      Bump VERSION to 0.1.114.25
      7da51240
    • IanShaw027's avatar
      feat: add admin auth migration reports view · f73117f9
      IanShaw027 authored
      f73117f9
    • IanShaw027's avatar
      4ebdfcd1
    • erio's avatar
      feat(monitor): admin channel monitor MVP with SSRF protection and batch aggregation · 20a4e418
      erio authored
      新增 admin「渠道监控」模块(参考 BingZi-233/check-cx),独立于现有 Channel 体系。
      admin 配置 + 后台定时调用上游 LLM chat completions 健康检查 + 所有登录用户只读可见。
      
      后端:
      - ent: channel_monitor + channel_monitor_history(AES-256-GCM 加密 api_key)
      - service 按职责拆分:service/aggregator/validate/checker/runner/ssrf
      - provider strategy map 替代 switch(openai/anthropic/gemini)
      - repository batch 聚合(ListLatestForMonitorIDs + ComputeAvailabilityForMonitors)消除 N+1
      - runner: ticker(5s) + pond worker pool(5) + inFlight 防并发 + TrySubmit 防雪崩
        + 凌晨 3 点 cron 清理 30 天历史
      - SSRF 防护:强制 https + 私网/loopback/云元数据 IP 拒绝(127/8、10/8、172.16/12、
        192.168/16、169.254/16、100.64/10、::1、fc00::/7、fe80::/10)+ DialContext
        在 socket 层防 DNS rebinding
      - API key sanitize:擦除 url.Error 与上游响应 body 中的 sk-/sk-ant-/AIza/JWT 模式
      - APIKeyDecryptFailed 标志位 + 单 monitor 路径检测,避免空 key 调用上游
      
      handler:
      - admin: CRUD + 手动触发 + 历史接口(api_key 脱敏)
      - user: 只读列表 + 状态详情(去除 api_key/endpoint)
      - ParseChannelMonitorID 共用 + dto.ChannelMonitorExtraModelStatus 共用
      
      前端:
      - 路由 /admin/channels/{pricing,monitor} + /monitor(用户只读)
      - AppSidebar 父项 expandOnly 支持
      - ChannelMonitorView 拆为 8 个子组件 + ChannelStatusView 拆出 detail dialog
      - composables/useChannelMonitorFormat + constants/channelMonitor 共享
      - i18n monitorCommon namespace 消除 admin/user 两 view 重复
      
      合规:所有文件符合 CLAUDE.md(Go ≤ 500 行 / Vue ≤ 300 行 / 函数 ≤ 30 行)
      CI: go build / gofmt / golangci-lint(0 issues) / make test-unit / pnpm build 全绿
      20a4e418
    • IanShaw027's avatar
      feat: rebuild auth identity foundation flow · e9de839d
      IanShaw027 authored
      e9de839d
  13. 15 Apr, 2026 1 commit
    • erio's avatar
      feat(usage): add account cost display to admin dashboard and usage pages · 6ade6d30
      erio authored
      - Add account_cost column to dashboard aggregation tables (migration 107)
      - DashboardStats: add TotalAccountCost/TodayAccountCost fields
      - ModelStat/GroupStat: add AccountCost field with SQL aggregation
      - GetStatsWithFilters: always return TotalAccountCost (remove accountID filter)
      - Dashboard Token cards: show user(green)/cost(orange)/standard(gray)
      - Usage stats card: show account cost and standard below main value
      - Model/Group distribution tables: add orange cost column
      6ade6d30
  14. 14 Apr, 2026 12 commits
    • erio's avatar
      feat(payment): add recharge fee rate setting and fix provider card UI · 98140f6c
      erio authored
      - Add recharge_fee_rate system setting (percentage fee on top of recharge amount)
      - Full backend chain: config constant, PaymentConfig struct, update validation,
        read/write persistence, DTO, handler GET/PUT responses
      - Frontend: settings input with preview, i18n (zh/en), API types
      - Fix provider card toggle layout: labels above switches to save width
      - Fix Chinese translation: "EasyPay" → "易支付" in provider description
      98140f6c
    • erio's avatar
      feat(payment): balance recharge multiplier and refund amount separation · 60a4b931
      erio authored
      - Add balance_recharge_multiplier system setting (e.g. 1.2 = charge 100 get 120)
      - Separate order_amount (credited balance) from pay_amount (actual payment)
      - Refund calculates gateway amount proportionally from pay_amount
      - Frontend shows both amounts in order details, payment status, refund dialog
      - Admin settings UI for configuring recharge multiplier
      60a4b931
    • erio's avatar
      fix: merge 30 general improvements from release branch · 6ac8ccde
      erio authored
      Bug fixes:
      - Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel)
      - Filter soft-deleted users in GetByGroupID
      - Stripe CSP policy (allow Stripe.js in script-src and frame-src)
      - WebSearch API key validation on save
      - RECHARGING status in payment result success check
      - Windows test fixes (logger Sync deadlock, config path escaping)
      
      Feature enhancements:
      - Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider)
      - EasyPay mobile H5 payment (device param + PayURL2)
      - SSE error propagation in WebSearch emulation
      - AccountStatsCost DTO field for admin usage logs
      - Plans sort by sort_order instead of created_at
      - UsageMapHook for streaming response usage data
      - apicompat Instructions field passthrough
      - EffectiveLoadFactor for ops concurrency/metrics
      - Usage billing RETURNING balance for notify system
      - BulkUpdate mixed channel warning with details
      - println to slog migration in auth cache
      - Wire ProviderSet cleanup
      - CI cache-dependency-path optimization
      
      Frontend:
      - Refund eligibility check per provider (canRequestRefund)
      - Plan sort_order editing
      - Dead code cleanup (simulate_claude_max, client_affinity)
      - GroupsView platform switch guard
      - channels features_config API type
      - UsageView account_stats_cost export
      6ac8ccde
    • erio's avatar
      feat: websearch quota enhancements and balance notify hint · 7c729293
      erio authored
      - QuotaLimit changed to *int64 (null=unlimited, >0=limited)
      - Add reset-usage endpoint (POST /admin/settings/web-search-emulation/reset-usage)
      - Show quota usage in header always (collapsed and expanded)
      - Add reset quota button in expanded provider view
      - Quota input: empty=unlimited with ∞ placeholder, must be >0 if set
      - Add email verification hint on balance notify card
      7c729293
    • erio's avatar
      fix: batch 1 audit fixes — quota SQL fixed mode, public recharge URL,... · ed8a9d97
      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
      ed8a9d97
    • erio's avatar
      feat(notify): add platform/ID to quota alert email, add recharge URL to balance alert · c1eb79e4
      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
      c1eb79e4
    • erio's avatar
      feat(notify): convert email lists to NotifyEmailEntry struct with toggle support · 915b7a4a
      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
      915b7a4a
    • erio's avatar
      feat(notify): add global toggles, percentage threshold, and visibility control · eba289a7
      erio authored
      - Add global toggle for account quota notification in admin settings
      - Add percentage-based threshold type for per-account quota alerts
      - Hide balance notify card on user profile when global toggle is off
      - Expose balance_low_notify_enabled and account_quota_notify_enabled in PublicSettings
      - Add threshold type (fixed/percentage) to QuotaNotifyToggle with $ / % switcher
      eba289a7
    • erio's avatar
      fix(notify): remove percentage threshold from balance notification · cef22c70
      erio authored
      Balance low notification only supports fixed USD amount threshold.
      Percentage threshold is a quota concept, not applicable to balance.
      Reverted threshold_type from admin settings, user profile, and all
      backend/frontend layers. DB fields (balance_notify_threshold_type,
      total_recharged) retained for potential future quota use.
      cef22c70
    • erio's avatar
      feat(notify): add percentage threshold type for balance low notification · f694afbb
      erio authored
      - Add threshold_type field (fixed/percentage) to system and user settings
      - Add total_recharged field to users table, auto-incremented on balance credit
      - Percentage mode: effective threshold = total_recharged × percentage / 100
      - User-level threshold_type inherits from system default when not set
      - Update admin settings UI with radio selector (fixed amount / percentage)
      - Migration: 102_add_balance_notify_threshold_type.sql
      f694afbb
    • erio's avatar
      feat(websearch): settings UI overhaul and quota improvements · d0674e0f
      erio authored
      - Remove Priority field, auto load-balance by quota remaining
      - Replace QuotaRefreshInterval (daily/weekly/monthly) with SubscribedAt
        (subscription date, monthly lazy refresh via Redis TTL)
      - Add collapsible provider cards, API key show/copy, usage progress bar
      - Add test endpoint (POST /web-search-emulation/test) bypassing quota
      - Wire WebSearchManagerBuilder on startup (was never called before)
      - Fix nextMonthlyReset day-of-month overflow (Jan 31 → Feb 28)
      - Fix non-deterministic sort in selectByQuotaWeight
      - Map ProxyID in builder for provider-level proxy tracking
      - Fix frontend timezone drift in subscribed_at date picker
      - Fix provider deletion index shift for expandedProviders state
      d0674e0f
    • erio's avatar
      feat(notify): add balance low & account quota notification system · b32d1a2c
      erio authored
      - User balance low notification: email alert when balance drops below
        configurable threshold (user email + verified extra emails)
      - Account quota notification: broadcast email to admin-configured
        recipients when daily/weekly/total quota usage exceeds alert threshold
      - Admin settings: global enable/disable, default threshold, quota
        notification email list (Email Settings tab)
      - User profile: enable/disable, custom threshold, add/remove extra
        notification emails with verification code flow
      - Account quota: per-dimension alert toggle and threshold in quota
        control card
      - Trigger logic: first-crossing only (old >= threshold && new < threshold
        for balance; old < threshold && new >= threshold for quota), naturally
        prevents duplicate notifications without Redis dedup
      b32d1a2c