426 Commits

Author SHA1 Message Date
shaw
a613a587ba feat: add subscription expiry email toggle 2026-05-21 14:27:50 +08:00
Wesley Liddick
eda04c6129
Merge pull request #2615 from wucm667/feat/redeem-code-batch-update
feat(redeem): 兑换码支持批量修改
2026-05-21 10:39:46 +08:00
lyen1688
1d2445ff52 修复 API Key ACL 开关的 CI 校验 2026-05-20 23:51:39 +08:00
lyen1688
08c8c67df7 为 API Key ACL 增加反代真实 IP 开关 2026-05-20 22:51:46 +08:00
Wesley Liddick
51f72186a5
Merge pull request #2613 from wucm667/feat/api-key-usage-daily-detail
feat(usage): 用户 API Key 用量页支持按日明细
2026-05-20 16:55:42 +08:00
wucm667
3263ca63c7 feat(redeem): add redeem code batch update 2026-05-20 16:08:41 +08:00
wucm667
22ff1acde3 fix(auth): 停用/删除分组后阻断 API Key 2026-05-20 15:52:00 +08:00
wucm667
90b2b2a757 feat(usage): 用户 API Key 用量页支持按日明细 2026-05-20 15:48:38 +08:00
Wesley Liddick
378a0a6a61
Merge pull request #2599 from Arron196/feature/email-template-editor
feat: 添加邮件模板编辑器与通知邮件模板化
2026-05-20 15:12:57 +08:00
shaw
825834b5cb test(api-contract): fix admin/settings expected map missing openai_codex_user_agent 2026-05-20 15:07:07 +08:00
benjamin
55b13cd7b4 feat(settings): 添加邮件退订入口
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-20 11:09:18 +08:00
benjamin
88346b4d53 feat(admin): 添加邮件模板管理接口
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-20 11:09:18 +08:00
Wesley Liddick
74e35a0150
Merge pull request #2582 from wucm667/feat/channel-pricing-sync-models
feat(channels): 模型定价支持一键同步最新模型
2026-05-20 08:43:10 +08:00
Wesley Liddick
44c13e7a73
Merge pull request #2578 from wucm667/feat/payment-force-qrcode
feat(payment): 支持强制移动端统一使用二维码支付
2026-05-20 08:41:29 +08:00
wucm667
92ad68a314 feat(channels): 模型定价支持一键同步最新模型
从 LiteLLM 定价目录中读取指定平台的最新模型列表,
将尚未录入的模型以新定价条目(价格留空)的形式追加,
管理员只需点击同步最新模型按钮即可完成操作。

- backend/service: PricingService 新增 ListModelNamesByProvider
- backend/handler: ChannelHandler 新增 SyncPricingModels (GET /api/v1/admin/channels/pricing/sync-models)
- backend/routes: 注册新路由(在 /:id 通配符之前)
- backend/wire_gen: 手动更新 NewChannelHandler 调用
- frontend/api: channels.ts 新增 syncPricingModels
- frontend/i18n: zh.ts / en.ts 新增 5 个 key
- frontend/view: ChannelsView 定价区域标题行新增「同步最新模型」按钮
- tests: pricing_service_test + channel_handler_test 新增单元测试
2026-05-19 20:32:32 +08:00
name
2eb622f2f6 Remove ops retry replay storage 2026-05-19 19:37:41 +08:00
wucm667
e4c7927eff feat(payment): 支持强制移动端统一使用二维码支付 2026-05-19 18:22:12 +08:00
Wesley Liddick
32037cb17b
Merge pull request #2570 from wucm667/fix/ops-sla-exclude-ip-denied
fix(ops): 用户 IP 限制导致的 ACCESS_DENIED 不计入 SLA 错误
2026-05-19 16:03:16 +08:00
wucm667
271aba1abe fix(ops): exclude IP-denied access from SLA 2026-05-19 15:41:54 +08:00
DaydreamCoding
b19da9c7fe feat(dingtalk): 钉钉 OAuth 登录接入与 internal_only 用户属性同步
⚠️ 应用类型约束:当前实现仅支持「钉钉登录-企业内部应用」(DingTalk 开放平台
internal_app 类型)。第三方个人应用、第三方企业应用类型暂不支持——OAuth 流程
相同但 corp 校验、跨企业行为不同。backend 通过 DingTalkAppKind 校验对非
internal_app 类型 fail-closed(硬约束)。

钉钉 OAuth 登录主链
- 4 步 OAuth 链:ExchangeCodeForUserToken / GetUnionIdByUserToken /
  GetUserIdByUnionId / GetStaffInfoByUserId;app token 缓存
- pending session 机制持久化 OAuth 中间态;cookie-only token 持久化
- 三种分流:bind_login_required / email_completion / choose_account_action
- corp_restriction_policy 支持 none + internal_only;stale "whitelist" 在
  加载层与写入层均静默 coerce 为 none + slog.Warn
- bypass_registration 开关:企业内部模式豁免全局 REGISTRATION_DISABLED
- isReservedEmail / signup_source / canUnbindProvider / OAuth pending flow
  等横切点支持 dingtalk provider
- migration 136:4 表 CHECK 约束加入 'dingtalk' provider 值

internal_only 模式同步企业邮箱/姓名/部门到用户属性
- SyncCorpEmail / SyncDisplayName / SyncDept 三个独立开关 + 对应
  SyncXxxAttrKey 目标属性 key(默认 dingtalk_email / dingtalk_name /
  dingtalk_department);非 internal_only policy 在写入层与加载层均
  coerce 为 false,admin handler 与 setting_service 双层兜底
- 同步语义:首次注册写 users.username(昵称优先 → 企业姓名 fallback),
  之后每次登录刷新 3 个属性;空值也写入以覆盖旧值
- 邮箱三级 fallback:org_email > email > extension["企业邮箱"]
  (钉钉自定义字段 JSON)
- 部门路径递归向上拼接,跳过 dept_id=1 选首个真实子部门,剥离根组织名
- GetUnionIdByUserToken 同时返回 OIDC /contact/users/me 的 nick 字段;
  新增 GetDeptInfo 调用 OAPI /topapi/v2/department/get
- AuthHandler 注入 UserAttributeService;OAuth pending flow 在
  createPendingOAuthAccount / bindPendingOAuthLogin 分别派发到
  AfterRegistration(syncUsername=true)/ AfterLogin
- migration 137 seed dingtalk_email/name/department 三个用户属性定义

附带修复(同集成路径暴露的两个 OAuth 注册回归)
- LoginOrRegisterOAuthWithTokenPair 新建用户分支用 inferLegacySignupSource
  覆写 caller 显式传入的 signupSource,导致 dingtalk/linuxdo/oidc/wechat
  渠道授权按 email 渠道读取;改为只在 caller 未显式传入时回退邮箱推断
- mergeProviderDefaultGrantSettings 把 parse fallback 默认值
  (Concurrency=5 / Balance=0) 当作"未配置"哨兵,admin 显式设 5 时被误判
  退回全局默认(复现:全局默认 1 + 渠道默认并发 5 + grant_on_signup → 新
  用户实际 concurrency=1);去掉哨兵,admin 任何 >=0 值都覆盖 globalDefaults

前端
- DingTalk Login / Callback / EmailCompletion / ChoiceAccount / Error
  视图;router + auth API client
- admin SettingsView:corp policy radio(none / internal_only)+ bypass
  注册开关 + i18n;internal_only 下展示三同步开关 + 目标 attr key 下拉
  (拉取 user attribute definitions),展示 fieldEmail /
  qyapi_get_department_list 钉钉权限申请提示
- Profile:S1 主动绑定 / S5 解绑钉钉按钮 + 合成邮箱防自锁

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 15:27:47 +08:00
Wesley Liddick
03473d3ee8
Merge pull request #2554 from Arron196/feature/sync-upstream-models-pr
feat: 支持从上游同步账号可用模型列表
2026-05-19 14:42:47 +08:00
Wesley Liddick
ae4c738887
Merge pull request #2457 from wucm667/fix/openai-fast-policy-default-pass
fix: 默认透传 OpenAI service_tier
2026-05-19 14:34:37 +08:00
benjamin
36c00374d3 feat: expose upstream model sync admin API
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 19:01:33 +08:00
wucm667
e9637148dd fix(openai): pass service_tier by default 2026-05-14 16:45:31 +08:00
2ue
bb4c1abe28 Fix image billing size normalization 2026-05-12 15:21:31 +08:00
shaw
ea751f6515 test: update admin settings contract for Antigravity UA 2026-05-11 22:55:02 +08:00
shaw
9377c96746 fix: 让消息 cache_control 改写默认关闭 2026-05-11 21:26:41 +08:00
shaw
b23055af5b feat: add Airwallex payments and multi-currency support 2026-05-11 11:17:26 +08:00
shaw
fda1ed459d feat: 优化 OAuth 账号导入流程 2026-05-08 11:36:09 +08:00
shaw
8a835b22bb ci: fix lint and test failures 2026-05-07 19:26:18 +08:00
Wesley Liddick
e69319e747
Merge pull request #2224 from lyen1688/feat-email-oauth-github-google
feat: 增加 GitHub 和 Google 邮箱快捷登录
2026-05-07 10:07:28 +08:00
Wesley Liddick
d52da45363
Merge pull request #2202 from Michael-Jetson/main
新增三大功能:兑换码邀请返利、批量修改用户并发数、Markdown页面渲染
2026-05-07 09:35:14 +08:00
shaw
fff4a300c6 feat(risk-control): add content moderation audit 2026-05-07 09:14:47 +08:00
lyen1688
480fe27b31 fix: 更新邮箱 OAuth 单测契约 2026-05-06 17:19:20 +08:00
lyen1688
af550fa64e feat: 增加 GitHub 和 Google 邮箱快捷登录 2026-05-06 16:06:11 +08:00
Michael-Jetson
cf2d5067c3 fix(security): add JWT auth + visibility check to pages API
- GET /pages/:slug now requires JWT + checks custom_menu_items visibility
- GET /pages (list) is admin-only
- GET /pages/:slug/images/* uses visibility check without JWT (browser
  img tags cannot carry auth headers), blocks admin-only page images
- Frontend fetch adds Authorization header from authStore.token
- settingService nil guard changed to fail-closed (deny access)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 07:00:08 -07:00
Michael-Jetson
4cbd4932a0 feat: add redeem code affiliate rebate, batch concurrency API, and markdown page rendering
1. Redeem code affiliate rebate: balance-type redeem codes now trigger
   invite rebate for the inviter. Payment fulfillment uses context key
   to prevent double-rebate.

2. Batch concurrency update: new POST /admin/users/batch-concurrency
   endpoint supporting mode=set/add with all=true for all users.

3. Markdown page rendering: new GET /api/v1/pages/:slug API serves local
   .md files. Custom menu items with url="md:slug" render markdown with
   collapsible TOC sidebar, scroll spy, and copy buttons on code blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 06:44:37 -07:00
Wesley Liddick
37f7c7128c
Merge pull request #2120 from gaoren002/fix/rate-limit-429-cooldown-config
fix(rate-limit): make 429 fallback cooldown configurable
2026-05-05 19:46:11 +08:00
2ue
6faa344916 feat: add OpenAI image generation controls 2026-05-05 03:26:54 +08:00
lyen1688
6a41cf6a51 feat: add admin affiliate record pages 2026-05-03 20:33:13 +08:00
shaw
9d801595c9 test: 更新管理员设置契约字段 2026-04-30 13:48:27 +08:00
gaoren002
4b904c887c fix(rate-limit): make 429 fallback cooldown configurable 2026-04-30 03:01:39 +00:00
DaydreamCoding
30f55a1f72 feat(openai): OpenAI Fast/Flex Policy 完整实现(HTTP + WebSocket + Admin)
对称参照 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: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:15:09 +08:00
shaw
9b6dcc57bd feat(affiliate): 完善邀请返利系统
- 修复返利不到账的根因:tryClaimAffiliateRebateAudit 中 PostgreSQL 参数类型推断冲突
  - 补全 OAuth 注册路径(LinuxDo/OIDC/WeChat/Pending Flow)的邀请码绑定
  - 前端 OAuth 注册页面传递 aff_code 参数
  - 新增返利冻结期机制:可配置冻结时间,到期后自动解冻(懒解冻)
  - 新增返利有效期:绑定后 N 天内有效,过期不再产生返利
  - 新增单人返利上限:超出上限部分精确截断
  - 增强返利流程 slog 结构化日志,便于排查问题
  - 已邀请用户列表增加返利明细列
2026-04-26 12:42:35 +08:00
shaw
4e1bb2b445 feat(affiliate): add feature toggle and per-user custom invite settings
- 在系统设置「功能开关」中新增邀请返利总开关,默认关闭;
  关闭态:菜单隐藏、注册忽略 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 位系统码与新自定义码
- 补充单元测试与集成测试覆盖新方法、冲突路径与边界值
2026-04-25 20:22:07 +08:00
shaw
aa8ee33b0a refactor(affiliate): tighten DI and harden inviter code validation
- Drop SetAffiliateService setters and ProvideAuthService /
  ProvidePaymentService / ProvideUserHandler wrappers in favor of direct
  Wire constructor injection. AffiliateService has no back-edge to
  Auth/Payment/User, so the indirection was never required.
- Change RegisterWithVerification's variadic affiliateCode to a fixed
  parameter; adjust all call sites.
- Validate aff_code length and charset in BindInviterByCode before any
  DB lookup, eliminating timing-side-channel and useless DB roundtrips
  on malformed input.
- Make affiliate cache invalidation synchronous; surface Redis errors
  via the project logger instead of swallowing them in a detached
  goroutine.
- Add an integration test guarding cross-layer tx propagation in
  AccrueQuota and a unit test pinning the aff_code format rules.
2026-04-25 08:44:18 +08:00
VpSanta33
f03de00cb9 feat: add affiliate invite rebate flow and admin rebate-rate setting 2026-04-24 22:22:26 +08:00
Wesley Liddick
ff08f9d798
Merge pull request #1853 from gaoren002/fix/codex-image-generation-bridge
fix(openai): 完善 Codex 在 Responses 链路下的图片生成兼容性
2026-04-24 08:55:23 +08:00
gaoren002
5f41899705 fix: bridge codex image generation over responses 2026-04-23 15:13:57 +00:00
erio
5e060b2222 Merge remote-tracking branch 'upstream/main' into feat/channel-insights
# Conflicts:
#	backend/cmd/server/wire_gen.go
2026-04-23 22:30:45 +08:00