Commit Graph

2284 Commits

Author SHA1 Message Date
IanShaw027
07f23aaa7d fix wxpay config contract and h5 scene info 2026-04-21 08:35:53 +08:00
IanShaw027
2626e8f22c fix legacy email identity backfill grants 2026-04-21 08:28:48 +08:00
IanShaw027
09351e9459 fix auth completion and payment resume hardening 2026-04-21 08:23:26 +08:00
IanShaw027
ebe7524415 fix profile activity and migration remediation 2026-04-21 02:08:56 +08:00
IanShaw027
a27a7add3d fix payment resume result consistency 2026-04-21 02:08:34 +08:00
IanShaw027
e12599c1b9 fix settings auth source default persistence 2026-04-21 02:08:04 +08:00
IanShaw027
7c6491c2d3 fix auth pending session hardening 2026-04-21 01:45:25 +08:00
erio
88decb6e0c refactor(channels): tighten types and error paths per second review
- service: drop groupRepo nil guard (DI must inject), switch SupportedModels to SliceStable to match doc
- frontend: reuse user-side DTO types in SupportedModelChip/AvailableChannelsTable instead of duplicating shapes; narrow admin statusLabel param to ChannelStatus
- tests: replace nil-groupRepo case with ListAll/ListActive error propagation and BillingModelSource default-backfill coverage
2026-04-21 01:42:18 +08:00
IanShaw027
1d8432b8a4 fix: harden payment resume and wxpay webhook routing 2026-04-21 01:40:56 +08:00
IanShaw027
0a461d8248 fix: harden auth identity legacy migrations 2026-04-21 01:30:37 +08:00
erio
365ef1fdf7 refactor(channels): consolidate pricing index, tighten types, polish DTOs
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).
2026-04-21 01:05:14 +08:00
IanShaw027
ea27ac6fd7 fix: unify email identity sync and retry first-bind defaults 2026-04-21 01:00:59 +08:00
IanShaw027
7a9488ff37 Add legacy identity safety remediation migration 2026-04-21 00:59:20 +08:00
IanShaw027
067eb23d8e Tighten WeChat OAuth capability mode selection 2026-04-21 00:46:40 +08:00
IanShaw027
e4fe9fae2a Fix profile refresh identity compatibility 2026-04-21 00:42:55 +08:00
IanShaw027
55e8dd550a Tighten WeChat payment resume flow 2026-04-21 00:33:23 +08:00
IanShaw027
1521d50399 fix: apply email first-bind defaults on legacy login 2026-04-21 00:31:52 +08:00
erio
654cfb6480 feat(channels): add "Available Channels" aggregate view
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
2026-04-21 00:27:10 +08:00
erio
c46744f366 refactor(channel-monitor): tighten runner lifecycle + add unit tests
- pool 改在 NewChannelMonitorRunner 构造时初始化,消除 Start 在 mu 内
  赋值、fire/Stop 在 mu 外读取的竞态隐患
- Schedule 在 !started 时由静默 return 改为 slog.Warn,错过的调度可见
- Schedule 在 interval<=0 时升为 slog.Error:Create/Update validateInterval
  已保证不可达,真触发即数据/校验链 bug
- 抽出 monitorRunnerSvc 内部接口(仅 ListEnabledMonitors+RunCheck),
  生产 *ChannelMonitorService 自然满足;runner 单元测试可注入轻量 stub
- 新增 channel_monitor_runner_test.go(10 个用例,//go:build unit):
  覆盖 Schedule/Unschedule/Start/Stop 生命周期、in-flight 槽对称释放、
  Stop 等待正在执行的 RunCheck 退出(无游离 goroutine)

启动失败的恢复策略:保持现状(log+return)。CLAUDE.md 明确"配置应保证启动
成功(必填项校验+正确数据校验)",validate{Provider,Interval,Endpoint,
APIKey,PrimaryModel} 已在 Create/Update 全部覆盖;DB 不可用是基础设施问题,
不该靠应用层无限重试兜底。
2026-04-22 20:08:31 +08:00
erio
c2f9ad7a21 refactor(channel-monitor): event-driven scheduler + sidebar cleanup
后端 - ChannelMonitorRunner 重写为事件驱动调度
- 删除 5 秒轮询架构(每次 ListEnabled + listDueForCheck 全表扫描),
  改为每个 enabled monitor 一个独立 goroutine + ticker(按各自 IntervalSeconds)
- 新增 MonitorScheduler 接口,service 通过 setter 注入避免依赖环
- ChannelMonitorService.Create/Update/Delete 直接回调 scheduler.Schedule/Unschedule
- runner.Start 一次性加载所有 enabled monitor 建立任务表
- 新建/启用立即触发首次检测,禁用/删除即时撤销 ticker
- 保留 inFlight 去重 + pond 池并发上限 + 全局开关每次 fire 实时校验
- 删除 listDueForCheck / monitorTickerInterval / monitorListDueTimeout

前端 - 可用渠道改为用户级菜单
- 从 adminNavItems 移除 /available-channels(admin 主菜单不再重复出现)
- buildSelfNavItems 始终包含可用渠道入口,普通用户主菜单和
  管理员"我的账户"区都能看到
2026-04-22 19:17:08 +08:00
erio
a7415d4d2e feat(monitor): 30-day raw retention + timeline 4-tier style + CC template seed + JSON format button
- History retention 1d → 30d(60s × 30d ≈ 43200 行/model,PG 无压力);
  ComputeAvailability* 不再 UNION rollup 表,直接扫 histories 精度更高。
- Timeline bar 四级高度+颜色双重编码:operational 高+绿 / degraded 中+黄 /
  failed+error 短+红 / 未测试 很短+灰。
- migration 113 seed「Claude Code 伪装」模板(ON CONFLICT DO NOTHING)。
  user_id 用 legacy 格式(user_<64hex>_account_<uuid>_session_<uuid>),
  避免新版 JSON 字符串内嵌 JSON 在编辑器里一长串 \" 难读。
- MonitorAdvancedRequestConfig 加「格式化」按钮 + white-space:pre
  让 body textarea 对长字符串不压扁。
2026-04-21 15:24:48 +08:00
erio
6925ac25c4 feat(channel-monitor): apply template via subset picker; CC 2.1.114 baseline doc
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.
2026-04-21 14:39:19 +08:00
erio
a296425994 feat(channel-monitor): request templates with snapshot apply + headers/body override
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
2026-04-21 14:14:49 +08:00
erio
b363bff1d8 feat(channel-monitor): preserve upstream error body
Monitor:
- callProvider now returns both textPath-extracted text and raw body;
  runCheckForModel uses rawBody on non-2xx so history.message stops being
  "upstream HTTP 503: " with empty body (gjson textPath produces "" for
  error responses like {"error":{"message":"No available accounts..."}})
- truncateForErrorBody collapses whitespace then caps at 300 bytes
  (monitorErrorBodySnippetMaxBytes); final truncateMessage still enforces
  the 500-byte DB column cap

Frontend:
- MonitorFormDialog: primary_model input text color and ModelTagInput tags
  now both track form.provider (via new getPlatformTextClass + existing
  getPlatformTagClass with platform prop).

(cherry-picked from 1d3b0418; dropped gateway_handler logging改动,不在本 PR 范围)
2026-04-21 11:59:11 +08:00
erio
ef6ec8a15a fix(channel-monitor): drop soft delete, refactor feature flag to declarative form
### 后端修复:日志表不该用软删除

channel_monitor_histories / channel_monitor_daily_rollups 都是日志/聚合表,
没有恢复需求。110 里加的 SoftDeleteMixin 会让 DELETE 自动变成 UPDATE deleted_at,
导致行和索引只增不减,徒增磁盘占用和查询成本。

改回分批物理删(参考 OpsCleanupService.deleteOldRowsByID 模板):

- ent schema 移除 SoftDeleteMixin,重新 go generate
- repo 新增 deleteChannelMonitorBatched 辅助 + 两条 prune SQL 常量
  (WITH batch AS SELECT id LIMIT 5000 → DELETE IN batch)
- DeleteHistoryBefore / DeleteRollupsBefore 改调分批 raw SQL
- 移除 ComputeAvailability / ComputeAvailabilityForMonitors / UpsertDailyRollupsFor /
  ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors 等
  raw SQL 中的 deleted_at IS NULL 过滤
- UpsertDailyRollupsFor 的 ON CONFLICT 去掉 deleted_at = NULL 重置
- migration 111 DROP COLUMN deleted_at + 对应索引(110 已部署但 maintenance
  首跑在次日 02:00,此时尚无业务数据在依赖软删除)

### 前端重构:feature flag 声明式 + 复用

AppSidebar.vue 里 7 处 `...(flag ? [item] : [])` 样板代码删光,改为 NavItem 加
featureFlag?: () => boolean | undefined 字段,加一个 applyFeatureFlags 递归
过滤(含 children)。语义统一为 `!== false`(宽容策略,undefined 时默认显示,
避免 public settings 未加载完成时菜单闪烁消失 — 对应用户反馈"刷新后菜单消失
要去保存设置才回来")。

- 集中声明 4 个 flag getter:flagChannelMonitor / flagPayment /
  flagOpsMonitoring / flagAdminPayment
- 提取 buildSelfNavItems 复用用户端主菜单和管理员"我的账户"子菜单
- 未来新增开关:在统一位置加一个 flag getter + 给对应 NavItem 加字段
  (不用再动渲染逻辑)

bump 0.1.114.29
2026-04-23 17:31:15 +08:00
erio
8cf83c984e feat(channel-monitor): aggregate history to daily rollups + soft delete
明细只保留 1 天,超过 1 天聚合到新表 channel_monitor_daily_rollups(按
monitor_id/model/bucket_date 维度),聚合保留 30 天。两张表都用 SoftDeleteMixin
软删除(DELETE 自动改为 UPDATE deleted_at = NOW())。

聚合 + 清理任务由 OpsCleanupService 的 cron 统一调度,与运维监控的清理共享
schedule(默认 0 2 * * *)和 leader lock。ChannelMonitorRunner 的 cleanupLoop
被移除,只保留 dueCheckLoop。

读取路径 ComputeAvailability* 改为 UNION 明细(今天 deleted_at IS NULL)+
聚合(过去 windowDays 天 deleted_at IS NULL),SUM(ok)/SUM(total) 自然加权
计算可用率,AVG latency 用 SUM(sum_latency_ms)/SUM(count_latency)。

watermark 表 channel_monitor_aggregation_watermark 单行(id=1),记录
last_aggregated_date,重启后从该日期 +1 继续聚合,首次为 nil 则从
today - 30d 开始回填,单次最多 35 天上限避免长事务。

raw SQL 的 ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors
都补上 deleted_at IS NULL 过滤(SoftDeleteMixin interceptor 只对 ent query 生效)。

bump version to 0.1.114.28

GroupBadge 在 MonitorKeyPickerDialog 中复用平台主题色 + 倍率/专属倍率
(顺手优化)。
2026-04-21 10:10:56 +08:00
IanShaw027
bf3ef2d19a add admin user last used support 2026-04-21 00:22:17 +08:00
erio
7da5124067 feat(channel-monitor): add feature switch settings + fix extra_models save
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
2026-04-21 00:21:29 +08:00
IanShaw027
beeab54ae3 Implement latest-used user repo queries 2026-04-21 00:17:48 +08:00
IanShaw027
b79052aaf2 Decouple email sync tests from local stubs 2026-04-21 00:16:06 +08:00
IanShaw027
16be82b959 fix payment visible methods and resume recovery 2026-04-21 00:14:05 +08:00
IanShaw027
5d58c7c6fb Add auth identity legacy backfill and email sync 2026-04-21 00:13:40 +08:00
IanShaw027
9204145746 Close profile identity and avatar loop 2026-04-21 00:11:03 +08:00
IanShaw027
9e84e2fd2b fix: persist admin payment visibility and scheduler settings 2026-04-21 00:05:17 +08:00
IanShaw027
f83fd59dca Refine payment UX for wallet flows 2026-04-21 00:05:09 +08:00
erio
a1425b457d feat(channel-monitor): redesign user dashboard as card grid
Reference check-cx UI: INTELLIGENCE MONITOR hero + 3-column card grid
with 60-point timeline bars.

Backend:
- Add PrimaryPingLatencyMs + Timeline[60] to UserMonitorView
- ListRecentHistoryForMonitors: batch CTE + ROW_NUMBER() window query
- indexLatestByModel / indexAvailabilityByModel helpers

Frontend:
- 7 new components: ProviderIcon, MonitorMetricPair, MonitorAvailabilityRow,
  MonitorTimeline, MonitorHero, MonitorCard, MonitorCardGrid
- ChannelStatusView 381→~180 lines (delegated to subcomponents)
- AbortController reload concurrency protection
- HSL 0-120° availability color mapping
- Replace emoji with Icon component (bolt / globe)
- i18n: monitorCommon.* shared namespace, channelStatus.hero.*

Bump VERSION to 0.1.114.24
2026-04-20 23:38:59 +08:00
IanShaw027
7ef7fd19e7 fix: restore wechat payment oauth and jsapi flow 2026-04-20 23:34:57 +08:00
IanShaw027
6f00efa350 fix: support legacy payment method aliases 2026-04-20 23:05:29 +08:00
IanShaw027
724f8e89a1 feat: resolve auth identity migration reports 2026-04-20 22:29:21 +08:00
IanShaw027
452e55a53c feat: add admin auth identity repair binding 2026-04-20 22:22:14 +08:00
IanShaw027
3bd3027251 feat: expose auth identity migration reports 2026-04-20 22:05:33 +08:00
erio
bbc4aed3d9 fix(openai): 移除已下线 Codex 模型并修复归一化兜底副作用
- backend: 删除 gpt-5 / 5.1 / 5.1-codex / 5.1-codex-max / 5.1-codex-mini / 5.2-codex / 5.4-nano 的内置映射与 DefaultModels 条目
- backend: normalizeCodexModel 默认兜底由 gpt-5.1 改为 gpt-5.4,gpt-5.3-codex-spark 独立保留映射
- backend: 修复 isOpenAIGPT54Model 与 shouldAutoInjectPromptCacheKeyForCompat 对 claude / gpt-4o 的误判(之前依赖 gpt-5.1 作为非 GPT 族的隐式 sentinel,改后需要显式前缀守卫)
- backend: 清理 billing_service 中已不可达的 fallback 价格与 switch 分支
- frontend: 从白名单、OpenCode 配置、预设映射中移除已下线模型
- 同步更新所有相关单测

Refs: #1758, parallels upstream #1759 but adds downstream guard fixes
2026-04-20 22:01:41 +08:00
IanShaw027
aaf4946b27 fix: normalize pending oauth email lookups 2026-04-20 21:59:03 +08:00
IanShaw027
31d0183d45 fix: normalize repository email lookups 2026-04-20 21:51:57 +08:00
IanShaw027
b309822199 fix: tighten legacy payment provider resolution 2026-04-20 21:46:24 +08:00
IanShaw027
422f60a145 fix: normalize legacy wechat auth identity keys 2026-04-20 21:42:35 +08:00
IanShaw027
f65429145e fix: route legacy linuxdo users to account binding 2026-04-20 21:31:05 +08:00
IanShaw027
5adefb466b fix: finalize oauth identity bindings 2026-04-20 21:24:33 +08:00
IanShaw027
bdcd3d87e5 fix: resolve unique legacy payment providers 2026-04-20 21:09:38 +08:00
IanShaw027
32059ae9d5 fix: backfill email identities on successful login 2026-04-20 20:58:19 +08:00