Compare commits

...
573 Commits
Author SHA1 Message Date
Wesley Liddick ba1f130d28 Merge pull request #3864 from fengshao1227/fix/frontend-i18n-and-site-logo-sanitize
fix(frontend): 补齐 en 语言包缺失 key 并对 site_logo 统一应用 sanitizeUrl
2026-07-09 17:35:00 +08:00
Wesley Liddick 668c79ea86 Merge pull request #3824 from bestony/worktree/brave-valley-9578
feat(keys): sort by current concurrency
2026-07-09 17:25:40 +08:00
li 15c59be787 fix(frontend): 补齐 en 语言包缺失 key 并对 site_logo 统一应用 sanitizeUrl 2026-07-09 17:22:04 +08:00
shaw 49fb18e073 Merge origin/main into worktree/brave-valley-9578
Resolve conflict in api_key_repo.go: keep both ListAllByUserID (this PR)
and attachLastUsedIPs/latestUsageLogIPs (main), and have ListAllByUserID
attach last-used IPs so the current_concurrency sort path keeps the
last_used_ip column populated.
2026-07-09 17:17:16 +08:00
Wesley Liddick 14f7a7e603 Merge pull request #3859 from fengshao1227/fix/messages-response-failed-handling
fix(messages): /v1/messages 非 cyber response.failed 补全 failover 和错误回写
2026-07-09 17:09:27 +08:00
li 104fd2b6ec fix(messages): /v1/messages 非 cyber response.failed 补全 failover 和错误回写 2026-07-09 16:49:13 +08:00
Wesley Liddick 0438057c0b Merge pull request #3816 from Vibeone/fix/ops-inband-sse-error-logging
fix(ops): 记录固化 200 SSE 流上的就地错误,修复流内限流不进错误看板
2026-07-09 16:35:29 +08:00
Wesley Liddick addcb34e14 Merge pull request #3851 from fengshao1227/fix/responses-to-anthropic-instructions-and-developer-role
fix(apicompat): ResponsesToAnthropicRequest 补全 instructions 字段并映射 developer role
2026-07-09 16:34:36 +08:00
Wesley Liddick 843dcddea3 Merge pull request #3853 from fengshao1227/fix/messages-transport-failover
fix(messages): /v1/messages 传输层错误对齐 failover 链路
2026-07-09 16:34:00 +08:00
Wesley Liddick c842c3166c Merge pull request #3847 from heathermhuang/codex/grok-45-official-support
Add official Grok 4.5 support
2026-07-09 16:07:42 +08:00
Wesley Liddick c3df469377 Merge pull request #3855 from Wei-Shaw/fix/grok-video-per-second-billing
fix: bill Grok video per second and harden video usage logging
2026-07-09 16:07:23 +08:00
Heatherm Huang 243678e166 Fix Grok 4.5 alias test expectations 2026-07-09 15:50:58 +08:00
li 7468427e44 fix(messages): /v1/messages 传输层错误对齐 failover 链路,不再直接 502
Fixes #3850 (part 2)
2026-07-09 15:49:10 +08:00
shaw d4952154ff fix: bill Grok video per second and harden video usage logging
Follow-up fixes for the #3775 audit findings:

- Bill Grok video generation per second of output, matching the xAI rate
  card: parse the request duration (1-15s, upstream default 8s) and compute
  cost as per-second price x duration x count. The built-in rate card values
  were already xAI per-second prices but were previously charged per video,
  undercharging up to 15x with a user-controlled duration.
- Group video_price_* fields are now documented and surfaced as per-second
  rates (USD/s); admin UI labels, placeholders and hints updated accordingly.
- Persist video_count/video_resolution/video_duration_seconds on usage_logs
  (migration 172) so video billing is auditable, and exempt any row with
  video_count > 0 from the image_size check constraint: a video billed via a
  token-mode channel price produces billing_mode='token' with image_count=1
  and no image_size, which the previous constraint rejected, dropping the
  whole billing transaction.
- Only refetch the group in apiKeyWithFreshGroupMediaPricing when the group
  object actually looks like it is missing media pricing fields (both media
  multipliers zero and all prices nil, impossible for a normally loaded
  group), removing a per-usage DB query for groups without overrides.
- Frontend: drop the unused admin.groups.mediaPricing locale block, map
  cleared price inputs to null (create) / -1 (update, cleared via backend
  normalizePrice) instead of sending "" that failed *float64 unmarshalling,
  and align video price placeholders with the text-to-video default model
  (grok-imagine-video 0.05/0.07, 1080p only on 1.5 at 0.25).
2026-07-09 15:38:59 +08:00
li 1785509873 fix(apicompat): ResponsesToAnthropicRequest 补全 instructions 字段并映射 developer role
Fixes #3850
2026-07-09 15:34:10 +08:00
Wesley Liddick 9ba0fb3084 Merge pull request #3775 from heathermhuang/codex/grok-media-pricing-labels
fix: add Grok video pricing controls
2026-07-09 15:03:38 +08:00
Wesley Liddick b6d2df24d8 Merge pull request #3800 from Ge-limin/feat/codex-models-manifest
feat: Codex 客户端模型清单(manifest)透传接口
2026-07-09 14:40:23 +08:00
Wesley Liddick 52da41fd6e Merge pull request #3809 from hongheshan-svg/fix/upstream-anthropic-429-fallback
fix(ratelimit): Anthropic 无 reset 头的 429 也进入兜底冷却,避免账号永不冷却导致的 429 循环
2026-07-09 14:40:10 +08:00
Wesley Liddick dfff28ab05 Merge pull request #3843 from InCerryGit/fix/issue-3540-lenient-json-limit
fix(gateway): cap lenient JSON normalization
2026-07-09 14:39:55 +08:00
Wesley Liddick e2c662e965 Merge pull request #3840 from feitianbubu/fix/crs-sync-frontend-timeout
fix(crs-sync): raise frontend sync timeout to 180s
2026-07-09 14:39:39 +08:00
Wesley Liddick 9392d1fc43 Merge pull request #3841 from fengshao1227/fix/html-escape-site-name-and-sanitize-doc-url
fix(security): HTML-escape site_name 并对 doc_url 统一应用 sanitizeUrl
2026-07-09 14:39:26 +08:00
Wesley Liddick 9ba6f3c2b0 Merge pull request #3845 from wucm667/feat/groups-quota-used-display
feat: 配额操控列表显示已用额度
2026-07-09 14:39:12 +08:00
Wesley Liddick a57157b9fa Merge pull request #3822 from wucm667/feat/api-key-last-used-ip
feat(api-key): 展示 API Key 最近使用 IP
2026-07-09 14:38:01 +08:00
Wesley Liddick 0118fa3ce2 Merge pull request #3721 from CHOS1N11111/codex/add-response-format-compat
Add response_format compatibility mapping
2026-07-09 14:37:46 +08:00
Wesley Liddick 105ac31c37 Merge pull request #3781 from fengshao1227/fix/openai-oauth-empty-mapping-model-guard
fix(scheduler): 空 model_mapping 的 OpenAI OAuth 账号不再吸收全部模型
2026-07-09 14:37:33 +08:00
Wesley Liddick 7302be4d13 Merge pull request #3833 from superman2003/fix/security-and-frontend-hardening
fix(gateway,frontend): 修复 Gemini 鉴权绕过与前端支付/会话缺陷
2026-07-09 14:37:19 +08:00
Wesley Liddick 636c452535 Merge pull request #3836 from ShuYeJang/main
fix(billing): 渠道定价覆盖写穿 fallbackPrices 共享指针导致全局计费污染
2026-07-09 14:37:05 +08:00
Heatherm Huang cccba9a82e Add official Grok 4.5 support 2026-07-09 14:26:10 +08:00
Wesley Liddick 554d47d04a Merge pull request #3846 from Wei-Shaw/chore/go-toolchain-1.26.5
chore: Go 工具链升级 1.26.4 → 1.26.5——修复 stdlib 漏洞并补齐 CI 版本引用
2026-07-09 14:16:48 +08:00
shaw 25a7169601 chore: Go 工具链升级 1.26.4 → 1.26.5——修复 stdlib 漏洞并补齐 CI 版本引用
- backend/go.mod 工具链 1.26.5:修复 stdlib crypto/tls 漏洞(GO-2026-5856)
- 同步全部构建/校验点的硬编码版本:根 Dockerfile、backend/Dockerfile、
  deploy/Dockerfile 基础镜像;backend-ci / release / security-scan 三个
  workflow 的 go version 校验
2026-07-09 14:06:57 +08:00
wucm667 54859022aa feat: show used quota in groups list 2026-07-09 11:45:59 +08:00
InCerry 53a5c45bd8 fix(gateway): cap lenient json normalization
Fixes #3540
2026-07-09 11:15:52 +08:00
li bfb827b879 fix(security): HTML-escape site_name 并对 doc_url 统一应用 sanitizeUrl
Refs #3839 (第 8、12 点)
2026-07-09 10:03:49 +08:00
feitianbubu 4e5be8f75e fix(crs-sync): raise frontend sync timeout to 180s 2026-07-09 09:45:47 +08:00
superman2003andCursor 29a5fcd25e fix(gateway,frontend): 修复鉴权绕过与前端支付/会话缺陷
后端:
- Gemini /v1beta 鉴权中间件补齐主中间件的授权校验: API Key 的 IP 白/黑名单、
  专属分组授权、运行时过期/配额二次检查, 修复经 Gemini 端点绕过 IP ACL、
  越权访问专属分组、以及状态未刷新时的配额/有效期绕过窗口。
- 粘性会话等待计划分支改走 newSelectionResult 以 hydrate 账号凭证, 修复调度
  快照中账号凭证被剥离导致等待路径转发鉴权失败。
- SSE 流式转发客户端断开时不再 break 跳过当前事件 usage 合并, 修复少计费。
- Forward 对 nil gin.Context 的防御补齐; 上游错误体读取失败时记录日志避免静默。

前端:
- logout 将本地会话清理移入 finally, 服务端吊销失败也保证本地登出。
- Stripe 弹窗轮询改用正确的 auth_token 键并加防重入; 收到 INIT 后清除兜底
  超时定时器, onUnmounted 清理 message 监听器。
- token 刷新请求补充 30s 超时, 避免挂起导致请求队列与 loading 永久卡死。
- 路由守卫在公共设置未加载时先 await fetchPublicSettings, 避免 payment/
  risk_control 被误判为未启用而错误拦截。
- 支付状态轮询回调补充防重入与终态守卫。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 09:06:56 +08:00
Wesley Liddick 309fda2904 Merge branch 'fix/channel-pricing-pollutes-fallback-tests' 2026-07-08 19:23:55 +00:00
Wesley Liddick 88581912ba test: add regression tests for fallback pricing pollution 2026-07-08 19:23:38 +00:00
Wesley Liddick f029945cfc Merge branch 'fix/channel-pricing-pollutes-fallback' 2026-07-08 18:39:19 +00:00
Wesley Liddick 4a30397623 fix: prevent channel pricing overrides from mutating shared fallback pricing 2026-07-08 18:37:33 +00:00
Bestony@Homelab 5debe1db33 feat(keys): sort by current concurrency 2026-07-08 15:51:22 +08:00
wucm667 7a11b39d6d fix(api-key): check usage log rows close 2026-07-08 15:36:12 +08:00
wucm667 e0d149d511 feat(api-key): show last used IP 2026-07-08 15:26:54 +08:00
Heatherm Huang 3b206cc639 test: preserve grok video resolution forwarding 2026-07-08 13:50:49 +08:00
Heatherm Huang 889b657451 test: accept casted video billing constraint 2026-07-08 13:50:49 +08:00
Heatherm Huang 376e03ded1 fix: update Grok media default rate card 2026-07-08 13:50:49 +08:00
Heatherm Huang 4d702e3234 fix: split Grok image and video pricing 2026-07-08 13:50:49 +08:00
Heatherm Huang 890cc2bb98 fix: clarify Grok media pricing controls 2026-07-08 13:44:09 +08:00
Eyre921 5aba53d542 fix(ops): 记录固化 200 SSE 流上的就地错误,修复流内限流不进错误看板
流式请求一旦 flush 了 keepalive ping,HTTP 状态码即固化为 200;此后
出现的错误(等待并发槽位超时后回退的限流、Wait 后二次计费校验失败、
流开始后才无可用账号等)只能就地以 SSE error 帧回传。而 ops_error_logger
以 status>=400 为采集触发条件,这类挂在 200 流上的失败此前会在错误看板里
完全隐形——客户端能收到 rate_limit_error,但网关侧没有任何错误记录可供排障。

- service: 新增 OpsStreamError 上下文 + MarkOpsStreamError/GetOpsStreamError,
  采用「首个标记生效」保留根因错误,避免被后续通用兜底帧覆盖。
- handler: handleStreamingAwareError 在 streamStarted 分支标记流内错误。
- handler: OpsErrorLoggerMiddleware 在 status<400 且无上游错误上下文时,
  据标记补记一条错误日志;分级用 IntendedStatus(如并发限流 429),
  StatusCode 仍记 wire 的 200。上游透传错误已由 upstream-context 分支落库,
  故此路径不重复记录。
- 补充单测覆盖补记、no-op、skip_monitoring 跳过与首个标记生效。
2026-07-08 02:51:51 +00:00
Wesley Liddick 6f43986c37 Merge pull request #3811 from jianjianai/hotfix/admin-scheduler-score-opt-in
fix(admin): 管理员账号列表默认关闭调度权值计算以降低负载
2026-07-08 10:22:10 +08:00
Wesley Liddick f407d6a044 Merge pull request #3804 from fengshao1227/fix/compact-body-signal-routing
fix(compact): 识别 /v1/responses body 中的 compaction_trigger 信号
2026-07-08 10:12:58 +08:00
shaw a56eb5b4dc fix(compact): body-signal 提升上移到 handler 层并对齐 path-based 链路
合并 main 解决拆分冲突后,将原先 Forward 内的 body-signal 提升重构到
handler 的 compact 归一化入口之前,修复原方案的四个问题:

- reqStream 未重推导:body-signal 原始请求带 stream:true,Forward 级提升
  后 compact 上游返回 JSON(Accept: application/json)却被流式 handler
  解析,"stream ended before a terminal event" 会触发最多
  max_account_switches 次换号 failover,且每次都白烧一次上游 compact 配额;
  handler 级提升让白名单归一化先删除 stream,reqStream 自然为 false。
- requireCompact 调度过滤失效:原方案 path 改写发生在 requireCompact 判定
  之后,调度器不会过滤不支持 compact 的账号;现在改写先于该判定。
- passthrough / Grok / chat-completions 桥接分支位于 Forward 检测点之前,
  passthrough 账号完全无法命中;handler 级改写对所有分支生效。
- body 归一化口径不一致:body-signal 现在与 path-based 一样走白名单归一化
  (prompt_cache_key 等一并删除),而非仅依赖 OAuth 黑名单转换。

检测函数导出为 HasCompactionTriggerInInput 供 handler 使用,保留原 PR 的
7 个单测;新增 6 个 handler 级回归测试(提升、codex 别名路由、尾斜杠、
子路径不误伤、path-based 无双重后缀、普通请求不受影响)。

Refs #3777
2026-07-08 10:04:14 +08:00
shaw a855317624 Merge remote-tracking branch 'origin/main' into fix/compact-body-signal-routing
# Conflicts:
#	backend/internal/service/openai_gateway_service.go
2026-07-08 09:57:31 +08:00
Wesley Liddick f4e8bec54c Merge pull request #3803 from fengshao1227/fix/sidebar-scroll-position-persist
fix(frontend): 路由切换后保持侧边栏滚动位置
2026-07-08 09:53:05 +08:00
Wesley Liddick 6631cbad67 Merge pull request #3812 from Wei-Shaw/refactor/split-bloated-files-r2
refactor: 纯移动拆分 8 个后端大文件与 i18n 语言包
2026-07-08 09:03:44 +08:00
shaw d0bcaa8b09 test(i18n): 新增语言包 spread 组装键冲突守卫测试 2026-07-08 08:49:23 +08:00
shaw bb5d2e84a1 refactor(handler): 纯移动拆分 setting_handler.go(3957→468行) 2026-07-08 08:49:22 +08:00
shaw f013bc1141 refactor(service): 纯移动拆分 admin_service.go(4409→642行) 2026-07-08 08:49:22 +08:00
shaw 2a4c28e8f5 refactor(service): 纯移动拆分 antigravity_gateway_service.go(4664→639行) 2026-07-08 08:49:22 +08:00
shaw d0f669338b refactor(service): 纯移动拆分 openai_ws_forwarder.go(4675→399行) 2026-07-08 08:49:21 +08:00
shaw db3bd9971e refactor(repository): 纯移动拆分 usage_log_repo.go(4701→212行) 2026-07-08 08:49:21 +08:00
shaw 4d23ad4bac refactor(service): 纯移动拆分 openai_gateway_service.go(4872→1095行) 2026-07-08 08:49:21 +08:00
shaw 50043b1176 refactor(service): 纯移动拆分 setting_service.go(5471→263行) 2026-07-08 08:49:20 +08:00
shaw 084d26cbd2 refactor(service): 纯移动拆分 gateway_service.go(7294→1289行) 2026-07-08 08:49:20 +08:00
shaw d9e514f982 refactor(i18n): 拆分 zh/en 语言包为域模块(深比较等价校验通过) 2026-07-08 08:49:19 +08:00
Wesley Liddick 317339e716 Merge pull request #3813 from Wei-Shaw/fix/bump-aws-sdk-eventstream-vuln
fix(deps): 升级 AWS SDK 修复 GO-2026-5764(backend-security CI 失败)
2026-07-08 08:48:20 +08:00
shaw a4f942d8a9 fix(deps): 升级 AWS SDK 修复 govulncheck 报告的 GO-2026-5764
eventstream v1.7.5→v1.7.8、service/s3 v1.96.2→v1.97.3(含关联 internal 模块),
修复 EventStream 解码器 DoS 漏洞,恢复 backend-security CI。
2026-07-08 08:37:29 +08:00
jjaw 6ae5fc31b3 fix(admin): gate scheduler score calculation 2026-07-08 06:58:35 +08:00
zhengshanandClaude Opus 4.8 3866da508f fix(ratelimit): Anthropic 无 reset 头的 429 也进入兜底冷却
此前 Anthropic 429 若响应头无窗口重置时间(如 Extra usage required),
被视为"非真实限流"直接跳过标记。后果:账号永不冷却,调度器让每个
请求反复撞同一批持续 429 的账号——failover 预算烧尽后客户端稳定
收到 429(生产实测:一次请求 1.3s 内连换 4 账号全 429,而 DB 中
这些账号的限流状态毫无更新)。

改为与其他平台一致走 apply429FallbackRateLimit 秒级兜底回避:
- 默认 5s,管理端 RateLimit429CooldownSettings 可调 1~7200s
- 管理端关闭该设置即恢复旧行为(不标记)
- 日志 reason 标记为 anthropic_no_reset_time 便于运营区分

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:19:23 +08:00
Wesley Liddick e8e234252a Merge pull request #3807 from Wei-Shaw/refactor/gateway-cc-pipeline-and-file-split
refactor(gateway): 抽取 CC forwarder 公共管线并拆分两大 service 文件
2026-07-07 23:08:55 +08:00
shaw d754be0d8e refactor(gateway): 抽取 CC forwarder 公共管线并拆分两大 service 文件
PR #3802 遗留项:三个 CC forwarder(raw 直转 / responses 回退 / messages
回退)间约 85% 重复的 HTTP 管线与 SSE 循环骨架收敛到新文件
openai_gateway_cc_pipeline.go,messages / chat_completions 两条主路径中
逐字相同的错误处理块一并接入。各路径有意保留的行为差异(GLM effort
归一化、fast policy 适用范围、ClientDisconnect 语义、Grok 分支等)留在
调用方,未强行统一。

同时对两个最臃肿的网关文件做纯移动拆分(零语义变化,逐字节校验):

- openai_gateway_service.go 7821→4872 行:
  调度 → openai_gateway_scheduling.go
  passthrough → openai_gateway_passthrough.go
  用量/计费/codex 快照 → openai_gateway_usage.go
- gateway_service.go 10912→7294 行:
  调度 → gateway_scheduling.go
  Anthropic APIKey 直通 → gateway_anthropic_passthrough.go
  Bedrock → gateway_bedrock.go

回归保障:全部搬迁块与 HEAD 逐字节 diff 一致;留存文件经"HEAD 减去搬迁
范围"重构比对,差异仅为 goimports 移除的孤儿 import;定向单测
(fallback/raw/cyber/grok/transport/调度/用量/广域 Forward-Handle 扫描)
全绿;另经独立对抗审计确认零行为差异。
2026-07-07 22:14:46 +08:00
li 2dd2be9922 fix(compact): 识别 /v1/responses body 中的 compaction_trigger 信号
Codex remote compact v2 可以把 compact 触发器作为 input item
(type=compaction_trigger)嵌入普通 POST /v1/responses 请求体,
而非直接调用 /v1/responses/compact。此前 isCompactRequest 仅检查
URL path 后缀,导致 body-signal 形式的 compact 请求被当作普通
Responses 处理——上游路径、模型映射、body 归一化全部错误,Codex
收到非 compact 响应后报 "expected exactly one compaction output
item, got 0",长会话无法继续。

新增 hasCompactionTriggerInInput 检测 input 中的 compaction_trigger
item,命中后提升为 compact 请求并改写 URL path,使后续所有
isCompactRequest 分支(模型映射、body 归一化、上游 URL 构建)
自动生效。

Fixes #3777 (part 1: body-signal routing)
2026-07-07 21:23:02 +08:00
li a86c534cb0 test: 补充侧边栏滚动位置持久化的前端测试
- AppSidebar.spec.ts:验证 template ref 绑定、sidebarNavRef 声明、
  onBeforeUnmount 保存 scrollTop、onMounted 恢复
- app.spec.ts:验证 sidebarScrollTop 默认值为 0 且可读写
2026-07-07 21:11:40 +08:00
li c7e44a83ad fix(frontend): 路由切换后保持侧边栏滚动位置
AppLayout 在每个页面组件内部包裹,路由切换时整个组件树(含
AppSidebar)被销毁重建,侧边栏滚动位置丢失。管理员菜单较长
或屏幕较矮时,每次点击靠下的菜单项后都要重新滚动。

在 appStore 增加 sidebarScrollTop 状态,AppSidebar 在
onBeforeUnmount 保存、onMounted 恢复滚动位置。

Fixes #3796
2026-07-07 21:08:03 +08:00
Wesley Liddick f68f3b86c6 Merge pull request #3802 from Wei-Shaw/fix/messages-chat-fallback-hardening
fix(messages): 修复 /v1/messages CC 回退的错误处理旁路并补齐流式测试
2026-07-07 21:07:29 +08:00
shaw dad92488e5 fix(messages): 修复 /v1/messages CC 回退的错误处理旁路并补齐流式测试
针对 #3795 合并后审计发现的问题:

- 非 failover 上游错误分支改走共享 handleAnthropicErrorResponse:恢复
  ops 上游错误记录、错误透传规则、cyber_policy 检测与按状态码映射的
  错误 type(400→invalid_request_error 等);删除随之失效的
  mapUpstreamStatusToAnthropicStatus
- 流式读错误对齐 forwardResponsesViaRawChatCompletions:scanner 出错时
  不再 finalize(不再补发 message_stop 把截断伪装成正常完成),返回
  stream usage incomplete 错误
- sawDone 由死变量改为与兄弟一致的无 [DONE] 哨兵 debug 日志
- 补充 fast policy 在本路径有意省略的说明注释
- 新增 8 个单测:流式收尾闭块、tool_call 分片聚合、length→max_tokens、
  空流补帧、非 failover 400 走共享处理器、读错误不伪造收尾、
  Responses 账号门控回归、非流式转换
2026-07-07 20:59:41 +08:00
Wesley Liddick 4bcb13f77f Merge pull request #3786 from creamtea47/codex/openai-fast-force-priority
add force priority fast policy action
2026-07-07 20:45:45 +08:00
Wesley Liddick 9643382bd3 Merge pull request #3795 from fengshao1227/fix/messages-inbound-chat-completions-fallback
fix(messages): /v1/messages 入站支持不兼容 Responses API 的 OpenAI 上游
2026-07-07 20:45:14 +08:00
Wesley Liddick af4b75a77c Merge pull request #3794 from fengshao1227/fix/image-gen-namespace-permission-bypass
fix(image): 识别 Codex namespace image_gen 工具声明以拦截生图请求
2026-07-07 20:42:43 +08:00
Wesley Liddick c7249a54ac Merge pull request #3793 from heathermhuang/codex/grok-video-text-fallback
fix: route text-only Grok video 1.5 requests
2026-07-07 20:22:44 +08:00
Wesley Liddick 65ff800362 Merge pull request #3799 from Wei-Shaw/fix/batch-image-audit-hardening
fix(batch-image): 修复审计发现的计费死锁、状态机与队列原子性缺陷
2026-07-07 20:20:49 +08:00
shaw b5604afd3d test(batch-image): 集成测试适配 ReplaceBatchImageItemsForJob 的 indexing 状态守卫
守卫要求 job 处于 indexing 状态才允许重建 item 表;原测试在 created
状态下直接调用被 409 拒绝。改为先断言非 indexing 状态的冲突行为,
再经 created→submitted→indexing 合法链推进后执行原有断言。
2026-07-07 20:13:28 +08:00
CHOS1N 2fd3539527 Merge remote-tracking branch 'upstream/main' into codex/add-response-format-compat 2026-07-07 20:05:50 +08:00
limingeandClaude Fable 5 13e773ef5e feat: Codex 客户端模型清单(manifest)透传接口
背景:Codex CLI / Codex App 会从 provider 的 GET {base_url}/models?client_version=...
(自定义 provider 模式)或 GET /backend-api/codex/models(chatgpt_base_url 模式)
刷新模型选单,期望 ChatGPT Codex manifest 格式({"models":[{slug,...}]})。
sub2api 此前只提供 OpenAI 兼容格式的 /v1/models,Codex 客户端解析失败后静默
回落到本地缓存,导致指向 sub2api 的 Codex 客户端模型选单永久冻结在切换
provider 当天的状态,新模型(如 gpt-5.6 系列)永远不会出现。

方案:新增 manifest 透传——用组内可调度 OAuth 账号的凭据向
chatgpt.com/backend-api/codex/models 实时转发请求,响应体与 ETag 原样透传。
不在网关侧解析或维护模型清单:manifest schema 随 Codex 客户端版本演进,
透传保证网关无需跟进 schema 变化,且返回的始终是账号真实的模型权限
(区别于静态 DefaultModels 的"理论列表")。

路由:
- GET /backend-api/codex/models(新增)
- GET /v1/models 带 client_version 查询参数且组平台为 openai 时分发到
  manifest 透传(client_version 是 Codex 客户端的天然指纹,普通 OpenAI
  客户端不携带);其余请求保持原有 OpenAI 格式行为不变。

上游失败时按 fast-fail 返回错误,不伪造列表;Codex 客户端自身会回落缓存。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 19:49:40 +08:00
shaw 80a229bce5 fix(batch-image): 修复审计发现的计费死锁、状态机与队列原子性缺陷
修复 PR #3768 批量图像 MVP 合并后审计报告中的全部问题:

结算与计费(高危):
- 所有 SETTLEMENT_* 失败(超冻结/计数非法/manifest 冲突/定价缺失/扣费失败)
  统一计入 retry_count 并在耗尽时释放冻结转 failed,消灭 settling 无限
  requeue 导致的冻结余额永久锁死
- 耗尽出口的释放指纹统一为 RequestHash,与 processor/Cancel/recovery 一致;
  release 遇同 request id 指纹冲突视为幂等成功,治愈历史毒消息
- 管理端校验 hold_multiplier >= discount_multiplier,定价快照对存量脏数据钳制
- 释放前校验 per-job hold claim(dedup+归档表),杜绝幻影释放

索引对账(高危):
- provider 输出与提交 custom_id 集对账:未知条目丢弃并记事件,
  漏项补 PROVIDER_RESULT_MISSING 失败行,保证 success+fail == item_count

提交与恢复(高危):
- 提交前转 uploading 并在 provider.Submit 期间心跳刷新 updated_at;
  恢复扫描改为原子复核(FailStaleUnsubmittedBatchImageJob),
  消灭慢提交被误杀退款而上游任务照常计费的孤儿场景
- 上游任务创建成功但本地状态推进失败时,尽力取消上游并清理输入
- recovery 释放失败时入队交由 worker releaseTerminalHold 兜底重试

队列与并发(中危):
- Enqueue(SetNX+LPush)与 Reserve(BRPop+ZAdd)均改为 Lua 原子脚本,
  消灭崩溃窗口导致 job 脱离队列、被 7 天 inflight 键锁死
- 锁冲突按 LockConflictDelay 重新入队(原直接丢弃需等 10 分钟 stale 恢复)
- 处理期间心跳:active zset 续期(ZAddXX 防幽灵成员)+ 锁 TTL 续期
- ReplaceBatchImageItemsForJob 增加 indexing 状态守卫,防掉队 worker 重写账目

存量回归(中危):
- image-only 定价条目(仅图片价无 token 价)恢复 token 计费 fail-closed,
  不再按 $0 计费;图片计费路径不受影响
- 鉴权余额门槛恢复 balance <= 0 语义,MinimumBalanceReserve 不再作硬 403

加固:
- ZIP max_items 钳制到管理员上限;Submit 补齐 Platform==Gemini 校验;
  gemini downloadUri 跟随前做 host 白名单校验
- 批量客户端改用共享 httpclient(拨号/TLS/响应头超时有界)
- 审计点名的忽略错误(MarkDownloaded/SettlementFailed/AppendEvent 等)改为记日志
2026-07-07 18:53:56 +08:00
li 69d02d2036 fix: 补齐 failover 路径的 appendOpsUpstreamError 日志
审查发现 error 路径缺少 appendOpsUpstreamError 和 upstreamDetail
记录,与 forwardResponsesViaRawChatCompletions 不对称。
2026-07-07 17:35:09 +08:00
li 5c0e580fbe fix(messages): /v1/messages 入站支持不兼容 Responses API 的 OpenAI 上游
ForwardAsAnthropic 此前无条件把 Anthropic Messages 转为 Responses
格式发往 /v1/responses,导致只支持 /v1/chat/completions 的第三方
OpenAI 兼容上游(京东云、DeepSeek、Kimi、GLM 等)全部 400。

在入口增加与 ForwardAsChatCompletions 对称的 ShouldUseResponsesAPI
分流:APIKey 账号 + 上游不支持 Responses 时,改走新建的
forwardAnthropicViaRawChatCompletions,链式复用全部已有转换桥
(Anthropic→Responses→CC 请求,CC→Responses→Anthropic 响应),
不新建转换函数。

Fixes #3735
2026-07-07 17:23:18 +08:00
li a02700c16f fix(image): 识别 Codex namespace image_gen 工具声明以拦截生图请求
分组 allow_image_generation=false 时,Codex /image 技能通过
namespace 工具声明(type=namespace, name=image_gen)和 Responses
Lite 的 input additional_tools 绕过了现有生图意图检测——现有逻辑
只识别 tools[].type == "image_generation" 这一种形式。

在 gjson 版(openAIJSONToolsContainImageGeneration)和 map 版
(hasOpenAIImageGenerationTool)检测中增加 namespace image_gen
识别;同时扫描 input[].type == "additional_tools" 中嵌套的工具
声明,覆盖 Responses Lite 格式。

Fixes #3608
2026-07-07 16:58:48 +08:00
Heatherm Huang 3b2099350e fix: route text-only grok video requests 2026-07-07 16:44:20 +08:00
Wesley Liddick 44ab690a01 Merge pull request #3768 from Turtle-Li/feature/batch-image-foundation
feat: add batch image generation MVP
2026-07-07 16:32:45 +08:00
Wesley Liddick 17d991786b Merge pull request #3788 from fengshao1227/fix/codex-function-call-item-id
fix(codex): 剥离续链 function_call item 的非法 item_* id
2026-07-07 16:22:01 +08:00
Turtle_Li c10ae9f9d2 chore: remove batch image draft reports 2026-07-07 16:21:25 +08:00
li fd64d07e61 fix(codex): 剥离续链 function_call item 的非法 item_* id
OpenAI OAuth 转发续链请求时,function_call 等 call-input 类 item 的
id 被客户端以 item_* 形式回放,但上游要求以 fc 开头,返回 400
"Expected an ID that begins with 'fc'",导致工具续链反复失败。

filterCodexInputWithOptions 在 PreserveReferences=true 路径下对
call-input 类 item(function_call/tool_call/local_shell_call 等)
增加 id 前缀检查:非 fc 开头即删除。output 类(function_call_output
等)的 id 无此约束,不动。

Fixes #3785
2026-07-07 15:54:55 +08:00
shaw 17b6481f8b chore: update sponsors 2026-07-07 15:44:28 +08:00
NellPoi e979990bf9 add force priority fast policy action 2026-07-07 15:27:23 +08:00
shaw 156e17335f chore: update sponsors 2026-07-07 15:15:52 +08:00
Turtle_Li a6023840f7 Merge remote-tracking branch 'origin/main' into feature/batch-image-foundation
# Conflicts:
#	deploy/Dockerfile
2026-07-07 15:14:37 +08:00
Wesley Liddick b5b0ea3460 Merge pull request #3708 from sweetcornna/fix/antigravity-401-self-heal
fix(antigravity): 网关转发默认走生产端点(修复一请求就 401/502、账号被 benched)
2026-07-07 15:12:29 +08:00
li fa01aec80d fix: 前缀全部加连字符,与 thinking_protocol.go 风格对齐
deepseek → deepseek-、moonshot → moonshot-、qwen → qwen-/qwen2-/qwen3-/qwen4-、
minimax → minimax-、llama → llama-/llama2-/llama3-、mistral → mistral-、
mixtral → mixtral-。避免无连字符前缀误伤假想的连写模型名。
2026-07-07 15:00:00 +08:00
Wesley Liddick 87bdaf8e60 Merge pull request #3780 from fengshao1227/fix/gateway-parse-error-observability
fix(gateway): 记录请求体解析失败的真实原因,不再吞错
2026-07-07 14:37:46 +08:00
Wesley Liddick a98397adbb Merge pull request #3778 from fengshao1227/fix/websearch-emulation-history-blocks
fix(websearch): 剥离历史消息中上游不接受的 web search 块
2026-07-07 14:33:16 +08:00
Wesley Liddick aff87d9497 Merge pull request #3776 from alfadb/fix/openai-compact-usage-sse-detection
fix(openai): 避免 JSON 响应被 SSE 文本误判导致用量丢失
2026-07-07 14:31:52 +08:00
li 9b75c7b76c fix(scheduler): 改用异族厂商前缀黑名单,兼容渠道级模型映射
自审发现白名单守卫有误伤面:渠道级模型映射在账号选定之后才改写
请求体模型名,调度过滤看到的是改写前的原始别名。若按 Codex 白名单
判定,「渠道映射自定义别名 → gpt-5.x + 空映射 OAuth 账号」的既有
部署会被误判为 model_not_found。

改为保守 fail-open 黑名单:空映射默认仍允许,仅排除 deepseek/glm/
kimi/qwen/gemini/grok 等明确异族厂商前缀(Codex 上游绝无可能服务,
透传必然换来不可重试 400)。前缀分类先例见 ResolveThinkingProtocol。
2026-07-07 14:19:58 +08:00
Wesley Liddick 571e0b8132 Merge pull request #3774 from heathermhuang/codex/grok-composer-image-bridge
fix: bridge Grok Composer image inputs
2026-07-07 14:13:28 +08:00
li 2a3dcb499f fix(scheduler): 空 model_mapping 的 OpenAI OAuth 账号不再吸收全部模型
空映射此前一律视为「允许所有」,导致混合部署下请求非 OpenAI 模型
别名(deepseek-v4 等)时,OAuth 账号可能被调度选中;转发阶段对未知
模型原样透传,Codex 上游返回不可重试的 400,请求无法 failover 到
真正支持该模型的 API Key 账号。

IsModelSupported 对空映射的 OpenAI OAuth(未开透传)账号改为与转发
归一化对齐的判定 isOpenAIOAuthServableModel:可归一到已知 Codex 模型
集合(含 gpt-image-*、推理后缀、别名拼写)或 claude-* dispatch 系列
才视为可服务。API Key/显式映射/透传/其他平台语义不变。

Fixes #3662
2026-07-07 14:06:21 +08:00
li 40c563c4ae fix(gateway): 记录请求体解析失败的真实原因,不再吞错
400 "Failed to parse request body" 此前丢弃底层错误,无法区分
JSON 真非法、还是 body 被截断/被中间件提前消费。

- 服务层 invalid json 错误增补 len/offset/非法字符信息
  (仅诊断元数据,不含 body 内容,可安全 wrap);
- handler 层新增 logRequestBodyParseFailure,向服务端日志输出
  底层错误 + body 长度 + 转义后的 head/tail 片段(各 256B),
  客户端响应文案保持不变;
- 接入全部 9 处入站解析点(messages/count_tokens/responses/
  chat_completions/embeddings,Anthropic 与 OpenAI 网关)。

Fixes #3715
2026-07-07 13:53:39 +08:00
li 867616fca8 fix(websearch): 剥离历史消息中上游不接受的 web search 块
Web Search 模拟合成的 server_tool_use / web_search_tool_result 块
(srvtoolu_ws_ 前缀)被客户端回传到后续 /v1/messages 历史后,
上游一律 400(GLM 等第三方兼容上游只接受
text/thinking/image/tool_use/tool_result),会话从此卡死。

新增 FilterWebSearchHistoryBlocks 预过滤:
- 模拟伪造块对所有上游无条件剥离;
- 真实 web search 块仅对 passback-required 协议族剥离
  (复用 ResolveThinkingProtocol),不误伤官方 Anthropic 会话;
- 接入 Anthropic 通用转发与 API Key 透传两条路径的既有 pre-filter 区。

Fixes #3677
2026-07-07 13:41:40 +08:00
alfadb 438f17be5e fix(openai): avoid compact usage loss from json sse heuristic
Replace the substring-based data:/event: SSE heuristic in
handleNonStreamingResponse with bodyHasSSEFraming, a line-level check
that only matches when a physical line starts with the SSE data:/
event: field names. This prevents OAuth compact JSON responses whose
output text merely contains the literal substrings "data:" or
"event:" from being misrouted into handleSSEToJSON, which was
causing usage to be recorded as zero even though the JSON body itself
carries a valid top-level usage object. Content-Type: text/event-stream
detection and genuine SSE body handling are unaffected.
2026-07-07 13:04:28 +08:00
Turtle_Li 83d7b0e440 fix: isolate duplicate item integration test 2026-07-07 11:24:04 +08:00
github-actions[bot] 9d5f1b73ad chore: sync VERSION to 0.1.146 [skip ci] 2026-07-07 03:16:52 +00:00
Heatherm Huang c34db70a88 fix: bridge grok composer image inputs 2026-07-07 10:55:07 +08:00
Turtle_Li 5494261902 Merge remote-tracking branch 'origin/main' into feature/batch-image-foundation
# Conflicts:
#	.github/audit-exceptions.yml
2026-07-07 10:53:17 +08:00
Turtle_Li aff148167c fix: address batch image ci failures 2026-07-07 10:50:36 +08:00
Wesley Liddick d7a6a4513a Merge pull request #3729 from starmiaoa/feat/account-import-dnd-batch-20260705
支持账号数据拖拽和批量导入
2026-07-07 10:44:39 +08:00
Wesley Liddick baadb22682 Merge pull request #3761 from alfadb/fix/responses-alias-normalization
修复 OpenAI Responses compact 端点归一化
2026-07-07 10:37:08 +08:00
Wesley Liddick 863953321e Merge pull request #3771 from Wei-Shaw/fix/redis-scan-index-hardening
fix(redis-index): 加固 #3762 引入的索引驱动清理架构
2026-07-07 10:21:17 +08:00
alfadb ddd63a8402 test: cover endpoint wildcard edge cases 2026-07-07 10:20:23 +08:00
alfadb 75e3089496 fix: normalize inbound endpoints from raw request path 2026-07-07 10:20:23 +08:00
alfadb 2fb212b7d6 fix(openai): 区分 responses compact 入站端点
/v1/responses/compact 及其别名路径(/responses/compact、/openai/v1/responses/compact、
/backend-api/codex/responses/compact 等,含嵌套子路径)是独立的客户端端点,
新增 EndpointResponsesCompact 常量并单独归一化,不再与根 /v1/responses 混同。
根 Responses 别名(/responses、/backend-api/codex/responses 等)仍归一到
EndpointResponses。NormalizeInboundEndpoint 优先匹配 compact 分支,避免根路径
前缀提前命中。DeriveUpstreamEndpoint 在 inbound 已为 compact 但原始路径无法
派生后缀时,回退到 compact 而非根 upstream。
2026-07-07 10:20:23 +08:00
shaw 7650cce5a4 fix(redis-index): harden index-driven cleanup introduced in #3762
- 启动清理覆盖 score 已过期的索引成员,并新增 marker 门控的一次性
  遗留 wait 键清扫,消除重启后幻影等待计数挤占 maxWait 的问题
- UMQ 争锁失败路径回填观测到的持有者锁索引(PTTL=-1 异常锁以已到期
  候选入索引),恢复被 SCAN 删除后丢失的孤儿锁兜底完整性
- 后台清理同时回收 account 与 user 两个活跃索引的到期候选,修复
  user 索引过期成员无回收路径导致的无界增长
- acquire/incrementWait Lua 脚本返回 {result, now},索引写复用脚本
  内 Redis 时间,热路径每次操作减少一次 TIME 往返;清理循环外提
  now 并批量写索引
- 删除无生产调用方的 GetActiveAccountLoadMap 及配套死代码
- 索引维护失败增加 warn 日志,不再静默丢弃
- 移除根目录工单文档 REDIS_SCAN_ARCHITECTURE_OPTIMIZATION.md
2026-07-07 10:04:38 +08:00
Wesley Liddick db9d92ba8f Merge pull request #3762 from jianjianai/fix/add-chinese-comments
优化 Redis SCAN 清理架构
2026-07-07 09:36:20 +08:00
shaw 7c8e13208b style: replace interface{} with any per golangci-lint gofmt rewrite rules 2026-07-07 09:32:15 +08:00
shaw d37d216f9d Merge remote-tracking branch 'origin/main' into pr-3762
# Conflicts:
#	backend/internal/repository/concurrency_cache.go
#	backend/internal/repository/concurrency_cache_integration_test.go
2026-07-07 09:22:44 +08:00
Wesley Liddick 96f3697ecb Merge pull request #3767 from qqqianye/fix/admin-account-compact-probe
fix(frontend): add compact probe mode to admin account test modal
2026-07-07 09:09:05 +08:00
Wesley Liddick 42ecbeb8c6 Merge pull request #3769 from Wei-Shaw/chore/renew-xlsx-audit-exceptions
chore: renew expired xlsx audit exceptions to 2026-10-06
2026-07-07 08:47:48 +08:00
shaw a564051f83 chore: renew expired xlsx audit exceptions to 2026-10-06
xlsx is frozen at 0.18.5 on the npm registry (fixed versions are only
published on the SheetJS CDN), so GHSA-4r6h-8v6p-xvw6 and
GHSA-5pgg-2g8v-p4x9 remain flagged by pnpm audit. Mitigation is
unchanged: admin-export-only usage behind a dynamic import.
2026-07-07 08:38:30 +08:00
Wesley Liddick a862eac1d8 Merge pull request #3758 from catoncat/fix/test-oauth-codex-headers
fix: add missing Codex CLI headers for OAuth account test
2026-07-07 08:34:18 +08:00
Wesley Liddick 913d838770 Merge pull request #3755 from wucm667/fix/subscription-usd-cny-rate-opt-in
feat(payment): 套餐编辑页展示订阅 CNY 实扣预览
2026-07-07 08:29:12 +08:00
Wesley Liddick 3828a48752 Merge pull request #3751 from heathermhuang/codex/grok-image-pricing-latest
fix: expose Grok image pricing controls
2026-07-07 08:29:01 +08:00
Wesley Liddick 77b1a0452e Merge pull request #3760 from DaydreamCoding/fix/codex-detect-version-gate-message
fix(codex-detect): codex 版本门拒绝返回带版本号的差异化文案
2026-07-07 08:28:51 +08:00
Wesley Liddick 26b51ccfd3 Merge pull request #3737 from wucm667/fix/sync-models-non-v1-base-url
fix(models): 同步上游模型支持非 /v1 API 版本 base_url(如智谱 /v4)
2026-07-07 08:28:41 +08:00
Turtle_Li 89edba8028 fix: restrict batch image groups to gemini 2026-07-07 04:14:09 +08:00
Turtle_Li 3c43fdec11 docs: add batch image PR readiness notes 2026-07-07 03:31:39 +08:00
Turtle_Li 1b07fe821a merge: sync batch image branch with origin main 2026-07-07 03:27:11 +08:00
qqqy 7c2a828c56 fix(frontend): add compact probe mode to admin account test modal 2026-07-07 01:18:40 +08:00
Wesley Liddick 67e945f8e8 Merge pull request #3759 from Wei-Shaw/feat/account-header-override
feat: apikey 账号支持请求头覆写(Anthropic/OpenAI)
2026-07-06 20:47:33 +08:00
cat cb151e36eb fix: respect custom User-Agent in OAuth account test
Match the image OAuth test path and gateway behavior: check for a
custom user_agent credential first, fall back to codexCLIUserAgent
only when no custom UA is configured.
2026-07-06 20:45:00 +08:00
jjaw 5089c303e1 优化 Redis SCAN 清理架构 2026-07-06 20:27:13 +08:00
shaw 31b6e0d94a fix: 请求头覆写审计问题修复(禁止名单缺口/beta 对称性/批量清空防护)
后端:
- 禁止名单补充 content-type(multipart boundary 为每请求随机值,静态覆写必坏
  images 路径)、x-claude-code-session-id / x-client-request-id(会击穿每请求
  会话同步,与已禁的 session_id 等同类)、cookie / x-goog-api-key(与透传路径
  的入站鉴权残留清洗保持一致)
- anthropic-beta 覆写与 body 净化对称:四个 Anthropic 构建器在
  sanitizeAnthropicBodyForBetaTokens 前以覆写值为有效 finalBeta,避免覆写
  丢 token 后 header/body 不对称被上游 400
- 保存/应用两路径共用 normalizeHeaderOverrideEntry,消除双份校验规则漂移
- GetHeaderOverrides 按 modelMappingCache 先例增加热路径缓存(WS 每消息
  重建头场景收益最大);ApplyHeaderOverrides 移除无观测效果的排序

前端:
- 禁止名单镜像同步新增项;值长度改按 UTF-8 字节校验(与后端 len() 对齐)
- Create 弹窗切换平台时重置覆写配置,避免上一平台模板行串台
- BulkEdit:开启覆写但无有效行时拦截保存(防止整键替换静默清空所选账号
  既有配置);开启分支显示整体替换警告;混合平台选择时隐藏模板按钮
- 删除未使用的 isBlockedHeaderOverrideName 导出
2026-07-06 20:05:50 +08:00
shaw ec7b20649e feat: apikey 账号支持请求头覆写(Anthropic/OpenAI)
- 账号 credentials 新增 header_override_enabled / header_overrides,
  仅对 anthropic/openai 平台的 api_key 账号生效
- 转发前对同名请求头做大小写不敏感覆盖(EqualFold 全量删除后按
  wire casing 写入单值),值为空的条目视为占位不参与覆盖
- 覆盖全部出站路径:/v1/messages(标准+透传)、count_tokens、
  /v1/responses、chat completions、embeddings、images、全部 WS 模式、
  账号测试/探测、上游模型列表
- 创建/编辑/批量更新统一校验:RFC 7230 名称格式、去重、长度/条目上限、
  24 个禁止覆写头(认证/连接控制/accept-encoding/sec-websocket-*/
  会话隔离头),应用时二次防御过滤
- 前端三个账号弹窗新增开关+键值行编辑器+模板按钮(Claude Code CLI /
  Codex CLI 标准头,值为空),本地校验与后端规则对齐,i18n 中英文
2026-07-06 19:11:08 +08:00
cat 1c0ccb477a fix: add missing Codex CLI headers for OAuth account test
The testOpenAIAccountConnection function was sending requests to
chatgpt.com/backend-api/codex/responses without the required Codex CLI
identification headers, causing 401 Unauthorized for OAuth/PAT accounts.

The gateway Forward path and the compact test path both set these headers:
- Originator: codex_cli_rs
- User-Agent: codex_cli_rs/<version>
- OpenAI-Beta: responses=experimental

This patch adds the same headers to the normal (non-compact) OAuth test
path. API key accounts and other platforms are unaffected since the
headers are inside the isOAuth branch.
2026-07-06 19:09:26 +08:00
DaydreamCodingandClaude Opus 4.8 4b321142bb fix(codex-detect): codex 版本门拒绝返回带版本号的差异化文案
codex_cli_only 的 403 出口此前对所有拒绝原因硬编码同一句
"This account only allows Codex official clients",但能走到版本门
(判定链第 5 步)的请求其实已命中官方 UA/originator,仅版本不符,
再回「只允许官方客户端」会误导。

- CodexClientRestrictionDetectionResult 增 DetectedVersion/MinCodexVersion/
  MaxCodexVersion,仅在 VersionTooLow/VersionTooHigh 分支填充
- 新增 CodexClientRestrictionMessage:too_low/too_high 给出带实际版本号与
  边界的提示;undetectable/未命中/黑名单/缺指纹 仍用通用兜底句(不泄露门控细节)
- Forward 唯一 403 出口改用该映射函数

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 19:03:36 +08:00
Heatherm Huang de31e57333 test: document Grok video media billing unit 2026-07-06 18:32:29 +08:00
Heatherm Huang 95717a7e62 test: clarify Grok video billing test name 2026-07-06 18:30:32 +08:00
Heatherm Huang 6b4c3c6ed1 test: cover Grok video media rate billing 2026-07-06 18:23:38 +08:00
wucm667 a23a263513 feat(payment): preview subscription CNY charge in plan editor 2026-07-06 17:52:53 +08:00
wucm667 f881ff7cb0 fix(models): support non-v1 OpenAI models URLs 2026-07-06 17:46:44 +08:00
shaw 6cea1c35bb feat: 适配 OpenAI 新模型 gpt-5.6-sol/terra/luna 2026-07-06 17:34:22 +08:00
Heatherm Huang a42e9e3fc8 fix: expose Grok image pricing controls 2026-07-06 17:02:28 +08:00
Wesley Liddick d1d3400b69 Merge pull request #3645 from bestony/worktree/lucky-harbor-dbe4
feat(keys): add api key concurrency stats
2026-07-06 16:45:15 +08:00
github-actions[bot] 76bb7b0338 chore: sync VERSION to 0.1.145 [skip ci] 2026-07-06 08:30:51 +00:00
Wesley Liddick 3fa08aa930 Merge pull request #3749 from moonfunjohn/codex/easypay-custom-methods
EasyPay custom visible payment methods
2026-07-06 16:06:59 +08:00
Turtle_Li 0b729496ee fix: center batch image empty state 2026-07-06 15:39:57 +08:00
Turtle_Li 616cf17d9e fix: hide batch image entry without allowed key 2026-07-06 15:03:17 +08:00
Albert Coady 27cb485d55 fix: share built-in payment method matching 2026-07-06 15:00:29 +08:00
Albert Coady 22ec77b570 fix: match built-in payment methods exactly 2026-07-06 15:00:29 +08:00
Albert Coady b197ba61ce test: align antigravity mapping preset label 2026-07-06 15:00:29 +08:00
Albert Coady a5a2fea045 Polish EasyPay custom method UI 2026-07-06 15:00:29 +08:00
Albert Coady 0dc6e56aae fix: harden easypay custom method validation 2026-07-06 15:00:29 +08:00
Albert Coady bf76168ba5 feat: add custom easypay payment methods 2026-07-06 15:00:29 +08:00
Wesley Liddick 2854ab2ace Merge pull request #3744 from Wei-Shaw/fix/subscription-cny-optin-rate
fix(payment): 订阅 CNY 换算改为独立汇率配置的显式 opt-in(含 #3738)
2026-07-06 14:58:20 +08:00
Wesley Liddick e95c909ee5 Merge pull request #3747 from Wei-Shaw/revert-3738-fix/subscription-confirm-amount-and-affiliate-base
Revert "fix(payment): 订阅确认页显示换算后 CNY 金额 + 邀请返利按 USD price 计算"
2026-07-06 14:58:00 +08:00
Wesley Liddick ba1bb0a3d3 Revert "fix(payment): 订阅确认页显示换算后 CNY 金额 + 邀请返利按 USD price 计算" 2026-07-06 14:55:04 +08:00
Wesley Liddick 11fd61458c Merge pull request #3738 from wucm667/fix/subscription-confirm-amount-and-affiliate-base
fix(payment): 订阅确认页显示换算后 CNY 金额 + 邀请返利按 USD price 计算
2026-07-06 14:53:59 +08:00
shaw d56e94b875 feat(payment): 订阅 CNY 换算改为独立汇率配置的显式 opt-in
- 新增 SUBSCRIPTION_USD_TO_CNY_RATE 配置(1 USD = X CNY,默认 0=关闭),
  替代复用 balance_recharge_multiplier 的隐式换算,促销倍率与订阅定价解耦
- 未配置汇率时订阅保持 price 直付的存量行为,存量部署升级零影响
- 前端确认页/原价/手续费/方式限额与后端换算条件严格镜像(rate>0 且币种为 CNY)
- 管理后台新增汇率配置输入(zh/en 文案),checkout-info 透出 subscription_usd_to_cny_rate
- 单测锁定:汇率未配置时不换算、换算使用汇率而非余额倍率、余额订单不受影响、返利仍按 USD price
2026-07-06 14:34:17 +08:00
Turtle_Li d73fa8eab2 fix: keep image generation migration immutable 2026-07-06 14:25:10 +08:00
shaw d089c5789c Merge branch 'pr-3738' into fix/subscription-cny-optin-rate 2026-07-06 14:14:19 +08:00
Turtle_Li 202c6989a1 fix: preserve immutable initial migration 2026-07-06 14:11:49 +08:00
Wesley Liddick 9fac6f15ce Merge pull request #3739 from Wei-Shaw/fix/openai-advanced-scheduler-audit-fixes
fix(scheduler): 修复 OpenAI 高级调度器审计发现的正确性与性能问题
2026-07-06 13:45:15 +08:00
Turtle_Li 9703ca9d33 merge: sync batch image foundation with upstream main 2026-07-06 13:40:09 +08:00
Turtle_Li d8e96f0f97 fix: bound batch image settlement retries 2026-07-06 13:33:53 +08:00
Turtle_Li a3c5cc4578 test: add batch image QA reports 2026-07-06 12:44:59 +08:00
Turtle_Li 5553d83605 fix: localize antigravity image mapping labels 2026-07-06 12:31:27 +08:00
Turtle_Li 8fab636998 feat: complete batch image workflow 2026-07-06 12:22:04 +08:00
CHOS1N e2326a7998 Format response format types 2026-07-06 12:14:30 +08:00
shaw 0fd2e9216d fix(scheduler): 修复 OpenAI 高级调度器审计发现的正确性与性能问题
针对 #3692 合并后审计发现的问题集中修复:

- previous_response_id 剥离条件改为按 call_id 全覆盖校验,
  部分可重建的工具续链不再被误剥离(不受开关门控的行为回归)
- 粘性加权回退路径补分组归属校验并清理失效绑定,杜绝跨分组账号泄漏
- 账号列表页:无 OpenAI 账号时跳过分数计算、过滤池限定 openai 平台、
  负载批查合并为账号并集一次查询,消除全表扫描与 Redis N+1
- 订阅优先模式下常规池不可用时回退订阅池等待计划,
  busy-but-waitable 的订阅账号不再导致请求硬失败
- TopK/权重 DB 覆盖显式受总开关门控,与兄弟子开关语义一致
- 前端未分组 OpenAI 账号回退展示基础分,不再显示 "-"
- ListAllWithFilters 等能力正式进入 AccountRepository/AdminService 接口,
  移除匿名接口断言与静默降级;负载批查失败补 warn 日志
- SelectAccountWithSchedulerForCapability 增加显式 previousResponseCanMove
  参数,移除 "previous_response_can_move" 魔法字符串哨兵
- 设置写入路径补"基础权重不得全为零"聚合校验;
  运行时设置批量读取失败的降级路径覆盖全部键并留痕
2026-07-06 11:43:16 +08:00
Wesley Liddick 759332aa92 Merge pull request #3692 from linshuboy/codex/openai-advanced-scheduler-controls-squashed
[codex] add OpenAI advanced scheduler controls
2026-07-06 10:59:23 +08:00
wucm667 b408edf97b fix(payment): convert subscription CNY pay amount 2026-07-06 10:56:43 +08:00
Wesley Liddick f0f1f59a44 Merge pull request #3716 from wucm667/fix/antigravity-refresh-server-invalidated-token
fix(antigravity): 401 服务端失效的 token 触发主动刷新,修复 NeedsRefresh 仅看 expires_at 的死循环
2026-07-06 10:18:49 +08:00
Wesley Liddick ac68d73f0c Merge pull request #3717 from wucm667/fix/anthropic-models-honor-group-config
fix(gateway): Anthropic /v1/models 返回分组 models_list_config 自定义模型列表
2026-07-06 10:18:36 +08:00
Wesley Liddick 2e70ec0a15 Merge pull request #3714 from MoRanHuiShou-pug/fix-payment-nul-bytes
fix: sanitize payment response NUL bytes
2026-07-06 10:18:18 +08:00
Wesley Liddick de15f29d1f Merge pull request #3718 from wucm667/fix/usage-csv-export-utf8-bom
fix(usage): CSV 导出补 UTF-8 BOM,修复 Excel 打开中文列乱码
2026-07-06 10:12:27 +08:00
Wesley Liddick 809e1cf8ee Merge pull request #3704 from feitianbubu/feat/sidebar-logo-home-link
feat: 点击侧边栏 Logo/站点名返回首页
2026-07-06 10:12:15 +08:00
Wesley Liddick c9dda3c01f Merge pull request #3734 from Yinr/patch-1
fix(i18n): correct misleading "irreversible" description for revoke action
2026-07-06 10:12:04 +08:00
shaw 6752cdc826 fix(i18n): 用量页费用明细提示将面向用户的"成本"措辞统一为"费用"
用户 /usage 页费用列悬浮提示中的成本明细/输入成本/输出成本/
缓存创建成本/缓存读取成本/图片输出成本 改为对应的"费用"表述;
成本是运营方视角的词,面向用户应称费用。管理端真正的账号成本
词条(accountCost 等)保持不变。
2026-07-06 09:36:21 +08:00
Yeyin Hu ee90246aa5 Modify revokeDesc for subscription termination
Updated revokeDesc to indicate restoration option.
2026-07-06 08:23:43 +08:00
Yeyin Hu 2d2b6f0325 Update revokeDesc to allow subscription recovery 2026-07-06 08:12:09 +08:00
白喵喵喵喵 83455a3fee fix(frontend): harden account data batch import 2026-07-05 19:31:06 +08:00
白喵喵喵喵 728bb1bc9d feat(frontend): 支持账号数据拖拽和批量导入 2026-07-05 18:58:57 +08:00
linshuboy f26ca5661e feat: add OpenAI advanced scheduler controls
Related: #1089, #408, #123
2026-07-05 17:24:38 +08:00
CHOS1N 5fcbe7e307 Add response_format compatibility mapping 2026-07-05 13:58:08 +08:00
wucm667 aee9a7ba98 fix(usage): add UTF-8 BOM to CSV export 2026-07-05 08:36:15 +08:00
wucm667 41cdd438d7 fix(gateway): honor Anthropic custom models list 2026-07-05 08:35:34 +08:00
wucm667 b23475ac0c fix(antigravity): refresh server-invalidated tokens 2026-07-05 08:34:34 +08:00
MoRanHuiShou-pug e76e0499d7 fix: sanitize payment response NUL bytes 2026-07-05 03:55:40 +08:00
sweetcornna cbfeab964e fix(antigravity): default gateway forward base URL to the production endpoint
resolveAntigravityForwardBaseURL() used ForwardBaseURLs() (which reorders the
daily/sandbox endpoint to the front) and returned the first entry by default —
daily-cloudcode-pa.sandbox.googleapis.com. Every Antigravity gateway request
was therefore sent to Google's sandbox endpoint, which rejects production OAuth
tokens: the account is benched with "OAuth 401: Invalid bearer token" (native
paths surface it as 502) and never recovers. The dashboard "test connection"
uses the production endpoint (antigravity.BaseURL), which is exactly why the
test succeeds while the gateway 401s.

Default to production (antigravity.BaseURLs[0] = cloudcode-pa.googleapis.com),
matching the OAuth/test path; the daily/sandbox endpoint is now opt-in via
GATEWAY_ANTIGRAVITY_FORWARD_BASE_URL=daily|sandbox.

Fixes #3611, #2962. Supersedes the token-refresh self-heal approach (that
premise — token staleness — was disproven: a 30s-old freshly-authorized token
also 401s on the gateway while working on the test path).

Verified on a live instance with a freshly-authorized Antigravity account:
/antigravity/v1/messages and /v1/messages now return real completions
(claude-sonnet-4-6, claude-opus-4-6); the same account/token 401/502'd before.
2026-07-04 17:09:16 +08:00
shaw 498f010ec3 fix(部署): 统一 Docker 部署 URL 安全默认值为开发友好模式
docker-compose.yml / docker-compose.local.yml 中
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP 与
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS 的兜底值由 false 改为 true,
与代码默认值(0c7a58fc)保持一致,避免未配置 .env 的 Docker 部署
在测试账号连接时因 http base URL 报 "invalid url scheme: http"。

同步更新 README(三语)、.env.example、config.example.yaml 中
过时的"默认拒绝 HTTP"描述,改为默认允许并指导生产环境显式收紧。
2026-07-04 13:51:37 +08:00
feitianbubu 20008264fe feat: 点击侧边栏 Logo/站点名返回首页 2026-07-04 13:33:53 +08:00
github-actions[bot] b650bdd68d chore: sync VERSION to 0.1.144 [skip ci] 2026-07-04 03:49:44 +00:00
Wesley Liddick 41def4ba03 Merge pull request #3699 from wucm667/fix/responses-billing-use-mapped-model
fix(openai): /v1/responses 计费使用映射后模型,修复按原始模型价格误计费
2026-07-04 10:40:53 +08:00
Wesley Liddick 707b87a96f Merge pull request #3676 from wucm667/fix/codex-import-refresh-token-missing-collision
fix(admin): Codex Session 导入 refresh_token 缺失时不再合并同 workspace 不同账号
2026-07-04 10:29:47 +08:00
shaw cbe0c4665f chore: update readme 2026-07-04 10:18:41 +08:00
shaw dec709fb3a chore: update sponsors 2026-07-04 10:06:11 +08:00
wucm667 4dd3aee5c2 fix(openai): use mapped billing model for responses 2026-07-04 10:04:42 +08:00
wucm667 6bd248fd1f fix(admin): avoid merging Codex access-only imports 2026-07-04 09:53:01 +08:00
shaw be297b90ce refactor(frontend): merge Codex image bridge and tool policy into one four-state control
Replace the two adjacent account-level controls (3-state injection bridge
+ 2-state explicit tool policy) in EditAccountModal with a single
four-state selector: follow channel / force inject / no injection /
block all. The four states map onto the existing extra keys
(codex_image_generation_bridge, codex_image_generation_explicit_tool_policy)
with no backend change; strip already disables the bridge upstream, so
the effective decision space is exactly these four states.

- load: strip takes precedence and reads as "block all"; legacy
  bridge_enabled key still recognized and cleaned up on save
- save: block writes policy=strip and drops the bridge override;
  inherit clears both keys
- semantic accent colors per state (sky/emerald/amber/rose) with dark
  mode support; state badge follows the same palette
- replace account-scope i18n keys in zh/en (channel-scope keys untouched)
- rewrite specs to cover all four states (25/25 passing)
2026-07-04 09:49:37 +08:00
Wesley Liddick dfdb838ce4 Merge pull request #3690 from AndersonBY/feature/codex-image-tool-policy
feat: add Codex image tool strip policy
2026-07-04 09:27:37 +08:00
Wesley Liddick be3aee1a4b Merge pull request #3694 from zhaoteng45/fix/antigravity-gemini31-pro-agent
fix: normalize Antigravity Gemini 3.1 Pro routing
2026-07-04 09:23:46 +08:00
Wesley Liddick 588e02b29e Merge pull request #3673 from heathermhuang/codex/grok-admin-issues-3545-3649
fix: preserve configured Grok OAuth concurrency
2026-07-04 09:22:18 +08:00
Wesley Liddick 659086861d Merge pull request #3670 from weiness/feat/configurable-migration-timeout
feat: make setup migration timeout configurable
2026-07-04 09:21:14 +08:00
Wesley Liddick 0c53d4f334 Merge pull request #3661 from jianjianai/fix/group-capacity-batch-hotpath
fix: 优化用户组容量汇总批量统计性能
2026-07-04 09:20:02 +08:00
Wesley Liddick 8e07ec55db Merge pull request #3685 from jianjianai/fix/optimize-slot-cleanup
fix: 优化并发槽位定时清理性能
2026-07-04 09:19:34 +08:00
Wesley Liddick 129d05258a Merge pull request #3696 from jianjianai/codex/token-expired-nonretryable
fix:添加 token_expired 刷新不可重试判断
2026-07-04 09:18:43 +08:00
Wesley Liddick 46ef146225 Merge pull request #3689 from DaydreamCoding/feat/usage-error-requests-alignment
feat(usage): 错误请求对齐用量明细(UI/排序/筛选/列设置)
2026-07-04 09:18:08 +08:00
Wesley Liddick 35df02aeac Merge pull request #3698 from Wei-Shaw/feat/anthropic-7d-oi-fable-model-rate-limit
feat(anthropic): 7d_oi(Fable 专属 7d 窗口)429 仅做模型级限流 + 用量窗口新增 7d F 进度条
2026-07-04 08:49:08 +08:00
shaw b3f796972e feat(anthropic): treat 7d_oi (Fable) window 429 as model-level rate limit
Anthropic OAuth/setup-token accounts now expose a Fable-only 7d window in
429 responses (anthropic-ratelimit-unified-7d_oi-*). Previously, a 429
triggered solely by this window fell through to the legacy path and marked
the whole account rate limited (picking the sooner 5h/7d reset) even though
5h/7d were still "allowed".

Backend:
- HandleUpstreamError: when only 7d_oi is rejected/exceeded, set a
  model-level rate limit (scope "claude-fable-5") and skip account-level
  SetRateLimited / session-window rewrite / temp-unsched entirely.
  Real 5h/7d exhaustion keeps the exact legacy account-level behavior.
- modelRateLimitKeysForRequest: append the Fable family scope for any
  mapped Anthropic model containing "fable", so claude-fable-5[1m] and
  other variants are excluded from scheduling together.
- Passive sampling: collect 7d_oi utilization/reset into Extra
  (passive_usage_7d_oi_*), including from the 429 response itself so the
  usage bar doesn't freeze at a stale value during the limit window;
  cleared on 5h window reset like the existing 7d keys.
- Usage API: new seven_day_fable window in UsageInfo. GetPassiveUsage
  builds it from passive samples; active queries parse the upstream
  seven_day_overage_included field (claim name from the
  representative-claim header) and fall back to passive data when the
  upstream usage API doesn't return it, so the active-query button keeps
  the bar populated. seven_day_sonnet handling is preserved unchanged.

Frontend:
- /admin/accounts usage cell renders a "7d F" (amber) progress bar next to
  the existing 5h / 7d / 7d S bars for Anthropic OAuth/setup-token accounts.
2026-07-04 08:28:30 +08:00
Turtle_Li a994fbd77a feat: add batch image MVP 2026-07-04 05:30:50 +08:00
jjaw e5dc1f5972 添加 token_expired 刷新不可重试判断 2026-07-04 04:30:15 +08:00
zhaoteng45 df2cedeead fix: normalize antigravity gemini 3.1 pro routing 2026-07-03 17:22:49 +00:00
DaydreamCoding ebbdc70311 feat(usage): 错误请求对齐用量明细(UI/排序/筛选/列设置)
错误请求列表(/admin/usage 错误 tab、Ops 弹窗、用户端 /usage 错误 tab)
对齐用量明细的交互与信息密度。Squash of:

- feat(usage): 错误请求全面对齐用量明细(UI/列序/排序/筛选/列设置/新列)
- refactor(usage): 错误表提取共享徽章工具与 IP 批量工具条,后端排序解析归并 SetSort
- feat(usage): /admin/usage 错误请求新增分类过滤
- fix(ops): 错误列表 phase=upstream 过滤生效,守卫豁免改为显式 opt-in
- fix(ops): 错误列表用户列回退显示已删除 KEY 所有者
- fix(usage): 错误请求状态码排序对齐展示/过滤,筛选项改固定常用码
2026-07-03 23:02:27 +08:00
Anderson f385cdceb7 feat: add Codex image tool strip policy 2026-07-03 22:36:39 +08:00
Wesley Liddick 2649573b99 Merge pull request #3684 from Wei-Shaw/fix/usage-log-queue-silent-drop
fix: prevent silent usage_logs drops under queue overflow
2026-07-03 22:17:24 +08:00
shaw a1b2b32e08 fix: prevent silent usage_logs drops under queue overflow (#3656)
高并发下扣费成功但 usage_logs 被双层异步队列静默丢弃(单用户实测丢失 49%),
导致消费排行与对账缺口。根因是 6a685727 为避免溢出时单行 INSERT 踩踏数据库,
把三处"队列满"改成了立即终态丢弃。

改为有界阻塞背压,兼顾数据不丢与批量写入模式不变:

- CreateBestEffort/createBatched 入队移除 default 立即丢弃分支,
  队列满时阻塞等待(受 detached 15s ctx 期限约束)
- writeUsageLogBestEffort 收到 dropped 不再直接放弃,统一走
  repo.Create 同步兜底(仍经批处理器;ctx 耗尽时换新 detached 窗口),
  重复写入由 ON CONFLICT DO NOTHING 幂等防护
- worker 池默认溢出策略 sample→sync(viper 默认 + 池常量),
  溢出时提交方内联执行(提交点在响应写出后,不阻塞客户端),
  显式配置 sample/drop 的部署不受影响
- 附带修复 ensure*Batcher 的存量数据竞争:channel nil 检查移入 sync.Once

正常负载零行为变化;突发时短暂等待替代永久丢失;数据库只见批量 INSERT。
2026-07-03 21:02:32 +08:00
jjaw f3a3a0869d 优化并发槽位清理 2026-07-03 21:02:31 +08:00
Heatherm Huang b2687b2258 fix(frontend): edit grok oauth model mapping 2026-07-03 19:43:54 +08:00
Wesley Liddick 28c2d20d9f Merge pull request #3675 from wucm667/fix/antigravity-401-auto-recover
fix(antigravity): 401 认证失败可自动恢复,不再永久卡在 error 状态
2026-07-03 16:58:37 +08:00
jianjian c620ad6a36 fix: align group capacity SQL with target schema 2026-07-03 08:44:39 +00:00
wucm667 d0a1443a41 fix(antigravity): allow oauth 401 auto recovery 2026-07-03 15:21:53 +08:00
Heatherm Huang 2b462b07de fix: preserve configured Grok OAuth concurrency 2026-07-03 14:41:18 +08:00
Wesley Liddick 87dfc66132 Merge pull request #3659 from jianjianai/fix/ops-realtime-stats-performance
fix: 优化运维实时账号统计接口性能
2026-07-03 10:45:24 +08:00
Wesley Liddick 5d7f213cbf Merge pull request #3657 from jianjianai/fix/redeem-invitation-bad-request
修复邀请码误走普通兑换接口时返回 500
2026-07-03 10:44:22 +08:00
weiness 36d5f4e4ca feat: make setup migration timeout configurable 2026-07-03 10:29:33 +08:00
shaw a5638a4e54 fix: match Codex session imports by chatgpt_user_id before shared account id
Members of the same ChatGPT team share chatgpt_account_id, so matching
imports by the account key first could overwrite another member's
account credentials. Identity keys are now ordered by strength
(user > email > access > account), and an account-key hit is rejected
when both sides carry different chatgpt_user_id values.

- Keep the account-key fallback when either side lacks a user id, so
  legacy accounts without chatgpt_user_id are updated and backfilled
  instead of duplicated
- Index all candidate accounts per shared key so a teammate's row can
  no longer shadow a legacy account depending on row order
- Apply the same conflict check to in-batch dedup and emit a warning
  when a legacy account is claimed via the shared account key
- Scope a 120s timeout to the Codex session import request instead of
  raising the global client timeout
2026-07-03 10:13:51 +08:00
jianjian 72ccd1b111 fix: batch group capacity summaries 2026-07-02 20:22:17 +00:00
jianjian 3f2ef60468 fix: optimize ops realtime account stats 2026-07-02 20:04:47 +00:00
jianjian 372436323a 修复邀请码普通兑换错误 2026-07-02 16:38:42 +00:00
github-actions[bot] 0b8e5eec32 chore: sync VERSION to 0.1.143 [skip ci] 2026-07-02 11:48:20 +00:00
Wesley Liddick 9caa3c9c5e Merge pull request #3635 from JRBaggins/hotfix/openai-oauth-count-tokens-latest
fix: handle OpenAI OAuth count_tokens scope errors
2026-07-02 17:41:43 +08:00
Wesley Liddick 3020652fa1 Merge pull request #3565 from zy6p/zy6p/pr-openai-ws-http-bridge
feat(openai-ws): 支持 http_bridge ingress 模式
2026-07-02 17:40:32 +08:00
Wesley Liddick 537bd50469 Merge pull request #3638 from infinityf4p/show-reset-credit-expiration
feat(openai): show reset credit expiration
2026-07-02 17:39:17 +08:00
Wesley Liddick 6bc6998b0e Merge pull request #3627 from alfadb/fix/ollama-anthropic-bearer-auth
fix(anthropic): 支持 API Key Bearer 认证方式
2026-07-02 17:37:30 +08:00
Wesley Liddick 4e23c77ece Merge pull request #3626 from DanisJiang/fix/claude-code-stream-keepalive
fix: prevent Claude Code stream keepalive stalls
2026-07-02 17:35:48 +08:00
Wesley Liddick 821399ade1 Merge pull request #3622 from deqiying/feat/subscription-revoke-restore
支持恢复已撤销订阅
2026-07-02 17:34:20 +08:00
Wesley Liddick 441b8ec2e2 Merge pull request #3614 from heathermhuang/codex/grok-media-group-gate-fix
fix: enable Grok media generation groups
2026-07-02 17:34:09 +08:00
Wesley Liddick fafdd8342f Merge pull request #3641 from infinityf4p/fix/openai-plan-type-workspace
fix(openai): avoid inactive workspace plan type overrides
2026-07-02 17:33:59 +08:00
Wesley Liddick 50dff6762c Merge pull request #3630 from seng1e/fix/codex-image-bridge-skip-compact
fix(openai): skip Codex image bridge injections for /responses/compact
2026-07-02 17:32:43 +08:00
Wesley Liddick e63492090a Merge pull request #3609 from phoenixikkifullstack/fix/antigravity-gemini-3.1-pro-high
fix: Handle invalid arguments correctly for Gemini reasoning models
2026-07-02 17:32:32 +08:00
Wesley Liddick 51b5142af7 Merge pull request #3612 from StarryKira/codex/fix-user-model-stats-requested-model
[codex] fix user model stats requested aggregation
2026-07-02 17:32:19 +08:00
Wesley Liddick 634b0eee48 Merge pull request #3430 from chaochaoweb3/codex/remove-claude-oauth-expires-in
fix: omit expires_in from Claude OAuth token exchange
2026-07-02 17:32:09 +08:00
Wesley Liddick fd66ae1ecb Merge pull request #3642 from infinityf4p/fix/openai-subscription-expiry-save
fix(openai): persist subscription expiration from OAuth
2026-07-02 17:31:58 +08:00
Wesley Liddick a32196eedd Merge pull request #3615 from DaydreamCoding/feat/usage-ip-geolocation
feat(usage): add IP geolocation lookup to usage tables
2026-07-02 16:42:06 +08:00
Wesley Liddick 3302146759 Merge pull request #3643 from bestony/feat/admin-group-column-settings
feat(admin): add group column settings
2026-07-02 16:40:59 +08:00
shaw 41af8e8e11 chore: update sponsors 2026-07-02 16:29:34 +08:00
Bestony@Homelab fa70a7217c test(keys): update api key contract concurrency field 2026-07-02 16:12:24 +08:00
shaw 11a3da65c0 fix(group): harden peak-rate config handling and label peak windows with server timezone
Follow-up fixes to #3569 based on code audit:

- Expose server_timezone / server_utc_offset in public settings (and the
  __APP_CONFIG__ injection payload) and label every peak-window display
  with the server UTC offset, so users don't misread the billing window
  as browser-local time
- Unify CreateGroup/UpdateGroup peak-config sanitization via a single
  NormalizePeakRateConfig chokepoint: non-subscription groups always get
  peak fields cleared; unparseable window strings and negative
  multipliers are scrubbed when peak is disabled
- Replace hot-path time.Parse in PeakMultiplierAt with a manual HH:MM
  parser (accept set verified byte-for-byte identical to
  time.Parse("15:04") by exhaustive fuzzing) and reuse it in validation
- Revert the zero-behavior CalculateCost indirection churn in
  billing_service/gateway_service introduced by #3569
- Remove dead GetGroupPlatformMap and the duplicate deref helper in the
  admin handler package
- Share frontend peak formatting via utils/peak-rate.ts, unify the ×N
  label format, and move hardcoded Chinese tooltips to i18n keys
2026-07-02 16:11:07 +08:00
Bestony@Homelab 089a7b7fae feat(keys): add api key concurrency stats 2026-07-02 15:58:29 +08:00
Heatherm Huang 99a8d8ad28 fix: normalize grok imagine image alias 2026-07-02 15:04:40 +08:00
Bestony@Homelab 5436d0bf10 feat(admin): add group column settings 2026-07-02 15:04:36 +08:00
Wesley Liddick a632cb0050 Merge pull request #3569 from xueshiji/main
feat(group): 订阅分组新增可选的高峰时段倍率,以支持智谱等coding plan的高峰时段
2026-07-02 14:55:34 +08:00
infinityf4p df59b8b966 fix: persist OpenAI subscription expiration 2026-07-02 14:38:08 +08:00
infinityf4p d0b8760eb9 fix: preserve OpenAI plan type from inactive workspaces 2026-07-02 14:37:30 +08:00
zy6p 5fe4ee1da1 style(test): gofmt oauth passthrough tests 2026-07-02 14:33:55 +08:00
infinityf4p dfb36e45f1 show reset credit expiration 2026-07-02 13:27:58 +08:00
JRBaggins c4128580f2 fix: handle OpenAI OAuth count_tokens scope errors 2026-07-02 11:42:10 +08:00
seng1e c797159bf5 fix(openai): skip Codex image bridge injections for /responses/compact
The Codex image_generation bridge block in Forward injects
tools:[image_generation] and tool_choice:"auto" (since #3498) into every
Codex request, including /responses/compact. The upstream compact endpoint
rejects tool_choice with 400 "Unknown parameter: 'tool_choice'", so every
remote compaction fails once the bridge is enabled. Guard the whole image
generation block with !isCompactRequest.

Fixes #3629
2026-07-02 11:09:50 +08:00
alfadb 7869b7fe38 fix(anthropic): 支持 API Key Bearer 认证方式 2026-07-02 10:43:08 +08:00
Yuhao Jiang a5781fe31d fix Claude Code stream keepalive stalls 2026-07-01 21:01:11 -05:00
deqiying b26dcc3da2 feat(subscription): 支持恢复已撤销订阅 2026-07-01 22:19:21 +08:00
DaydreamCoding 0ff93aca7a feat: add usage IP geolocation lookup 2026-07-01 21:49:49 +08:00
Heatherm Huang 9934bd2572 fix: default grok group media generation 2026-07-01 20:55:53 +08:00
Heatherm Huang 0435417f43 fix: enable grok media generation groups 2026-07-01 20:31:32 +08:00
github-actions[bot] 7dc7cfce1d chore: sync VERSION to 0.1.142 [skip ci] 2026-07-01 11:06:15 +00:00
shaw 98be187d56 chore: update sponsors 2026-07-01 19:04:40 +08:00
Haruka e236bff1ed fix: aggregate user model stats by requested model 2026-07-01 19:01:30 +08:00
phoenix 45be32b254 fix: remove space lines 2026-07-01 18:51:33 +08:00
shaw 60da9ba178 fix(frontend): refine grok icon, palette and i18n 2026-07-01 18:49:33 +08:00
xueshiji be26e6ff2c fix: test 2026-07-01 18:09:20 +08:00
phoenix f5b2961270 fix: Handle invalid arguments correctly for Gemini reasoning models 2026-07-01 18:08:25 +08:00
xueshiji a0eaf12a01 fix: test 2026-07-01 17:54:39 +08:00
Wesley Liddick 5eb9da9c93 Merge pull request #3593 from heathermhuang/codex/grok-media-routing
fix: route Grok media endpoints
2026-07-01 17:49:27 +08:00
xueshiji 5e99561d6e Merge branch 'Wei-Shaw:main' into main 2026-07-01 17:48:42 +08:00
xueshiji 1034f576d7 fix: 高峰倍率全链路透传、计费术语修正与边界处理
- 高峰倍率信息透传至可用渠道、支付计划、结算信息等 API,前端
  GroupBadge / GroupOptionItem / SubscriptionPlanCard / PaymentView /
  SubscriptionsView 统一展示高峰时段与倍率标签
- 修正计费术语:"文本倍率" → "token 倍率",明确高峰倍率同时作用于
  token 计费的图片 token,图片按次计费不受高峰影响
- 允许高峰倍率 multiplier=0,支持高峰时段免费策略
- 切换分组类型为 standard 时自动清除高峰倍率配置
- 长上下文计费与标准计费路径改用内部实现,移除冗余中间调用
- 前端高峰倍率相关控件文案改为 i18n
- 新增多组高峰倍率相关单元测试
2026-07-01 17:46:12 +08:00
zy6p 71c16b7230 fix(test): correct passthrough failover assertions 2026-07-01 17:20:55 +08:00
Heatherm Huang aac3261c69 fix: convert grok image edit uploads 2026-07-01 16:31:33 +08:00
Heatherm Huang f77cf6b477 Revert "feat: add LLM media tester"
This reverts commit a34d4967e6.
2026-07-01 16:08:21 +08:00
shaw 8c2d9b9a12 chore(openai): remove gpt-5.3-codex from OpenAI default model list
移除 OpenAI OAuth 账号默认模型列表中的 gpt-5.3-codex(后端 DefaultModels、前端 openai 白名单及 UseKeyModal 目录),保留 gpt-5.3-codex-spark 与计费/别名/messages-dispatch 相关逻辑不变。
2026-07-01 15:54:34 +08:00
Heatherm Huang c9fb221a31 fix: satisfy grok media lint 2026-07-01 15:42:00 +08:00
Wesley Liddick 0a9146c3d1 Merge pull request #3586 from deqiying/codex/fix-subscription-revoke-soft-delete
修复订阅撤销操作实际上是软删除的bug
2026-07-01 15:38:45 +08:00
Heatherm Huang 42e471f59a fix: harden grok media routing 2026-07-01 15:36:08 +08:00
Heatherm Huang a34d4967e6 feat: add LLM media tester 2026-07-01 15:12:48 +08:00
Wesley Liddick d679ee3c29 Merge pull request #3596 from JRBaggins/fix/tag-version-resolution
fix: source-build version resolution for release tags
2026-07-01 14:45:11 +08:00
xueshiji ebd83dc0f9 fix: 触发ci 2026-07-01 14:43:22 +08:00
shaw fd004bdd88 fix(account-repo): Clone query before Count to prevent state pollution
ListWithFilters reused the Ent query builder across Count() and All(),
so interceptor-appended predicates (SoftDeleteMixin's deleted_at IS NULL)
accumulated on the shared builder — the same pattern already fixed in
group_repo/promo_code_repo/user_repo by commit 2588fa6a (P1-03 audit).

The integration test now asserts pagination.Total matches len(items) on a
single page, guarding the total-vs-items invariant reported in #3601.

Fixes #3601
2026-07-01 14:35:49 +08:00
xueshiji e998f8044b fix: ci 2026-07-01 14:34:57 +08:00
JRBaggins de64b02612 fix: resolve build version from release tag 2026-07-01 14:25:46 +08:00
Wesley Liddick 3812e627a8 Merge pull request #3546 from nslogx/fix/platform-quota-five-platforms
fix: allow five platform quota updates
2026-07-01 14:07:08 +08:00
xueshiji 8b46994dc2 Merge branch 'Wei-Shaw:main' into main 2026-07-01 14:01:03 +08:00
Wesley Liddick 9817d5d5bf Merge pull request #3598 from DaydreamCoding/feat/openai-spark-shadow-account
feat(spark-shadow): OpenAI Spark 链接型影子账号
2026-07-01 14:00:52 +08:00
xueshiji 5e9529bb88 Merge branch 'Wei-Shaw:main' into main 2026-07-01 14:00:51 +08:00
shaw 96c9166b0e fix(usage): 用户使用记录默认显示推理强度列 2026-07-01 14:00:27 +08:00
DaydreamCodingandClaude Sonnet 5 bdf7ead157 feat(spark-shadow): OpenAI Spark 链接型影子账号
背景:gpt-5.3-codex-spark 使用独立于 codex 全局(5h/7d)的配额窗口(数据源是
/wham/usage 响应体的 codex_bengalfox,而非 codex 全局用的 x-codex-* 响应头),且
只能挂在已完成 OAuth 授权的 OpenAI 账号下复用其登录态,不能作为独立账号单独接入。
为此新增“链接型影子账号”(spark shadow account):影子账号本身不持有任何凭据,
通过 parent_account_id 指向母账号,凭据/token/代理透传自母账号并共享母账号的刷新
周期,仅在配额维度(quota_dimension=spark)和用量窗口上与母账号完全独立调度、互不
连坐。

实现:
- 数据模型:migration 154(+154a)给 accounts 表加 parent_account_id /
  quota_dimension 列 + 4 条约束(维度合法 / parent⟺非 global 维度一致 / 禁自指 /
  FK)+ 2 个 CONCURRENTLY 索引(母账号索引 + 每母账号至多一个影子的唯一索引)。
- 创建:POST /api/v1/admin/accounts/:id/shadow(CreateShadow)—— 一母一影(唯一
  索引兜底并发竞态),继承母账号 proxy/分组/并发/优先级(显式传参可覆盖),默认
  model_mapping 恒等映射到 spark(拒绝非 spark 模型),母账号必须是真实的 OpenAI
  OAuth 账号(非影子)。
- 凭据透传:resolveCredentialAccount 把影子解析回母账号,GetAccessToken / 请求头
  / WS 三条路径统一走此函数;影子自身 Credentials 恒为空(仅允许写 model_mapping),
  凭据写入的汇聚点 persistAccountCredentials 对影子早返 no-op,防止误写。
- 调度:parentHealthyForShadow 只看母账号是否仍是 OpenAI OAuth + 凭据/传输是否
  可用(active、token 未过期、未处于 401/刷新失败/传输故障导致的临时不可调度冷却),
  刻意不看母账号的 global 限流窗口——两条 429 道互不连坐。
- 用量:影子的 codex_5h/7d 走 OpenAIQuotaService.QueryUsage(/wham/usage 的
  codex_bengalfox),与母账号走的 WSv2 探测(/responses 头)完全独立的数据源、
  刷新节流与 staleness 判定。
- 备份:ExportData 显式排除影子账号(影子不持凭据,通用凭据型导入强制
  credentials 非空、无法表达父子链接),按 skipped_shadows 计数提示前端。
- 前端:账号操作菜单新增“创建 Spark 影子”入口,影子行展示回填的母账号信息
  (邮箱 / plan / 隐私模式 / 订阅到期 / chatgpt_account_id),批量操作自动跳过
  影子账号。

说明:migrations 目录用完整文件名(而非纯数字前缀)标识迁移,故本次新增的
154_account_spark_shadow.sql / 154a_..._notx.sql 与已有的
154_add_ops_system_logs_api_key_id.sql 按序号共存,与目录里 145/151 已有的
先例一致。

测试:新增约 20 个测试文件,覆盖 handler(CreateShadow 校验 / 母账号信息回填)、
repository(影子 round-trip / 一母一影唯一索引 / 迁移 schema)、service(凭据
透传三路径 / 调度母健康门 / 用量窗口来源与刷新节流 / CRS 母账号不变量 / 各类
早返与 fail-closed 场景)及前端组件(账号列表 / 操作菜单 / 用量重置)。

验证(镜像 CI;golangci-lint 首次全量分析耗时过长被跳过,其余全部实测):
- gofmt -l:干净
- go build ./... / go vet ./...:通过
- go test ./... -count=1:全绿(全部包 ok,含 internal/service、
  internal/repository、migrations)
- go test -tags integration ./internal/repository/... ./internal/service/...
  (真实 Postgres,testcontainers):全绿,含迁移幂等性
  (TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate)与影子相关全部用例
- pnpm lint:check / pnpm typecheck / pnpm build(真实 vite 构建)/
  pnpm vitest run:全绿(124 文件 760 用例)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 12:21:45 +08:00
zy6p e28b11ad75 fix(test): align compact passthrough expectation 2026-07-01 11:49:43 +08:00
Heatherm Huang c3e860607d fix: include official grok media model ids 2026-07-01 11:43:35 +08:00
Heatherm Huang 2fe756e4be fix: recognize grok media models 2026-07-01 11:43:35 +08:00
Heatherm Huang 3b5d812f7a fix: route grok media endpoints 2026-07-01 11:43:35 +08:00
Wesley Liddick 5f62625ae6 Merge pull request #3577 from heathermhuang/codex/fix-english-ui-cjk
fix: remove Chinese strings from English UI
2026-07-01 11:35:04 +08:00
zy6p 605b6f6632 fix(test): resolve oauth passthrough unit build errors 2026-07-01 11:34:16 +08:00
shaw db0414233c feat: 适配 sonnet5 2026-07-01 11:32:22 +08:00
Wesley Liddick e2d21c35af Merge pull request #3587 from alfadb/fix/openai-gpt-55-pro-codex-model
fix(openai): 保留 GPT-5.5 Pro Codex 模型名
2026-07-01 10:54:51 +08:00
shaw 59e9356c51 feat: 抹除 Anthropic OAuth 请求中客户端 dateline 隐写指纹
对 /v1/messages 转发到 Anthropic OAuth/setup-token 账号的请求做 dateline
归一化,将 system prompt 与 <system-reminder> 块中 "Today's date is …"
语句里的 4 种撇号变体与 "/" 日期分隔符还原为 ASCII 撇号 + "-",抹除某些
客户端在检测到非官方 base URL 时注入的 3 bit 隐写指纹。API Key 账号不受
影响。新增系统设置开关 enable_client_dateline_normalization,默认开启。
2026-07-01 10:54:18 +08:00
Wesley Liddick c1335bae12 Merge pull request #3588 from Ogannesson/fix/openai-codex-preserve-encrypted-reasoning
fix(openai): preserve encrypted reasoning across turns on codex OAuth path
2026-07-01 09:11:35 +08:00
alfadb b28a223337 fix(openai): 保留 GPT-5.5 Pro Codex 模型名
避免 Codex OAuth 模型归一化把 gpt-5.5-pro 降级为 gpt-5.5。\n\n同时补充 GPT-5.5 Pro 的计费回退与长上下文计费策略,保证保留上游模型名后仍使用现有 GPT-5.5/GPT-5.4 计费规则。\n\n验证:\n- go test ./internal/service/...\n- go build ./cmd/server/
2026-07-01 01:30:42 +08:00
deqiying 03727ac363 fix(subscription): 修复订阅撤销软删除失效
总: 增加明确的订阅撤销接口,修复撤销后的缓存失效和管理端 revoked 展示。

分: 同步失效订阅 L1 与 billing cache,补跨实例失效通知、soft-delete-aware 列表查询、revoked_at DTO 字段和回归测试。
2026-07-01 00:37:51 +08:00
Heatherm Huang 1158cf31bc fix: require localized interval validation 2026-06-30 22:31:21 +08:00
Heatherm Huang 41bb15d302 fix: remove remaining English fallbacks 2026-06-30 22:27:37 +08:00
Heatherm Huang b4f38b092f fix: preserve zh locale in i18n cleanup 2026-06-30 22:06:27 +08:00
Oganneson 73de2ea7f0 fix(openai): preserve encrypted reasoning across turns on codex OAuth path
PR #2068 dropped every reasoning item from input[] on the OAuth/codex path
(store=false). That silently discards encrypted_content -- the out-of-band
channel that carries reasoning context across turns under store=false --
degrading multi-turn agent reasoning with no visible error. Reported by
@neteroster on PR #2068.

The 404 that #2068 worked around ("Item with id 'rs_...' not found") is
triggered by the rs_* id lookup under store=false, not by the reasoning item
itself -- so the correct fix is to strip the id, not delete the item.

Verified end-to-end against the live chatgpt.com codex backend (gpt-5.5) and
a real OpenClaw container (api: openai-responses):
  - bare rs_ id, no encrypted_content  -> 404
  - id stripped                        -> 200
  - encrypted_content + id stripped    -> 200, reasoning context preserved
  - reasoning items require a summary field (missing -> 400)
  - real OpenClaw multi-turn agent loop -> all /v1/responses 200, zero 404

Fix: keep the reasoning item, strip only the rs_* id (always, independent of
PreserveReferences), preserve encrypted_content/content/summary verbatim, and
backfill an empty summary when absent. Tool-call call_id pairing is untouched.

Also verified compaction_summary items (cmp_*, the other encrypted_content
carrier): they require encrypted_content (missing -> 400) and their id does not
404 when present (kept or stripped), so the existing generic path already
handles them safely -- no special-casing needed.

Adds regression tests for each verified reasoning contract.

Refs #1957, #2068
2026-06-30 21:08:40 +08:00
Heatherm Huang 6e54776509 fix: localize English UI strings 2026-06-30 20:05:23 +08:00
github-actions[bot] dc1bc15457 chore: sync VERSION to 0.1.141 [skip ci] 2026-06-30 09:29:40 +00:00
Wesley Liddick 7cb98e5bdc Merge pull request #2007 from PMExtra/feature/user-usage-admin-parity
feat: align user usage analytics with admin
2026-06-30 17:10:09 +08:00
shaw 930326116e fix: 修复订阅支付金额显示错误 2026-06-30 16:26:22 +08:00
PMExtra cafc95c3e2 feat: align user usage analytics with admin 2026-06-30 15:31:28 +08:00
github-actions[bot] 89b2d63ef2 chore: sync VERSION to 0.1.140 [skip ci] 2026-06-30 06:26:55 +00:00
xueshiji 915c60b150 feat(group): 订阅分组新增可选的高峰时段倍率,以支持智谱等coding plan的高峰时段 2026-06-30 14:17:05 +08:00
shaw 345d5c6b56 chore: update sponsors 2026-06-30 14:11:14 +08:00
nslogx 6c46c2cb7d test: update platform quota contract for grok 2026-06-30 14:03:33 +08:00
Wesley Liddick 76e0d90736 Merge pull request #3509 from wucm667/fix/refund-pending-not-success
fix(payment): 退款 pending 不再当成最终成功,避免站内账务与网关状态不一致
2026-06-30 13:38:04 +08:00
Wesley Liddick 56ca027a0f Merge pull request #3568 from DaydreamCoding/fix/auth-signup-grok-platform-quota
fix(auth-signup): 平台配额快照脱离注册事务 + grok 补入 CHECK 约束
2026-06-30 13:37:54 +08:00
shaw bf4f006736 fix: 修复用户列表使用时间排序冲突 2026-06-30 13:36:36 +08:00
DaydreamCodingandClaude Opus 4.8 185f9c9920 fix(auth-signup): 平台配额快照脱离注册事务 + grok 补入 CHECK 约束
自助注册(含钉钉/OAuth)报 500→404 的根因:grok 自 2026-06 进入默认平台配额
(default_platform_quotas / auth_source_*),但 user_platform_quotas 的 CHECK
约束(迁移 142)仅允许 anthropic/openai/gemini/antigravity。注册时
snapshotPlatformQuotaDefaults 写 grok 行违反约束 → 整个注册事务被 Postgres 标记
aborted → consumePendingOAuthBrowserSessionTx 撞 "transaction aborted" → 500 →
clearCookies → 用户重试拿到 404(PENDING_AUTH_SESSION_NOT_FOUND)。
影响面:所有新自助注册(不限钉钉)。

修复(两层):
- 事务隔离(fix①):snapshotPlatformQuotaDefaults 用 ent.WithoutTx 剥离调用方事务,
  在基础连接 autocommit 执行。best-effort 快照失败永不毒化注册主事务,从根上消除
  "事务内 fail-open 形同虚设"陷阱——今后任何平台/约束漂移都不会再连累注册。
- 迁移 157:把 grok 加入 user_platform_quotas.platform 的 CHECK 约束,与代码平台
  列表(domain/constants.go PlatformGrok)对齐(DROP IF EXISTS + ADD,可重入)。

新增 ent.WithoutTx(ctx) helper(手写文件,不动生成代码)。

测试:
- 单测 TestSnapshotPlatformQuotaDefaults_DetachesCallerTransaction(RED→GREEN):
  快照即便在事务 ctx 中也必须用脱离事务的 ctx 调 repo。
- 集成测试 TestUserPlatformQuotaRepository_BulkInsertInitial_GrokAllowed:
  迁移 157 后 grok 可写入(真实 postgres 容器验证)。

验证:go build ./... / go vet -tags unit ./... / 全量单测(-tags unit,45 包) /
平台配额+迁移集成测试 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:22:27 +08:00
Wesley Liddick dcd8689d78 Merge pull request #3548 from dftian478/codex/中文-上下文窗口不切号
修复 OpenAI 上下文窗口错误误触发账号切换
2026-06-30 10:57:26 +08:00
Wesley Liddick efb8b45876 Merge pull request #3560 from StarryKira/feat/issue-3557-oauth-email
feat: fix OAuth email completion flow
2026-06-30 10:55:00 +08:00
Wesley Liddick ac6d4ccf09 Merge pull request #3549 from dftian478/codex/中文-gpt55-codex-instructions
修复 OpenAI GPT-5.5 的 Codex 指令选择
2026-06-30 10:53:35 +08:00
Wesley Liddick ac6f2e9693 Merge pull request #3559 from 315944211/fix/privacy-toast-result
fix: show privacy setting result accurately
2026-06-30 10:50:17 +08:00
zy6p 906be3f742 fix(openai-ws): honor explicit http_bridge ingress mode
(cherry picked from commit 9c0498a873f31472c94922a4115adab4587cd3e7)
2026-06-30 10:42:32 +08:00
zy6p 5654321380 fix(frontend): enable ws mode editing for openai setup-token accounts
(cherry picked from commit 73031d1ba8079b2f369a11b3de9542f27bd61405)
(cherry picked from commit acdbb9ce0471e5ab1009dc25e171a6497222347c)
2026-06-30 10:41:41 +08:00
zy6p 0476b5c97a fix(openai-ws): allow http_bridge accounts in ws ingress selection
(cherry picked from commit f0d215ec522e05043f5012a5f1e7aba2bfb57fcc)
(cherry picked from commit 7b14846a65e16570cb7b472b0ed06c6dcb190e0d)
2026-06-30 10:41:41 +08:00
Wesley Liddick e70e36e4d2 Merge pull request #3484 from bwliangc/feat/risk-control-matched-keyword
feat(risk-control): record matched keyword in keyword-block logs
2026-06-30 10:40:30 +08:00
zy6p 901958ba1b feat(openai-ws): add http_bridge ingress mode and account ws selector
(cherry picked from commit 58647ff63d7ff994c7c14c84c4913a8d3ed6be05)
(cherry picked from commit 9f18fb7c24e187fb20e90d1d9e89fcec91c56937)
2026-06-30 10:40:05 +08:00
wucm667 93a3bf3077 Fix refund pending finalization gaps 2026-06-30 10:19:50 +08:00
wucm667 7316d83027 fix(payment): 区分退款 pending 并收敛匿名查单 2026-06-30 10:00:48 +08:00
Wesley Liddick 55902d8548 Merge pull request #3558 from heathermhuang/codex/grok-cli-compat
fix: support Grok admin test and CLI compatibility
2026-06-30 08:42:59 +08:00
haruka 260fda19b3 feat: fix OAuth email completion flow 2026-06-30 01:11:51 +08:00
315944211 a0a3d0c33e fix: show privacy setting result accurately 2026-06-30 00:37:21 +08:00
Heatherm Huang 438510d298 fix: sanitize grok codex responses payloads 2026-06-29 21:37:30 +08:00
Heatherm Huang 10e623f674 fix: allow grok messages compatibility 2026-06-29 18:33:37 +08:00
Heatherm Huang 4a7148e203 fix: support grok cli compatibility routes 2026-06-29 17:53:19 +08:00
Wesley Liddick d3acd8e96e Merge pull request #3497 from JRBaggins/fix/openai-count-tokens-bridge
Bridge OpenAI count_tokens to responses input_tokens
2026-06-29 15:23:11 +08:00
dftian478 7cbf82ed65 修复 OpenAI 上下文窗口错误误触发账号切换 2026-06-29 10:29:23 +08:00
dftian478 709cf61853 修复 OpenAI GPT-5.5 的 Codex 指令选择 2026-06-29 10:25:43 +08:00
nslogx d86e83259e fix: allow five platform quota updates 2026-06-29 09:40:04 +08:00
Wesley Liddick c99112a9e9 Merge pull request #3515 from bestony/feat/ops-system-log-key-id
feat(ops): add key id filter to system logs
2026-06-29 09:24:40 +08:00
Wesley Liddick a22b12677f Merge pull request #3517 from bestony/feat/keys-column-settings
feat(keys): add API key column settings
2026-06-29 09:24:20 +08:00
Wesley Liddick b0dbc22fe5 Merge pull request #3510 from wucm667/fix/subscription-confirm-show-converted-amount
fix(payment): 订阅订单确认页显示换算后的 CNY 实付金额,而非 USD 套餐价
2026-06-29 09:24:01 +08:00
Wesley Liddick 7c857bd080 Merge pull request #3441 from deqiying/feature/openai-quota-headroom-scheduler
新增 OpenAI 剩余额度调度权重
2026-06-29 09:23:32 +08:00
Wesley Liddick bebafe7697 Merge pull request #3437 from imfangwenjie/fix/api-base-fetch
fix(frontend): use configured API base for direct requests
2026-06-29 09:23:13 +08:00
Wesley Liddick fc1e5a94a9 Merge pull request #3534 from mxyhi/fix/openai-quota-platform-post-billing
fix(openai): preserve quota platform in usage billing
2026-06-29 09:21:44 +08:00
Wesley Liddick 61f735a667 Merge pull request #3498 from liuaho6-commits/fix/codex-image-bridge-tool-choice
fix(openai): set tool_choice auto for Codex image bridge
2026-06-29 09:21:25 +08:00
Wesley Liddick b105cc0fd5 Merge pull request #3337 from ddnio/codex/openai-json-mode-developer-input
fix(openai): preserve JSON instructions in Codex OAuth input
2026-06-29 09:21:05 +08:00
Wesley Liddick 38577c6ca0 Merge pull request #3533 from Pluviobyte/codex/fix-api-key-unlimited-quota
fix(keys): reactivate exhausted keys set to unlimited
2026-06-29 09:19:53 +08:00
Wesley Liddick 47598462f5 Merge pull request #3401 from StarryKira/fix/issue-3394-fallback-pricing-log-spam
fix: stop per-request fallback-pricing log spam for unknown models (#3394)
2026-06-29 09:19:44 +08:00
Wesley Liddick 753c5e255c Merge pull request #3215 from fchange/fix/image-billing-false-positive
fix(openai): prevent false image billing on text-only /v1/responses requests
2026-06-29 09:19:34 +08:00
Wesley Liddick 3b18d1fa3d Merge pull request #3532 from smartcmd/fix/subscription-direct-pay-price
fix(payment): keep subscription price as direct pay amount
2026-06-29 09:19:24 +08:00
Wesley Liddick a822972d80 Merge pull request #3494 from devnomad-byte/docs/source-compile-admin-setup
docs: note admin account wizard requirement in source-compile install
2026-06-29 09:19:14 +08:00
mxyhi 82553c4dca fix(openai): preserve quota platform in usage billing 2026-06-28 11:13:09 +08:00
Pluviobyte da810c3b43 fix(keys): reactivate exhausted keys set to unlimited 2026-06-28 09:05:28 +08:00
daoge_cmd b1403e8b29 fix(payment): keep subscription price as direct pay amount 2026-06-28 06:29:01 +08:00
Bestony b244f850e7 feat(keys): add column settings 2026-06-27 15:14:13 +08:00
Bestony bad87ff533 feat(ops): add api key filter to system logs 2026-06-27 14:35:19 +08:00
wucm667 88ca0c1d13 fix(payment): 显示订阅 CNY 换算实付金额 2026-06-27 12:15:39 +08:00
Hao Liu e5f7836bf3 fix(openai): set tool_choice auto for Codex image bridge 2026-06-26 19:10:37 +08:00
JRBaggins 7a38c66214 Bridge OpenAI count_tokens to responses input_tokens 2026-06-26 17:59:26 +08:00
github-actions[bot] c275422251 chore: sync VERSION to 0.1.139 [skip ci] 2026-06-26 09:55:00 +00:00
shaw 9a0fbcc87d chore: update sponsors 2026-06-26 17:32:50 +08:00
Wesley Liddick df99b9417c Merge pull request #3495 from DaydreamCoding/feat/codex-detect-cli-only-engine-fingerprint
feat(codex-detect): codex_cli_only 检测加固 + 引擎指纹统一信号列表 + 账号级 app-server
2026-06-26 16:41:23 +08:00
DaydreamCodingandClaude Opus 4.8 819fda34d9 feat(codex-detect): codex_cli_only 检测加固 + 引擎指纹统一信号列表 + 账号级 app-server
将 codex_cli_only 客户端识别从「单一 strict 开关 + 固定 OR 头集合」重构为
可逐项管理的引擎指纹信号列表,加固整条判定链,并补齐账号级 app-server 控制、
对齐前端设置文案。

判定链(每步可短路):
- 账号未开 codex_cli_only → 不限制;gateway.force_codex_cli → 旁路放行
- 全局黑名单命中(OR 宽 deny)→ 立即拒
- 身份候选:官方 UA(strict,仅前缀)/ 官方 originator(OR)/ 全局白名单(双因子 AND)
  / 全局 app-server 开关 OR 账号 app-server 开关;均不命中 → 拒
- 版本门(仅官方候选):UA 须可解析引擎版本,再校验 [min,max] 区间
- 引擎指纹 AND 硬门:按信号列表逐条勾选 AND、每条行内变体 OR;无 Required 信号 → 放行

引擎指纹信号列表(唯一真源)
- 新增 openai.EngineFingerprintSignal 类型 + EvaluateEngineFingerprint 求值器
  (勾选 AND / 行内变体 OR / 无勾选 → 放行)
- CodexRestrictionPolicy 增 EngineFingerprintSignals;信号列表单一决定是否启用指纹门,
  不再保留独立「要求引擎指纹」总开关(与「信号全不选」语义重复)
- 新设置键 codex_cli_only_engine_fingerprint_signals(默认只勾 x-codex- 前缀);
  旧 body 指纹开关幂等迁移并入信号列表;wire 接线
- 黑/白名单自由条目、命名预设、版本区间 全局设置管线
- gateway 缺 settingService(仅测试/误配可达)时指纹门回退默认种子信号、失败关闭,
  不再因零值 policy(nil 信号)失败开放

账号级 Codex app-server(替换已失效的 ClaudeCode 放行机制)
- account.IsCodexCLIOnlyAppServerAllowed() 读 extra.codex_cli_only_allow_app_server,
  仅在 codex_cli_only 开启时生效;候选身份门「全局 OR 账号」,与旧系统双层控制对齐
- 移除已无入口的 claude_code 预设机制(allowedClientRegistry / MatchAllowedClients /
  账号 GetCodexCLIOnlyAllowedClients / reason);白名单 AllowedClientEntry / IsAllowedClientMatch 保留

门加固(反伪 + 写入校验)
- 官方 UA 访问门改 strict:IsCodexOfficialClientRequestStrict 仅前缀匹配,收窄「浏览器前缀 +
  中段 codex token」伪造面(strict 仍保留 Codex 家族前缀与 UA 尾部兜底,故对「任意前缀 +
  官方尾部 (name;ver)」仍放行——与 UA 可伪造、真正反伪靠引擎指纹门的设计一致)
- 官方客户端识别扩展:新增 codex-tui/、codex_vscode_copilot/ 前缀 + UA 尾部 (name;ver) 兜底
  (恢复 CODEX_INTERNAL_ORIGINATOR_OVERRIDE 的真实 client,如 cccc→codex-tui),originator 改
  精确集。该识别经 IsCodexOfficialClientByHeaders 被 passthrough 复用,故透传的官方判定一并
  修正(codex-tui 等不再被误改写 UA)——非「行为不变」,属有意修正
- 白名单写入校验 ValidateCodexWhitelistEntriesJSON + AllowedClientEntry.IsWhitelistable:
  双因子 AND 条目须可命中(非空 originator + 非空 ua_contains),拒绝写入会静默失效的死规则;
  黑名单(OR 宽 deny,允许 originator-only)不受约束

管理端 / 前端
- handler / DTO / settings_view / 契约测试;gateway 接入判定链
- 信号列表编辑器(替换 body 开关)、api 类型、SettingsView;无勾选给常驻警告
- Create/Edit/Bulk 三弹窗「Codex Only」下新增 app-server 开关(OR 合并全局)
- 文案:UA/Originator → User-Agent/Originator;黑/白名单重命名为 User-Agent/Originator 黑/白名单;
  「允许 App Server 第三方客户端」→「Codex app-server」+ 简介示例;i18n zh/en 同步
- 移除死代码 HasCodex*Fingerprint helper

测试:引擎指纹求值器 / 账号 app-server(OR 语义)/ detector(含 N1 strict、失败关闭)/
白名单写入校验 / BulkEdit spec 等;后端 build + service/openai/admin 单测全绿,前端 vue-tsc + vitest 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:19:41 +08:00
shaw f93a6c50ce fix: repair CI build & lint regressions on main
- openai_gateway_model_availability.go: pass platform through to
  listSchedulableAccounts so OpenAI/Grok diagnosis scopes to the
  correct candidate pool (build break introduced by Grok subscription
  PR #3310).
- no_account_error.go: drop redundant context.Context type on
  ctx := context.Background() to satisfy staticcheck ST1023.
2026-06-26 16:06:01 +08:00
Wesley Liddick a8cfafa02c Merge pull request #3489 from wucm667/fix/responses-anthropic-custom-tool-schema
fix(apicompat): Responses custom/freeform 工具转 Anthropic 时规范化 schema,修复 apply_patch 报错
2026-06-26 15:42:57 +08:00
Wesley Liddick d57106a56f Merge pull request #3490 from wucm667/feat/admin-cli-jwt-auth-fallback
feat(cli): sub2api-admin 支持 SUB2API_JWT 作为认证回退
2026-06-26 15:42:33 +08:00
Wesley Liddick 2fc4fef847 Merge pull request #3310 from heathermhuang/codex/grok-subscription-support
feat: add grok subscription support
2026-06-26 15:41:52 +08:00
shaw fcd3bc1272 fix: return 404 model_not_found instead of 503 when no account supports the model 2026-06-26 15:38:06 +08:00
devnomad-byteandClaude Opus 4.6 98feeccbe1 docs: note admin account wizard requirement in source-compile install
Method 3 (source compile) instructs users to pre-create config.yaml,
but does not mention that doing so disables the setup wizard — the
only code path that creates the initial admin account. Users following
the README end up with a running server and an empty users table, and
login fails with "invalid email or password".

The default.admin_email / default.admin_password fields in config.yaml
are loaded into the config struct but never read by any code path
(setup.SetupConfig.Admin is tagged yaml:"-"), so they cannot serve as
an alternative way to seed the admin.

Add a "Creating the Admin Account" subsection to Method 3 in all three
language READMEs (EN/CN/JA), explaining the wizard dependency and two
workarounds: let the wizard generate config.yaml, or temporarily move
config.yaml aside to trigger the wizard on first run.

Related: #521 (docker variant of the same symptom), #2617, #2350.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 15:19:58 +08:00
Heatherm Huang 44f502bab8 fix: address grok review feedback 2026-06-26 14:26:43 +08:00
wucm667 ac6e36f96b feat(cli): sub2api-admin 支持 SUB2API_JWT 认证回退 2026-06-26 14:11:25 +08:00
wucm667 40c8252734 fix(apicompat): 规范化 custom 工具 schema 2026-06-26 14:08:03 +08:00
Wesley Liddick 5f022663ac Merge pull request #3487 from lyen1688/fix/openai-response-failed-sanitize
fix: 修复 OpenAI response.failed 事件透传过长内容
2026-06-26 13:56:18 +08:00
lyen1688 8a7269f539 fix: sanitize verbose OpenAI response failed events 2026-06-26 12:29:58 +08:00
Wesley Liddick 683a8d8096 Merge pull request #3421 from syx0310/fork/openai-codex-pat-auth-upstream
feat: add codex personal access token auth
2026-06-26 11:15:38 +08:00
Wesley Liddick 54ef446c1d Merge pull request #3469 from wucm667/fix/responses-passthrough-duplicate-function-args
fix(openai): Responses passthrough 不再重复发出 function_call arguments,修复 Codex CLI 解析失败
2026-06-26 10:58:36 +08:00
bwlcandClaude Opus 4.8 815bc6c9b5 feat(risk-control): record matched keyword in keyword-block logs
The risk control center's moderation log records had no field for the
keyword that triggered a keyword block, so the admin UI couldn't show
which keyword was hit (only the application slog logged it).

- migration 156: add matched_keyword column to content_moderation_logs
- ContentModerationLog gains MatchedKeyword; set it on keyword block
- repo CreateLog/ListLogs persist and read the column
- frontend: show "命中关键词" inline in the log table and detail modal
- i18n: add matchedKeyword (zh/en)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:43:05 +08:00
Heatherm Huang 720db8983f test: harden grok quota readiness 2026-06-26 10:42:21 +08:00
Heatherm Huang 2a80495880 docs: clarify grok public route scope 2026-06-26 10:37:37 +08:00
Heatherm Huang 8a87a658ad test: cover grok readiness paths 2026-06-26 10:37:37 +08:00
Heatherm Huang 939905b8c7 fix: refine grok quota pause behavior 2026-06-26 10:37:37 +08:00
Heatherm Huang 0d28642181 feat: add grok quota probe parity 2026-06-26 10:37:37 +08:00
Heatherm Huang 1b9645ca32 fix: allow grok scheduler capability routing 2026-06-26 10:36:09 +08:00
Heatherm Huang e7a4f3f465 test: fix grok oauth validation suite 2026-06-26 10:36:09 +08:00
Heatherm Huang f29ccc7dfb fix: reduce grok oauth account-risk paths 2026-06-26 10:36:09 +08:00
Heatherm Huang b2e2c7e69c fix: harden grok oauth gateway paths 2026-06-26 10:36:09 +08:00
Heatherm Huang b3a07aeae7 fix: align grok oauth exchange with xai 2026-06-26 10:36:09 +08:00
Heatherm Huang 39be1ec97f feat: add grok subscription support 2026-06-26 10:36:09 +08:00
Wesley Liddick fd9c945fab Merge pull request #3470 from wucm667/fix/order-currency-symbol-from-field
fix(admin): 订单金额币种符号读取 currency 字段,修复 USD 渠道显示为 ¥
2026-06-26 09:46:02 +08:00
Wesley Liddick 94c059c99e Merge pull request #3471 from wucm667/fix/antigravity-standard-tier-empty-project-fallback
fix(antigravity): standard-tier 账号 project_id 为空时兜底,避免全部请求 500
2026-06-26 09:45:54 +08:00
Wesley Liddick 1721b2b2d1 Merge pull request #3404 from wucm667/fix/balance-preflight-prevent-overdraft
fix(billing): 余额扣减增加充足性校验,防止余额透支为负
2026-06-26 09:45:46 +08:00
Wesley Liddick 32ddd5c60b Merge pull request #3405 from wucm667/fix/subscription-order-apply-recharge-multiplier
fix(payment): 订阅套餐订单应用 BALANCE_RECHARGE_MULTIPLIER 汇率换算
2026-06-26 09:45:38 +08:00
Wesley Liddick d53dd112ce Merge pull request #3442 from hsn8086/fix/openai-chat-transport-failover
fix(openai): fail over on chat completions transport errors
2026-06-26 09:43:00 +08:00
Wesley Liddick 36a352f5eb Merge pull request #3429 from 404QAQ/fix/images-content-refusal-passthrough
fix(images): pass content-moderation refusals through as 400 instead of retrying
2026-06-26 09:42:50 +08:00
Wesley Liddick 9983f5b80a Merge pull request #3428 from Milesians/codex/fix-subscription-plan-validity-unit
fix(payment): support plural subscription validity units
2026-06-26 09:42:42 +08:00
Wesley Liddick 0bff6f9ac5 Merge pull request #3482 from SMGoro/fix/payment
fix(payment): 修复后端返回空supported_types时支付提供商卡片消失的问题
2026-06-26 09:42:33 +08:00
Wesley Liddick 6db9e42352 Merge pull request #3476 from zeyugao/main
Detect OpenAI overloaded errors by structured code
2026-06-26 09:42:22 +08:00
Wesley Liddick fd0da2570d Merge pull request #3481 from visa2/fix/gateway-glm-codex-tool-args-doubled
fix(apicompat): avoid doubling tool_call arguments from single-chunk upstreams (Codex + GLM)
2026-06-26 09:42:13 +08:00
Wesley Liddick 50c3e52799 Merge pull request #3460 from StarryKira/codex/fix-responses-cache-input
[codex] fix responses cache input for chat completions bridge
2026-06-26 09:42:04 +08:00
Wesley Liddick db1813f7a3 Merge pull request #3434 from feitianbubu/fix/codex-cli-only-chat-completions
fix(gateway): enforce codex_cli_only restriction on /v1/chat/completions
2026-06-26 09:41:46 +08:00
Wesley Liddick 6d50934de6 Merge pull request #3457 from feitianbubu/fix/admin-usage-cache-breakdown
fix(admin/usage): populate cache creation/read token breakdown in stats
2026-06-26 09:41:36 +08:00
Wesley Liddick 27278f62ed Merge pull request #3467 from jianjianai/fix/invalid-refresh-token-nonretryable
fix(token-refresh): treat refresh_token_invalidated as non-retryable / 添加 refresh_token_invalidated 到不可重试列表
2026-06-26 09:40:04 +08:00
Wesley Liddick d65b4c90ad Merge pull request #3435 from zichuanwangcloud-gif/fix/ops-dashboard-chart-infinite-height
fix(ops): prevent monitoring trend cards from growing unbounded
2026-06-26 09:39:06 +08:00
Wesley Liddick e89f4b43c0 Merge pull request #3433 from feitianbubu/fix/cc-terminal-attribution-header
fix(keys): add CLAUDE_CODE_ATTRIBUTION_HEADER=0 to Claude Code terminal templates
2026-06-26 09:38:57 +08:00
Wesley Liddick 38355ed204 Merge pull request #3436 from feitianbubu/fix/email-identity-error-shadowing
fix(auth): stop swallowing email auth identity create error via shadowed err
2026-06-26 09:38:49 +08:00
Wesley Liddick 03f6a28096 Merge pull request #3395 from 404QAQ/fix/errcheck-writestring
fix(lint): check WriteString return value to fix errcheck failure on main
2026-06-26 09:38:37 +08:00
shaw c9f42e1f77 fix(lint): gofmt token_refresh_service.go after refresh_token_invalidated addition 2026-06-26 09:34:20 +08:00
imfangwenjie 2a58a57a7c fix(frontend): use configured API base for direct requests
Route direct fetch calls, setup requests, gateway usage checks, and ops WebSocket connections through the configured API base instead of assuming same-origin backend paths.

Also derive OAuth callback suggestions from the configured API base so split frontend/API deployments show usable redirect URLs.
2026-06-26 02:42:44 +08:00
visa2andClaude Opus 4.8 29122e3051 fix(apicompat): avoid doubling tool_call arguments from single-chunk upstreams
When converting a Chat Completions stream into Responses events, the first
tool_call delta chunk was copied wholesale into stream state (including
function.arguments), then the same chunk's arguments were accumulated again by
the shared `+=` block. For OpenAI this is harmless because its first tool_call
chunk carries empty arguments, but upstreams that pack id+name+arguments into a
single chunk (e.g. GLM/Zhipu) end up with doubled arguments such as
{"cmd":"ls"}{"cmd":"ls"}. Codex then fails to parse the tool call with
"trailing characters", breaking every tool invocation.

Reset the copied arguments so the shared accumulator counts them exactly once,
keeping the emitted delta and the final done/arguments consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:58:38 +08:00
SMGDev 65ad7df4f4 fix(payment): 修复后端返回空supported_types时支付提供商卡片消失的问题
统一处理后端返回的null类型supported_types,将其标准化为空数组,避免调用includes()时报错导致卡片无法显示,同时修复多处相关的类型判断逻辑
2026-06-26 00:50:22 +08:00
chaochaoweb3 5bd9368ab4 fix claude oauth token exchange payload 2026-06-25 23:41:00 +08:00
Elsa cc7612bdbd Detect OpenAI overloaded error codes 2026-06-25 21:18:39 +08:00
Wesley Liddick ce6af41357 Merge pull request #3473 from DaydreamCoding/fix/gateway-openai-codex-spark-strip-image-tool
fix(gateway-openai): codex spark 剥离 image_generation 工具,修复 502
2026-06-25 17:43:24 +08:00
DaydreamCodingandClaude Opus 4.8 0112782049 fix(gateway-openai): codex spark 剥离 image_generation 工具,修复 502
gpt-5.3-codex-spark 为 text-only,不支持 image_generation 工具;codex CLI 默认
在 /responses 的 tools[] 携带该工具,导致 ChatGPT 后端返回 400
invalid_request_error(param=tools),被 mapUpstreamError 兜底成 502
"Upstream request failed"(真实 400 见 ops_error_logs.upstream_errors)。

新增 stripCodexSparkImageGenerationTools,对 spark 剥离客户端携带的
image_generation 工具(tools 清空则删键),接入三条路径:
- applyCodexOAuthTransformWithOptions:OAuth 全入口(responses/messages/chat-completions)
- buildUpstreamRequestOpenAIPassthrough 无条件 spark 块:覆盖 OAuth+APIKey
  /responses,不受 image-generation 开关影响
- WS stripCodexSparkImageGenerationToolFromRawPayload:覆盖 WebSocket /responses

bridge 注入(ensureOpenAIResponsesImageGenerationTool)本就跳过 spark;真正的缺口
在 normalizeOpenAIResponsesImageGenerationTools 会规范化并保留客户端工具。
临时绕过=换非 spark 模型;关 bridge/allow_image_generation 无效(工具是客户端带的)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:27:19 +08:00
wucm667 650c50e34b fix(antigravity): add project fallback for standard tier 2026-06-25 16:29:33 +08:00
wucm667 55242ffac1 fix(admin): 订单金额币种符号读取 currency 字段 2026-06-25 16:23:45 +08:00
wucm667 2b49d662cc fix(openai): dedupe passthrough function call args 2026-06-25 16:18:11 +08:00
简简aw 0a97a5f461 fix(token-refresh): treat refresh_token_invalidated as non-retryable 2026-06-25 15:12:21 +08:00
shaw 00d68ff6df feat(openai): add GPT-5.5 codex instructions and use as latest fallback 2026-06-25 11:44:42 +08:00
haruka dbdbfb1122 fix: avoid default codex instructions for chat bridge 2026-06-25 02:32:15 +08:00
feitianbubu 4567f6582b test(admin/usage): update sqlmock rows for cache breakdown columns 2026-06-24 23:59:22 +08:00
feitianbubu 063454ae96 fix(admin/usage): populate cache creation/read token breakdown in stats 2026-06-24 23:43:03 +08:00
shaw a1560ae77c chore: update sponsors 2026-06-24 22:14:47 +08:00
shaw 30adee43bc feat(admin/accounts): confirm before OpenAI weekly limit reset 2026-06-24 21:56:03 +08:00
deqiying a2cf297d90 feat: 新增 OpenAI quota headroom 调度权重 2026-06-24 00:13:22 +08:00
hsn 65fa728921 fix(openai): fail over on chat transport errors 2026-06-23 22:53:20 +08:00
feitianbubu 82576e0a38 fix(auth): stop swallowing email auth identity create error via shadowed err 2026-06-23 19:29:17 +08:00
zichuanwangcloud-gifandClaude Opus 4.8 9707dedca2 fix(ops): prevent monitoring trend cards from growing unbounded
The concurrency / switch-rate / throughput cards on the ops dashboard
sit in grid cells that only set `min-h-[360px]` (no definite height).
Their inner card uses `h-full`, which resolves to `auto` when the parent
height is `auto`. Combined with the Chart.js `responsive` +
`maintainAspectRatio: false` charts, this forms a height feedback loop:
the canvas reads the parent height to size itself, the content then grows,
the next ResizeObserver tick reads an even larger height, and the cards
stretch downward without bound.

On wide screens (`lg:grid-cols-4`) a sibling card usually fixes the row
height via `align-items: stretch`, masking the issue. It surfaces when no
sibling bounds the row height — e.g. the single-column (`grid-cols-1`)
stacked layout on narrow viewports, or when the concurrency card collapses
to little content. `min-h` only sets a floor, not a ceiling.

Fix: give the two Chart.js canvas cells a definite height (`h-[360px]`) so
the responsive resize has a fixed reference and the loop cannot run. The
concurrency card is not a responsive canvas, so it keeps `min-h-[360px]`
to avoid clipping its content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:46:17 +00:00
feitianbubu ae5e980dd1 fix(gateway): enforce codex_cli_only restriction on /v1/chat/completions 2026-06-23 18:38:52 +08:00
feitianbubu 28e7adef09 fix(keys): add CLAUDE_CODE_ATTRIBUTION_HEADER=0 to Claude Code terminal templates 2026-06-23 18:27:49 +08:00
404QAQ 9491de0a39 fix(images): pass content-moderation refusals through as 400 instead of retrying
When gpt-image upstream returns response.completed with no image but a text
refusal (content moderation, e.g. the model replies "this request was judged
unsafe to generate"), the soft-failure path treated it as a probabilistic
upstream failure and returned a retryable UpstreamFailoverError (502). Retrying
or switching accounts is futile for a content-policy block — it just burns
other accounts' quota and still surfaces an opaque 502 to the client.

Distinguish the two no-image cases:
  (A) model text refusal  -> 400 content_policy_violation, no retry, refusal
      reason passed through to the client.
  (B) truly empty response -> unchanged: retryable UpstreamFailoverError (502).

Adds extractOpenAIImagesModelRefusal (extracts the refusal text from
output_text.delta / message output_text, capped at 600 chars) and two unit
tests; the existing empty-response retry test is unaffected.
2026-06-23 16:57:16 +08:00
milesians 147c1879d9 fix(payment): support plural subscription validity units 2026-06-23 15:22:55 +08:00
syx0310 32df33a1c3 feat: add codex personal access token auth 2026-06-22 16:07:41 +00:00
shaw 85a3b12254 chore: update sponsors 2026-06-22 16:37:56 +08:00
wucm667 c6f375d3ab fix(payment): 订阅订单应用充值汇率换算 2026-06-22 10:33:26 +08:00
wucm667 9f5b57fc96 fix(billing): 防止余额计费持续透支 2026-06-22 10:30:27 +08:00
shaw e5f38a6f60 chore: update sponsors 2026-06-22 09:55:31 +08:00
github-actions[bot] d430343f51 chore: sync VERSION to 0.1.138 [skip ci] 2026-06-22 01:24:13 +00:00
harukaandClaude Opus 4.8 6239e395b8 i18n(channel): explain case-insensitive matching in pricing conflict messages (#3394)
Update the modelConflict / mappingConflict strings (en + zh) to state that
model names are matched case-insensitively, so an existing entry (e.g.
"GLM-5.2") already covers all case variants and the lowercase variant does not
need to be added. This addresses the confusion in #3394 where the rejection of
a case-only duplicate looked like inconsistent behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:47:49 -07:00
harukaandClaude Opus 4.8 7c2fee6c99 fix(billing): dedup fallback pricing warn to stop per-request log spam (#3394)
The "[Billing] Using fallback pricing for model: X" warn was emitted on
every request for any model not in LiteLLM but matched by getFallbackPricing
(e.g. "glm-5.2" substring-matches the "glm-5" fallback price). Via the stdlib
log bridge it is inferred as WARN and persisted to ops_system_logs, producing
tens of thousands of rows/day. It fired even when channel pricing already
overrides the price, and from several call sites (resolver, account-stats
pricing, non-channel billing, admin lookup).

Dedup the warn at the source (BillingService.GetModelPricing) via a sync.Map
keyed by the already-lowercased model name, so each model logs at most once
per process while keeping one audit line. Billing amounts are unchanged.

Also clarify the model/mapping pattern conflict error to state that names are
matched case-insensitively, so an existing entry (e.g. "GLM-5.2") already
covers all case variants and the lowercase variant need not be added — the
behavior the issue mistook for a case-sensitivity bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:46:52 -07:00
shaw 6936687870 fix(lint): check WriteString return in summarizeOpenAIImagesNoOutputBody
修复 PR #3381 引入的 errcheck lint 错误,satisfy golangci-lint
对 strings.Builder.WriteString 返回值的检查要求。
2026-06-21 21:43:58 +08:00
404QAQ f4b51b0f2b fix(lint): check WriteString return value in summarizeOpenAIImagesNoOutputBody
golangci-lint (errcheck) flagged the unchecked (*strings.Builder).WriteString
return value at openai_images_responses.go:632. Align with the existing
convention used elsewhere in the package (e.g. gateway_service.go) by
explicitly discarding the return values with `_, _ =`.
2026-06-21 21:39:44 +08:00
Wesley Liddick 8105846f33 Merge pull request #3326 from tairan/fix/selinux-bind-mount-labels
fix(deploy): add :Z SELinux labels to bind mounts in compose files
2026-06-21 21:23:27 +08:00
Wesley Liddick f079742bab Merge pull request #3371 from cugxuan/feat/subscription-affiliate-rebate
feat: apply affiliate rebate to subscription payments
2026-06-21 21:18:50 +08:00
Wesley Liddick a560d2f91e Merge pull request #3363 from kangjwme/feat/prefer-soonest-reset-scheduling
feat(scheduling): opt-in "prefer soonest reset" account selection
2026-06-21 21:17:44 +08:00
Wesley Liddick d2ff9c0c4c Merge pull request #3369 from skyswordw/codex/default-ccswitch-openai-gpt-55
fix(ccswitch): default OpenAI import model to gpt-5.5
2026-06-21 21:16:07 +08:00
Wesley Liddick 92ce5bfe24 Merge pull request #3347 from SavitarC/fix/usage-cache-token-tooltip
fix(usage): 显示缓存 Token 明细
2026-06-21 21:15:45 +08:00
Wesley Liddick febfe538b7 Merge pull request #3344 from Milesians/main
fix(frontend): refresh custom page document title
2026-06-21 21:15:17 +08:00
Wesley Liddick 6e27b82335 Merge pull request #3381 from 404QAQ/fix/images-incomplete-failover
fix(images): 识别 response.incomplete 触发 failover + 记录软失败上游响应
2026-06-21 21:14:48 +08:00
Wesley Liddick f6d734ec23 Merge pull request #3308 from a0yark/fix/gemini-tool-schema-cleanup
fix(gemini): clean unsupported tool schema fields
2026-06-21 21:14:09 +08:00
Wesley Liddick af1a032d35 Merge pull request #3375 from StarryKira/fix/3358-vertex-beta-and-cch
fix(gateway): filter anthropic-beta on the Vertex Anthropic path + drop cch sign (#3358)
2026-06-21 21:13:14 +08:00
Wesley Liddick 93de19665b Merge pull request #3359 from alfadb/fix/glm-effort-mapping
修复 GLM 推理强度映射
2026-06-21 21:12:46 +08:00
Wesley Liddick bed12b7016 Merge pull request #3360 from feitianbubu/fix/auto-mode-cc-entrypoint-ide
fix(gateway): in auto mode recognize Claude Code IDE clients via any cc_entrypoint
2026-06-21 21:12:33 +08:00
Wesley Liddick f597e926da Merge pull request #3335 from FjlI5/fix/openai-upstream-endpoint-logging
fix: 修正 chat-only API-key 账号上游端点被误记为 /v1/responses
2026-06-21 21:12:15 +08:00
Wesley Liddick 7b5fbe5197 Merge pull request #3364 from feitianbubu/fix/promo-clear-expiry
fix(promo): allow clearing promo code expiry on edit
2026-06-21 21:12:02 +08:00
Wesley Liddick 04494fd0d9 Merge pull request #3312 from 315944211/codex/update-pnpm-action-setup-node24
chore: update pnpm action setup
2026-06-21 21:11:43 +08:00
Wesley Liddick 945b9b208b Merge pull request #3374 from wucm667/fix/email-binding-enforce-suffix-whitelist
fix(auth): 邮箱身份绑定流程同样校验注册后缀白名单,堵住绕过
2026-06-21 12:56:53 +08:00
404QAQ b0d5592ae2 fix(images): 识别 response.incomplete + 记录软失败上游响应
修复 gpt-image-2 大图/编辑请求 502 报错(社区 issue #2232/#3135/#2516,
现象:upstream did not return image output)。根因是上游生成超时/截断时返回
response.incomplete,旧逻辑只认 error/response.failed,导致:
1) 软失败报成模糊 502 且不触发 failover 换账号重试
2) 上游真实响应未记录,ops_error_logs 里上游信息全空,无法排查

- openAIImagesUpstreamErrorFromSSEPayload 增加 response.incomplete 识别:
  生成超时/截断(max_output_tokens 等) → 可重试 502 触发 failover;
  content_filter/moderation → 400 不重试
- 软失败兜底(无图无标准错误)记录上游诊断摘要到 ops(last_event/status/
  incomplete_reason/body 片段),非流式+流式两条路径一致
- summarizeOpenAIImagesNoOutputBody 提取诊断信息,body 截断上限 1KB

基于官方 v0.1.137 净分支。测试: 5 个新单测 + 图片回归通过 (-tags unit)
2026-06-21 08:34:12 +08:00
harukaandClaude Opus 4.8 5cb8cdd36c test(claude-code): detection recognizes the new-CLI billing block (no cch)
Locks in that Claude Code detection keys on the billing block prefix +
cc_entrypoint=cli, not on the cch field that the new CLI (and now our own
mimicry) no longer sends:

- BillingBlockRecognizedWithoutCCH: an identity-prose-less sub-request whose
  system block is `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli;`
  (no cch) is still detected as Claude Code.
- NoCCHBlockStillRequiresClaudeCodeUA: dropping cch did not loosen detection —
  a non-claude-cli UA is still rejected, so ClaudeCodeOnly groups can't be spoofed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:02:44 -07:00
harukaandClaude Opus 4.8 6cfb7898df fix(claude-mimicry): drop the cch sign to match new Claude Code CLI
Recent Claude Code CLI versions no longer emit the cch=... signature field in
their x-anthropic-billing-header system block (issue #3358). sub2api still
injected cch=00000 when mimicking Claude Code for OAuth accounts and optionally
signed it, so mimicked requests now diverge from real CLI traffic — the opposite
of what the mimicry is for.

- buildBillingAttributionText emits the block without the cch=00000 segment;
  cc_version + cc_entrypoint=cli are kept (detection and Anthropic's first-party
  signal rely on the block, not on cch).
- Retire signing: remove the two enableCCH signBillingHeaderCCH call sites in
  buildUpstreamRequest / buildCountTokensRequest and delete the now-dead
  signBillingHeaderCCH, cchPlaceholderRe, cchSeed, xxHash64Seeded helpers.
- enable_cch_signing is now a documented no-op (kept for backward compat).
- Drop the obsolete signing tests (TestSignBillingHeaderCCH, TestXXHash64Seeded,
  TestSanitizeMustBeBeforeCCHSigning_HashConsistency) and update the prompt test
  to assert the injected block no longer carries cch=.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:01:29 -07:00
harukaandClaude Opus 4.8 efffd5d791 test(gateway): Vertex anthropic-beta filtering
Covers the #3358 fix:
- StripsUnsupportedClaudeCodeTokens reproduces the prod 400 — the four Vertex-
  rejected tokens (advisor-tool, prompt-caching-scope, redact-thinking,
  thinking-token-count) plus the identity betas are stripped while whitelisted
  tokens survive. Fails before the builder fix, passes after.
- DropsHeaderWhenAllUnsupported: no anthropic-beta header is sent when every
  client token is filtered out.
- BodySanitizeKeysOnFinalBeta: body.context_management is stripped based on the
  final beta, not the raw client value.
- BlocksViaBetaPolicy: an admin block rule on a Vertex account returns BetaBlockedError.
- TestFilterVertexBetaTokens unit-tests whitelist/drop-set/dedupe/empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:53:32 -07:00
harukaandClaude Opus 4.8 40e1cc14b3 fix(gateway): filter anthropic-beta on the Vertex Anthropic path (#3358)
Vertex AI's Anthropic endpoint rejects unknown anthropic-beta tokens with
HTTP 400. buildUpstreamRequestAnthropicVertex forwarded the client header
verbatim via the allowedHeaders whitelist, so recent Claude Code CLIs that
send advisor-tool-2026-03-01, prompt-caching-scope-2026-01-05,
redact-thinking-2026-02-12 and thinking-token-count-2026-05-13 broke every
Vertex service_account request, even though plain account-test requests passed.

This is the only upstream builder that bypassed beta filtering: the
OAuth/API-key path uses computeFinalAnthropicBeta and the Bedrock path uses
filterBedrockBetaTokens. Close the gap with a Vertex-specific whitelist
(vertexSupportedBetaTokens) mirroring bedrockSupportedBetaTokens, plus the
existing BetaPolicy block check:

- evaluateBetaPolicy block check (symmetric to resolveBedrockBetaTokensForRequest)
- filterVertexBetaTokens strips policy-filtered + non-whitelisted tokens
- body context_management sanitize now keys on the final beta, not the raw client value
- overwrite the anthropic-beta header after the whitelist copy loop with the final value

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 07:51:24 -07:00
wucm667 ecedc7c8d3 fix(auth): enforce email bind suffix whitelist 2026-06-19 21:17:45 +08:00
cugxuan 0fa604ba71 feat: apply affiliate rebate to subscription payments 2026-06-19 17:47:10 +08:00
skywalker d3dfa28f8e Update CC Switch OpenAI default model 2026-06-19 17:11:25 +08:00
feitianbubu 2dc1387b59 fix(promo): allow clearing promo code expiry on edit 2026-06-18 23:07:25 +08:00
kangjwme 510adf703c feat(scheduling): add opt-in "prefer soonest reset" account selection
Adds a use-it-or-lose-it scheduling strategy: prefer accounts whose
session window resets soonest, so near-reset accounts get drained first
instead of accounts whose reset is still far away.

Both schedulers, opt-in, default behavior unchanged:

- Anthropic (gateway_service.go): new GatewaySchedulingConfig
  .PreferSoonestReset flag. When on, the layered load-aware selection
  inserts a filterBySoonestReset stage (priority -> soonest-reset ->
  load -> LRU). Accounts with no active SessionWindowEnd are treated as
  lowest priority; ties fall through to LRU.

- OpenAI/Codex (openai_account_scheduler.go): new "reset" score weight
  in GatewayOpenAIWSSchedulerScoreWeights. Soonest-reset accounts score
  higher; weight defaults to 0 (no effect).

SessionWindowEnd (upstream 5h/quota ResetsAt) is already carried in the
scheduler snapshot, so no snapshot changes are needed.

Documented in deploy/config.example.yaml. Adds unit tests for the
Anthropic filter and the OpenAI reset factor.
2026-06-18 22:50:46 +08:00
feitianbubu e3e31bd4c5 fix(gateway): auto mode recognize Claude Code IDE clients via any cc_entrypoint 2026-06-18 19:22:10 +08:00
alfadb 89cfe24a0c fix(openai): normalize glm reasoning effort 2026-06-18 19:07:08 +08:00
SavitarC 51d722906c fix(usage): 显示缓存 Token 明细 2026-06-18 14:34:13 +08:00
milesians 952be8711b fix(frontend): refresh custom page document title 2026-06-18 12:30:21 +08:00
欧阳丹 df51edfb06 Preserve OAuth instructions while keeping developer input 2026-06-18 03:15:31 +08:00
FjlI5 bab8a9a93e fix(openai): log /v1/chat/completions upstream endpoint for chat-only API-key accounts
DeriveUpstreamEndpoint maps every OpenAI-platform request to /v1/responses, but
API-key accounts whose upstream only speaks Chat Completions
(!ShouldUseResponsesAPI) are forwarded directly to /v1/chat/completions. The
messages, responses and cyber-policy recording sites derived the endpoint via the
bare GetUpstreamEndpoint, so usage/ops records mislabeled those requests as
/v1/responses. Generalize the existing resolveRawCCUpstreamEndpoint into
resolveOpenAIUpstreamEndpoint and use it at every OpenAI recording site, matching
the already-correct chat-completions client path.
2026-06-18 00:29:04 +08:00
WANG TAIRAN 31640363fc fix(deploy): add :Z SELinux labels to bind mounts
Add the :Z (private unshared) SELinux label to all bind-mounted
directories in docker-compose.local.yml and docker-compose.dev.yml.

On systems with SELinux in Enforcing mode (e.g. Fedora, RHEL, CentOS),
containers using bind mounts are denied access to host directories
because the default 'user_home_t' context is not accessible to
container processes. The :Z label tells the container runtime to
relabel the mount point with 'container_file_t' so the container
can read/write it.

Named volumes in docker-compose.yml are not affected because the
runtime already handles their labels automatically.

Fixes permission-denied errors on:
- ./data:/app/data
- ./postgres_data:/var/lib/postgresql/data
- ./redis_data:/data
2026-06-17 12:38:40 +08:00
a0yarkandCursor 8c4a43cf72 fix(gemini): satisfy schema cleanup test lint
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 23:19:45 +08:00
315944211 369f53a7a4 chore: force node24 for cla action 2026-06-16 21:12:09 +08:00
315944211 abc203a37b chore: update pnpm action setup 2026-06-16 21:12:09 +08:00
github-actions[bot] 4a5665da5b chore: sync VERSION to 0.1.137 [skip ci] 2026-06-16 12:59:59 +00:00
Wesley Liddick eba9bea959 Merge pull request #3278 from 315944211/feat/show-account-id-in-admin-list
feat: show account id in account list
2026-06-16 20:35:55 +08:00
Wesley Liddick de38d62360 Merge pull request #3250 from WesleyZiwen/codex/anthropic-429-window-reset
fix: preserve Anthropic window cooldowns
2026-06-16 20:34:36 +08:00
Wesley Liddick 9e9e154f57 Merge pull request #3306 from bestony/fix/ip-acl-denial-message
fix(auth): include client ip in acl denial message
2026-06-16 20:32:50 +08:00
Wesley Liddick 7fb3e1aacd Merge pull request #3247 from alfadb/fix/reasoning-and-thinking-protocol
fix(gateway): 整合推理强度与思考协议处理(替代 #2155 / #2136 / #3246)
2026-06-16 20:29:21 +08:00
Wesley Liddick f204216918 Merge pull request #3243 from alfadb/feature/chinese-llm-fallback-pricing
feat(billing): 国产 LLM 兜底定价 (GLM / Kimi / MiniMax) + 收编 DeepSeek V4
2026-06-16 20:28:49 +08:00
Wesley Liddick 03ec90a25a Merge pull request #3223 from feitianbubu/fix/intercept-streaming-haiku-probe
fix: 修复CC Switch改为流式测试后请求失败的问题
2026-06-16 20:27:33 +08:00
shaw 6c7203d83b fix(gateway): preserve SSE event:error body so ops logs reflect real upstream errors
When an Anthropic upstream returned HTTP 200 but then emitted an SSE
`event: error` frame (overloaded_error / rate_limit_error / api_error /
etc.), Forward's stream branch matched on `err.Error() == "have error in
stream"` and returned `UpstreamFailoverError{StatusCode: 403}` with no
ResponseBody. That dropped three pieces of evidence:

- handleFailoverExhausted → ExtractUpstreamErrorMessage(nil) = "" →
  ops_error_logs.upstream_error_message was empty.
- errorPassthroughService.MatchRule(_, 403, nil) could only match rules
  without keywords, so keyword-based passthrough rules silently never
  fired.
- upstream_errors carried no stream_error record, leaving ops looking at
  a generic 403 with no clue whether the upstream was throttled,
  overloaded, or rejecting the request. ping-during-slot-wait amplified
  this by skipping failover (writerSizeBeforeForward guard), so 403s
  ballooned in the ops view well past the upstream's actual rate.

Fix:
- Introduce *sseStreamErrorEventError that carries the SSE data line.
  Error() still returns "have error in stream" so existing log searches
  keep working.
- Forward extracts via errors.As, appends an OpsUpstreamErrorEvent
  (kind="stream_error", with the sanitized message and a truncated raw
  body honoring LogUpstreamErrorBody*), and returns
  UpstreamFailoverError{StatusCode: 403, ResponseBody: rawJSON}.

StatusCode 403 is preserved verbatim: mapUpstreamError, failover
decisions (shouldFailoverUpstreamError(403)=true), client-visible message,
RetryableOnSameAccount, and rateLimitService side-effects (this path
already didn't invoke them) all match prior behavior. OAuth and API Key
accounts share this path; the API-Key passthrough branch is independent
and already forwards SSE error frames untouched, so it's unaffected.

Adds four unit tests: typed-error contract + RawData, empty data line,
event:error after partial stream output (streamStarted=true), and
non-JSON data line.
2026-06-16 20:25:09 +08:00
alfadb a05d9e87c0 feat(billing): 国产模型 thinking-enabled 自动填充 reasoning_effort 默认值
问题:Kimi/GLM/MiniMax 等国产 LLM 协议层只有 thinking on/off 开关,没有
reasoning_effort 档位概念。客户端启用 thinking 后 usage_log.reasoning_effort
长期为 NULL,无法在用量分析里区分 'thinking 开启' 与 'thinking 关闭'。

方案:仅在 'thinking 启用 + 上游属于 passback-required 国产模型 + 客户端
未明确指定 effort' 三者同时成立时,给 usage_log.reasoning_effort 写默认值
'high'(与 DeepSeek thinking-enabled 默认 effort 一致)。

设计原则:
1. **白名单**:仅 ResolveThinkingProtocol == PassbackRequired 集合内,
   且排除原生支持 effort 的 DeepSeek(避免覆盖客户端意图)。
2. **fail-open**:客户端显式传 effort 时永远不覆盖。
3. **未来兼容**:如 Kimi 后续加入真 effort 档位,客户端开始发 effort,
   guard (3) 自动让出,本逻辑变 no-op。

实现:
- gateway_request.go: 加 DefaultEffortForThinkingEnabled (按模型白名单)
  + OpenAIBodyHasThinkingEnabled (检测 OpenAI 协议 body 里的 thinking.type)
  + ApplyThinkingEnabledFallback (包装现有 extractor 的 nil-then-default 逻辑)
- gateway_handler.go: Anthropic 路径两处(主 + retry)对称补充
- OpenAI 路径全覆盖:openai_gateway_service.go (passthrough + non-passthrough)
  + openai_gateway_chat_completions_raw.go + openai_gateway_responses_chat_fallback.go
  + openai_ws_http_bridge.go + openai_ws_forwarder.go
- 跨协议路径:gateway_forward_as_chat_completions.go (CC client → Anthropic upstream)
  + gateway_forward_as_responses.go (Responses client → Anthropic upstream)
  + gemini_chat_completions_compat_service.go (一致性保持)

未覆盖:openai_ws_v2_passthrough_adapter.go 两处。原因:该 adapter 持有的是
session-level 客户端原始 model,没有 *Account 句柄无法走 GetMappedModel。
WS v2 当前对国产模型场景不重要(pi 调用 Kimi/GLM/MiniMax 走 sync HTTP),
留待后续如果出现 WS v2 + 国产模型用例时单独处理。

测试:
- TestDefaultEffortForThinkingEnabled (14 用例):覆盖 Kimi/GLM/MiniMax 大小写、
  Qwen thinking 变体、DeepSeek 排除、Claude/GPT/Gemini 不命中。
- TestOpenAIBodyHasThinkingEnabled (8 用例):covers enabled/adaptive/disabled、
  大小写、空 body、缺字段、invalid JSON fail-safe。
- TestApplyThinkingEnabledFallback (9 用例):现有 effort 不覆盖、nil + 启用 +
  passback → high、nil + disabled → nil、nil + 启用 + 排除模型 → nil。
2026-06-16 19:37:31 +08:00
alfadb 5c5283979b doc(thinking-protocol): clarify mappedModel vs originalModel semantics per call path
回应 PR #3247 Copilot review:

1. NormalizeChineseLLMThinking 文档写 'MiniMax M3 / M3.x' 但实现覆盖
   minimax-m* (M2.x 也命中)。改成 'MiniMax M-series (M2.x/M3.x)'。

2. Copilot 质疑 gemini_messages_compat 传 originalModel 不是
   mappedModel 是误用。实际是有意为之的跨协议场景:
   - 上游是 Gemini,但被剥离的 body 是 Anthropic 格式
   - 剥离逻辑要按客户端请求的 Anthropic 子协议族判定
   - 传 mappedModel (gemini-3.1-pro) 会被判为 Unknown→不剥离→retry 死循环

   未改代码逻辑,改为更新文档:
   - thinking_protocol.go ResolveThinkingProtocol 添加「调用路径语义」
     说明 Anthropic gateway 与 Gemini compat 两条路径的参数语义差异。
   - gemini_messages_compat_service.go 调用点加 inline 注释解释
     路径语义为什么该传 originalModel。
2026-06-16 19:37:31 +08:00
alfadb 56c6325d15 fix(gateway): rewrite thinking.type=enabled to adaptive for MiniMax M-series
\u95ee\u9898\uff1aAnthropic-SDK \u5ba2\u6237\u7aef\uff08\u5982 pi-ai\uff09\u5728 budget-based thinking \u8def\u5f84\u4e0a\u9ed8\u8ba4\u53d1
`thinking: { type: 'enabled', budget_tokens: ... }` \u7ed9\u4efb\u4f55 Anthropic \u517c\u5bb9\u4e0a\u6e38\uff0c\u5305\u62ec
MiniMax M3 / M2.x \u3002\u4f46 MiniMax \u5b98\u65b9\u6587\u6863\u660e\u786e\u5217\u51fa\uff1athinking.type \u53ea\u63a5\u53d7
"adaptive" \u6216 "disabled"\uff0c\u4e0d\u63a5\u53d7 "enabled"\u3002\u9020\u6210\u4e0a\u6e38\u53ef\u80fd 400 / \u9759\u9ed8\u5ffd\u7565\u3002

\u4fee\u590d\uff1a\u5728 Anthropic forward \u8def\u5f84\u4e0a\u3001StripEmptyTextBlocks \u4e4b\u540e\u3001\u91cd\u8bd5\u5faa\u73af\u4e4b\u524d\uff0c
\u52a0\u4e00\u4e2a NormalizeChineseLLMThinking \u9884\u6539\u5199\u6b65\u9aa4\uff1a
- \u4ec5\u5bf9 ResolveThinkingProtocol == PassbackRequired \u4e0a\u6e38\u751f\u6548\uff08claude-* \u4e0d\u4f1a\u8fdb\u6765\uff09
- \u4ec5\u5339\u914d minimax-m* \u524d\u7f00\u7684\u6620\u5c04\u540e\u6a21\u578b ID
- \u4ec5\u5f53 thinking.type == "enabled" \u65f6\u6539\u5199\u4e3a "adaptive"
- Kimi/GLM/DeepSeek \u7684 enabled \u4e0d\u53d7\u5f71\u54cd\uff08\u8fd9\u4e9b\u4e0a\u6e38\u63a5\u53d7 enabled\uff09

\u8bbe\u8ba1\u539f\u5219\uff1a
1. "\u767d\u540d\u5355\u8bed\u4e49"\uff1a\u53ea\u5bf9\u5df2\u77e5\u9700\u8981\u5904\u7406\u7684\u4e0a\u6e38\u6539\u5199\uff0c\u4e0d\u505a\u9690\u5f0f\u5e7f\u8c31\u8865\u4e01\u3002
2. "\u4f4e\u4fb5\u5165\u6027"\uff1aMiniMax \u4e4b\u5916\u7684\u6a21\u578b\u96f6\u6539\u52a8\uff0c\u73b0\u6709 4 \u4e2a\u4e0a\u6e38\u7684\u884c\u4e3a\u4e0d\u53d8\u3002
3. "\u53ef\u8bca\u65ad"\uff1a\u6539\u5199\u65f6 LegacyPrintf \u8f93\u51fa\u4e00\u884c\u65e5\u5fd7\uff0c\u4fbf\u4e8e\u8c03\u8bd5\u3002

\u6d4b\u8bd5\uff1a9 \u4e2a\u65b0\u7528\u4f8b\u8986\u76d6 MiniMax M3/M2.7\u3001adaptive/disabled \u4e0d\u53d8\u3001\u65e0 thinking
\u5b57\u6bb5\u3001Kimi/GLM/DeepSeek/Claude \u4e0d\u53d7\u5f71\u54cd\u3001invalid JSON fail-safe\u3002

\u4f9d\u8d56\uff1a\u672c\u5206\u652f\u57fa\u4e8e fix/thinking-block-protocol-aware-filter\uff0c\u7528\u5176
ResolveThinkingProtocol \u4f5c\u4e3a passback-required \u5224\u522b\u6e90\u3002\u4e0a\u6e38 PR \u987a\u5e8f\u9700\u5148\u5408
fix/thinking-block-protocol-aware-filter\uff08PR #2136\uff09\u518d\u5408\u672c PR\u3002
2026-06-16 19:37:31 +08:00
alfadb efbf6d2092 fix(test): update FilterThinkingBlocksForRetry call to use mappedModel param
The function signature was changed to require a mappedModel parameter
for protocol-aware thinking-block filtering, but this test call site
was not updated. Without the fix, the unit test build fails on CI.
2026-06-16 19:37:31 +08:00
alfadbandClaude Opus 4.7 6baf00d784 fix(gateway): protocol-aware thinking-block filtering for Anthropic-compatible upstreams
The gateway's thinking-block handling was designed for Anthropic's strict
semantics (drop blocks with missing/invalid signature), but third-party
Anthropic-compatible upstreams have INVERTED semantics:

  * DeepSeek `/anthropic`, Kimi `/coding`, GLM, Moonshot, qwen-*-thinking
    require ALL historical thinking blocks to round-trip verbatim.
  * Stripping any of them produces:
    400 "The content[].thinking in the thinking mode must be passed back
    to the API"

Without this fix, every multi-turn request from a thinking-capable client
(Claude Code, pi, etc.) to such upstreams loses its thinking blocks and
fails. This becomes especially painful when an account's model_mapping
maps `claude-sonnet-4-6 → deepseek-v4-pro` — `reqModel` looks Anthropic
but the upstream contract is the opposite.

Approach
--------

Branch all thinking-block transforms by the *mapped* model id (after
account model_mapping is applied), classifying into three families:

  * `anthropic-strict`     claude-/opus-/sonnet-/haiku-      → existing behaviour
  * `passback-required`    deepseek-/kimi-/moonshot-/glm-/   → preserve verbatim
                           qwen-*-thinking
  * `unknown`              other models                       → conservative
                                                               (preserve, no retry)

Affected entry points (all guarded):

  * Pre-filter on outbound:   `FilterThinkingBlocks`
    Previously dropped blocks with missing/invalid signature; now skips
    entirely for non-strict families. Pre-filter is needed because the
    post-error retry path can run out of budget on long conversations
    (maxRetryElapsed = 10 s).
  * 400 retry rectifier:      `FilterThinkingBlocksForRetry`
    Disables top-level thinking and converts thinking → text. Now skips
    for passback-required (those 400s aren't signature errors and any
    transformation breaks the round-trip contract).
  * 400 retry rectifier (tools): `FilterSignatureSensitiveBlocksForRetry`
    Same family-aware short-circuit.
  * 400 detector:             `shouldRectifySignatureError`
    Returns false for passback-required, so the retry path doesn't even
    fire.

Tests
-----

  * `thinking_protocol_test.go` — classifier across all known vendor
    prefixes plus edge cases (empty, case, qwen non-thinking).
  * `thinking_protocol_filter_integration_test.go` — locks in that the
    three filter entry points return the body byte-for-byte unchanged
    when the model id is passback-required or unknown, and still strip
    invalid blocks for anthropic-strict.

This PR supersedes #1350 (which only added the pre-filter without the
upstream-family awareness, and would have made third-party upstreams
worse). Once merged, please close #1350.

Reference issues:
  - NousResearch/hermes-agent#16748 — DeepSeek /anthropic strip behaviour
  - NousResearch/hermes-agent#15700 — DeepSeek thinking:disabled requirement

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 19:37:31 +08:00
alfadb 34b1e56e29 test: add 'max' → 'xhigh' test cases for reasoning effort normalization
Cover the DeepSeek reasoning_effort 'max' alias in:
- TestExtractOpenAIReasoningEffortFromBody
- TestExtractCCReasoningEffortFromBody
- TestExtractResponsesReasoningEffortFromBody
2026-06-16 19:37:31 +08:00
alfadb 142d8c3618 fix(gateway): normalize DeepSeek reasoning_effort 'max' to 'xhigh'
pi maps defaultThinkingLevel 'xhigh' → 'max' for deepseek-v4-pro via
thinkingLevelMap, but normalizeOpenAIReasoningEffort did not recognize
'max', causing all reasoning_effort to be dropped (0% fill rate since
traffic switched from claude to deepseek on May 1).

Add 'max' → 'xhigh' mapping to match Claude's NormalizeClaudeOutputEffort
behavior.
2026-06-16 19:37:31 +08:00
alfadb 262fe1230d feat(billing): 为 doubao-embedding-vision 添加图文差别兜底定价
火山方舟 doubao-embedding-vision 多模态向量化按量付费官方价为文本 ¥0.7/MTok、图片 ¥1.8/MTok,二者不同价。原计费引擎对 embedding 仅记单一 input token、无图片输入档位,无法表达该差别。

变更:
- ModelPricing 新增 ImageInputPricePerToken 字段
- OpenAIUsage / UsageTokens 新增 ImageInputTokens 字段
- extractOpenAIEmbeddingsUsage 解析 usage.prompt_tokens_details.image_tokens
- CalculateCost 拆分文本/图片输入计费;ImageInputTokens 为 0 时走原单价路径,存量 chat/vision 流量行为不变
- getFallbackPricing 新增 doubao-embedding-vision 分支(most-specific-first,覆盖带版本后缀别名),fallback 表填入 $0.098/$0.252 per MTok(汇率 ÷7.14)

测试:新增图文混合/纯文本/图片 token 超额三类用例及定价回退断言;service 包单测与 golangci-lint 全通过。
2026-06-16 19:37:02 +08:00
alfadb 4f5f2788e1 fix(billing): add kimi-for-coding fallback pricing 2026-06-16 19:37:02 +08:00
alfadb c90089c814 fix(billing): address Copilot review feedback
应 PR #3243 review 修正三项问题:

1. 删除 DeepSeek V4 fallback 定价的重复块(cherry-pick 时
   的旧位置 + 新国产 LLM 分段都写了一遍,后写覆盖前写,
   当前值相同所以无 bug,但冗余,未来改一处易漏另一处)。
2. 删除 minimax-m3 匹配中的死代码——modelLower 已经
   strings.ToLower(),'MiniMax-M3' 字面量永远不可能匹中。
3. 测试结构改 *float64 替代 0 sentinel,让 free-tier
   模型(GLM-4.5-Flash / GLM-4.7-Flash)的 0 价能被
   真正断言而非默默跳过;定义内联 floatPtr 辅助函数。
2026-06-16 19:37:02 +08:00
alfadb a4ce73391b feat(billing): add GLM / Kimi / MiniMax fallback pricing for Chinese LLM providers
将 fix/deepseek-fallback-pricing 的 DeepSeek V4 Pro/Flash 定价收编,
并扩展国产 LLM 兜底定价覆盖:

- 智谱 GLM (z.ai 公开 SKU 13 个): glm-5.1 / glm-5 / glm-5-turbo / glm-4.7 /
  glm-4.7-flashx / glm-4.6 / glm-4.5 / glm-4.5-x / glm-4.5-air / glm-4.5-airx /
  glm-4-32b-0414-128k / glm-4.5-flash / glm-4.7-flash
- 月之暗面 Kimi K 系列 4 个: kimi-k2.6 / kimi-k2.5 / kimi-k2-thinking /
  kimi-k2 (K2-0905/K2-0711 官方未保留定价,隐性回退到 kimi-k2)
- MiniMax M 系列 6 个: minimax-m3 (≤512K) / minimax-m2.7 / minimax-m2.7-highspeed /
  minimax-m2.5 / minimax-m2.1 / minimax-m2
- DeepSeek V4 沿用原 fix/deepseek-fallback-pricing 的 entry 移植过来

所有定价数据来自各家官方定价页 (USD/MTok 口径),与现有
Claude/GPT 风格保持一致 (硬编码在 Go 代码里,admin channel 优先)。

匹配策略:长 key 优先 (glm-5.1 优先于 glm-5, k2.6 优先于 k2);
未列出的国产厂商 alias (qwen/doubao/hunyuan) 一律不返回兜底价,
避免误计价——这与 fix/deepseek-fallback-pricing 的白名单语义一致。

数据源注释 (Source URL) 内嵌在每个 initFallbackPricing 分组开头,
便于后续 PR 同步上游价格变化。
2026-06-16 19:37:02 +08:00
alfadb f597d98bdf test(openai): use unpriced model in usage test 2026-06-16 19:37:02 +08:00
alfadb 5a593a511e test(billing): tighten DeepSeek V4 fallback assertions; clarify branch comments
Address copilot-pull-request-reviewer feedback on #2157:

- billing_service_test.go: extend TestGetFallbackPricing_FamilyMatching
  with optional expectedOutput / expectedCacheRead fields and assert
  full Input/Output/CacheRead pricing for all 4 DeepSeek cases
  (v4-pro, v4-flash, deepseek-chat→flash, deepseek-reasoner→flash),
  preventing silent regression to 0 output/cache cost.
- billing_service.go: rewrite the DeepSeek block comment to explicitly
  describe its scope (V4 Pro/Flash + chat/reasoner aliases, no
  unknown-deepseek fallback) and tighten the OpenAI comment to make
  it unambiguous that it only describes the OpenAI/Codex branch
  immediately below it.
2026-06-16 19:37:02 +08:00
alfadb 27e26a3a90 chore: fix gofmt alignment 2026-06-16 19:37:02 +08:00
alfadb c906bf000e feat(billing): add DeepSeek V4 Pro / Flash fallback pricing
Pricing source: https://api-docs.deepseek.com/quick_start/pricing

deepseek-v4-pro:  input $0.435/M, output $0.87/M, cache_read $0.003625/M
deepseek-v4-flash: input $0.14/M,  output $0.28/M, cache_read $0.0028/M

Also map legacy aliases deepseek-chat / deepseek-reasoner → v4-flash.
2026-06-16 19:37:02 +08:00
a0yarkandCursor 6c2db4f461 fix(gemini): clean unsupported tool schema fields
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 18:05:47 +08:00
shaw b8a482e127 fix(ci): unblock main after recent merges
Three independent CI blockers landed on main from concurrent PR merges:

- openai_quota_service.go (introduced by b8169492): const block spacing
  not gofmt-compliant + trailing blank line. golangci-lint v2.9 flagged it
  on every push after the merge.
- openai_images_failover_test.go (introduced by PR #3155, da30c599):
  NewOpenAIGatewayHandler call missing the opsService argument added by
  PR #3230 (b62b573f). Test was authored before #3230 and merged without
  rebase, causing "not enough arguments" compile error.
- account_quota_reset_test.go: TestIsFixedDailyPeriodExpired_NotExpired
  and TestIsFixedWeeklyPeriodExpired_NotExpired used time.Now()-1min as
  periodStart, which crosses the 09:00 UTC reset boundary when CI runs in
  the 09:00:00-09:00:59 window. Anchoring periodStart to today's 12:00
  UTC removes the race.
2026-06-16 17:59:06 +08:00
Wesley Liddick 44f5791008 Merge pull request #3154 from wucm667/fix/responses-session-hash-input-field
fix(openai): Responses API session hash fallback 纳入 input 内容,避免 sticky 绑死账号
2026-06-16 17:01:16 +08:00
Wesley Liddick 9c2c8ab3e5 Merge pull request #3155 from wucm667/fix/openai-images-server-error-failover
fix(openai): 图像接口上游 server_error 触发 failover 换号,不再 5xx 透传
2026-06-16 17:00:40 +08:00
Bestony@Homelab 56c62c59c8 fix(auth): include client ip in acl denial message 2026-06-16 17:00:35 +08:00
Wesley Liddick e4ccb75d0f Merge pull request #3220 from wucm667/fix/oauth-signup-apply-promo-code
fix(auth): OAuth 注册支持应用 URL 上的 promo_code 优惠码
2026-06-16 16:59:38 +08:00
Wesley Liddick 2ce8788929 Merge pull request #3299 from alfadb/fix/openai-responses-probe-tool-capability
fix(openai-probe): /responses 能力探测增加工具调用校验
2026-06-16 16:58:48 +08:00
Wesley Liddick 2e0ff1cfd5 Merge pull request #3258 from bwliangc/feat/channel-monitor-jitter
feat(渠道监控): 检测间隔支持正负随机抖动配置
2026-06-16 16:58:23 +08:00
Wesley Liddick 16765bde69 Merge pull request #3230 from DaydreamCoding/feat/openai-cyber-policy-passthrough
feat(openai): cyber_policy 硬阻断全链路透传、审计与计费
2026-06-16 16:55:51 +08:00
shaw b816949291 feat(openai-quota): query + reset rate-limit credits for OpenAI accounts
Adds an admin-side action that mirrors the Codex Desktop "rate-limit reset"
flow against chatgpt.com upstream for OpenAI OAuth accounts.

Backend
- OpenAIQuotaService.QueryUsage / ResetCredit hit /wham/usage and
  /wham/rate-limit-reset-credits/consume with the Codex Desktop header set,
  reusing OpenAITokenProvider for refreshed tokens and PrivacyClientFactory
  for the impersonated Chrome TLS fingerprint.
- Honors the account's configured proxy by reading the eager-loaded
  account.Proxy directly (falls back to proxyRepo only when missing).
- GET /api/v1/admin/openai/accounts/:id/quota
  POST /api/v1/admin/openai/accounts/:id/reset-quota
- Wire DI for the new service + handler dependency.

Frontend
- OpenAIQuotaResetCell renders a single action row in AccountUsageCell's
  OpenAI section: the existing local "查询" (active sampling) is injected
  via #pre-actions, alongside a "次数 N" button that doubles as the
  upstream query trigger and the available-credit indicator, and a "重置"
  button that consumes one credit.
- No duplicate 5h/7d window display; the local UsageProgressBar owns those
  bars to avoid confusion.
2026-06-16 16:55:07 +08:00
alfadb b88f8e4c04 fix(openai-probe): /responses 能力探测增加工具调用校验
原探测仅以 HTTP 状态码判定 /v1/responses 端点是否存在(404/405 视为不存在,其余视为存在),无法识别"端点存在、基础补全可用、但工具调用不可用"的上游。火山方舟 coding/v3 的 kimi-k2.6 即属此类:携带 tools 的请求仅返回 reasoning、不产出 function_call,导致账号被误判 openai_responses_supported=true。网关据此走 /responses 转换路径后工具调用结果丢失,带工具的请求确定性失败。

改动:
- 探测请求携带一个工具并以 tool_choice=required 强制调用,仅当响应 output 数组包含 function_call 项时判定为支持;2xx 但无 function_call 判定为不支持,使网关改走 /v1/chat/completions 直转路径(同一模型在该路径下工具调用正常)。
- 探测模型改用账号 model_mapping 中的真实上游模型;占位模型在第三方上游会返回 400 model-not-found,无法判定能力。
- 探测超时 8s 调整为 15s(探测在后台异步执行,为推理型模型先推理再产出工具调用预留时间)。
- 非 2xx(404/405 除外)仍保守判定为支持,不改变既有账号行为;运行时回退函数 isResponsesEndpointSupportedByStatus 保持不变。
- 新增单元测试覆盖判定逻辑与探测模型选择。
2026-06-16 15:59:00 +08:00
shaw f069c9ae00 fix(outbox-dedup): buildSchedulerGroupPayload typed-nil broke dedup_key consistency
#3255 introduced payload-aware dedup_key (sha256 over event_type, account_id,
group_id, payload_json). enqueueSchedulerOutbox checks "if payload != nil"
before json.Marshal — but Go interface containing a typed-nil map (returned
by buildSchedulerGroupPayload(empty)) is NOT == nil at the interface level.

So an ungrouped account's account_changed event went through this path:
  payload := buildSchedulerGroupPayload(account.GroupIDs)  // typed-nil map
  enqueueSchedulerOutbox(..., payload)                     // interface != nil
    → json.Marshal(typedNilMap) = "null"
    → dedup_key hash = sha256(... + "null")

While other call sites pass literal nil:
  enqueueSchedulerOutbox(..., nil)                         // interface == nil
    → payloadJSON stays empty
    → dedup_key hash = sha256(... + "")

The two dedup_keys differ for what should be the same logical event,
silently degrading dedup effectiveness in bursts on ungrouped accounts.

Fix: change buildSchedulerGroupPayload return type from map[string]any to
any so empty input returns true untyped-nil. All call sites pass the
result straight to enqueueSchedulerOutbox(payload any) — no inspection,
no breakage.

Adds regression test TestEnqueueSchedulerOutbox_UngroupedAccountDedupesWithLiteralNilPayload
asserting (1) typed-nil regression doesn't sneak back, (2) dedup_key for
empty-groups payload matches the literal-nil-payload key.
2026-06-16 14:21:09 +08:00
shaw acaffe29ec fix(account-repo): refresh candidates SQL excluded healthy accounts; fix CI build
Post-merge audit of #3272 found two regressions:

1. ListOAuthRefreshCandidates used "AND NOT (a AND b)" which, under PG
   3-valued logic, evaluates to NULL when both temp_unschedulable_until
   and temp_unschedulable_reason are NULL — i.e., the common healthy
   account state. Such rows were silently excluded from the background
   token refresh worker, so their OAuth access tokens would never get
   refreshed and eventually start returning 401.

   Verified empirically against PostgreSQL: only 3 of 5 test rows
   matched before the fix; after switching to "(a AND b) IS NOT TRUE"
   the expected 4 rows match.

2. The new ListOAuthRefreshCandidates method on AccountRepository was
   not implemented on stubAccountRepo in api_contract_test.go (build
   tag "unit"), breaking "make test-unit" which CI runs in
   .github/workflows/backend-ci.yml.

Tests:
- Added IS NOT TRUE and "AND NOT (" assertions to the SQL-shape unit
  test so the predicate can't regress to the broken form again.
- "go test -tags=unit ./internal/..." now passes cleanly.
2026-06-16 14:08:50 +08:00
shaw 2ba52bf4aa Merge pull request #3274 from jianjianai/fix/scheduler-outbox-cleanup
fix: cleanup consumed scheduler outbox rows / 添加 scheduler_outbox 表的清理代码,避免表过大

Additional hardening: WHERE clause adds a 10s grace via
created_at < NOW() - INTERVAL '10 seconds' to defend against the PG
sequence-id vs commit race (id assigned in tx, commit delayed past
watermark advance). Without it, slow committers could lose their
outbox row before the snapshot poller reads it.
2026-06-16 11:57:43 +08:00
shaw 31dc8913ac chore(outbox-cleanup): add 10s grace to defend against id-vs-commit race
PG sequences advance outside transactions, so a slow committer can hold
an id that gets surpassed by the watermark before its commit becomes
visible. Without the grace period, cleanup would delete such rows before
the snapshot poller ever sees them. 10s is a comfortable upper bound
on realistic enqueueSchedulerOutbox commit latency.
2026-06-16 11:57:32 +08:00
jjaw cb14935e9a fix: cleanup consumed scheduler outbox rows 2026-06-16 11:56:40 +08:00
shaw 4254dcb352 Merge pull request #3255 from jianjianai/fix1/outbox-scheduler-snapshot-coalesce
降低高频账号状态变更下的 outbox 写入压力 / safely coalesce scheduler outbox events

Migrations renumbered 151/152 -> 152/153 to avoid collision with #3232.
2026-06-16 11:50:35 +08:00
shaw 1fdbe52f96 chore(migrations): renumber scheduler outbox dedup migrations 151/152 -> 152/153
#3232 (already merged) occupies 151. Renumber #3255's dedup migrations to
avoid collision and keep linear ordering. Updated runner constant + tests.
2026-06-16 11:50:25 +08:00
jjaw b3ec6288ad fix: release scheduler outbox dedup on claim 2026-06-16 11:48:13 +08:00
jjaw 60cf89ae26 fix: recover scheduler outbox invalid dedup index 2026-06-16 11:48:13 +08:00
jjaw 3ef70b045d fix: safely coalesce scheduler outbox events 2026-06-16 11:48:12 +08:00
jjaw 34e66ec0a5 fix: outbox scheduler snapshot coalesce 2026-06-16 11:48:12 +08:00
shaw 45f3b0dd74 Merge pull request #3272 from jianjianai/fix/token-refresh-retry-backoff
fix: token refresh 筛选避免拉取全部账号导致数据库爆炸 / reduce token refresh retry amplification
2026-06-16 11:47:07 +08:00
jjaw 9b270f11d7 refactor: inline token refresh retry reason prefix 2026-06-16 11:44:39 +08:00
jjaw 74199b6a6e fix: reduce token refresh retry amplification 2026-06-16 11:44:39 +08:00
shaw b56c501d30 Merge pull request #3273 from jianjianai/fix/redis-wait-queue-hotpath
fix: move user wait queue accounting off hot path / 用户槽位立即成功的请求不调用等待队列
2026-06-16 11:43:07 +08:00
jjaw b0579c4891 fix: move user wait queue accounting off hot path 2026-06-16 11:41:54 +08:00
shaw 87d0f8da66 Merge pull request #3132 from jianjianai/main
修复 token refresh 拉取大量账号时触发 PostgreSQL 参数上限
2026-06-16 11:40:47 +08:00
jjaw 8b698ff4c1 fix account list parameter limit 2026-06-16 11:39:15 +08:00
Wesley Liddick 62fef6ebed Merge pull request #3178 from jianjianai/fix/fix-failover-error-body-reuse
fix: 修复 OpenAI failover 错误响应体重复读取 / reuse cached OpenAI failover error body
2026-06-16 11:13:10 +08:00
Wesley Liddick 8ddc138094 Merge pull request #3232 from jianjianai/fix1/account-expiry-autopause-index
fix:account expiry autopause index / 账号过期自动禁用索引优化
2026-06-16 11:12:57 +08:00
Wesley Liddick 2f1f1971b4 Merge pull request #3195 from jianjianai/fix/invalid-refresh-token-nonretryable
fix: treat invalid_refresh_token as non-retryable / 将 invalid_refresh_token 判定为不可重试
2026-06-16 11:12:45 +08:00
Wesley Liddick 9acd104151 Merge pull request #3201 from alfadb/fix/dockerfile-copy-docs-legal
fix(docker): 将 docs/legal 纳入构建上下文以支持 admin-compliance gate
2026-06-16 11:12:34 +08:00
Wesley Liddick 15b0753c1d Merge pull request #3283 from wucm667/fix/non-json-200-response-failover
fix(gateway): 上游 200 返回非 JSON 错误报文时纳入 failover,避免坏上游被反复命中误扣费
2026-06-16 11:12:21 +08:00
Wesley Liddick a0fc2c4143 Merge pull request #3218 from wucm667/fix/antigravity-system-role-message
fix(antigravity): 处理 messages 中 role:system 消息,修复 Claude Code 接入 400
2026-06-16 11:12:08 +08:00
Wesley Liddick 28bed8b866 Merge pull request #3252 from wucm667/fix/upstream-zstd-response-decompression
fix(gateway): 解压 zstd 上游响应体,修复非流式 usage 静默记 0 的计费丢失
2026-06-16 11:11:51 +08:00
shaw bbd9702496 fix(frontend): bump form-data to >=4.0.6 via pnpm override
Resolves GHSA-hmw2-7cc7-3qxx (CRLF injection) flagged by
frontend-security CI. axios pulls form-data ^4.0.5 which locked to the
vulnerable 4.0.5; override forces all transitive consumers to 4.0.6+
without needing an audit exception.
2026-06-16 10:48:12 +08:00
Wesley Liddick 715594847b Merge pull request #3267 from codeQuest-fly/codex/fix-sub2api-injection
feat: configure Claude OAuth system prompt blocks
2026-06-16 10:43:38 +08:00
Wesley Liddick 29978ac9a6 Merge pull request #3251 from wucm667/fix/chatcompletions-responses-tool-strict-default
fix(apicompat): chat-completions 转 Responses 显式补全 tool strict=false,避免可选参数被填零值
2026-06-16 10:26:39 +08:00
dailingfei b63b411654 fix: remove unused billing attribution helper 2026-06-15 17:16:26 +08:00
wucm667 ab9987b2e2 fix(gateway): fail over on non-JSON 2xx responses 2026-06-15 11:04:24 +08:00
315944211 25a9762a17 feat: show account id in account list 2026-06-15 00:02:04 +08:00
dailingfei 8ce7b9a8f6 feat: configure Claude OAuth system prompt blocks 2026-06-13 04:12:13 +08:00
bwlcandClaude Fable 5 c70c6a2659 feat(渠道监控): 检测间隔支持正负随机抖动配置
新增 jitter_seconds 配置:每轮调度在 interval 基础上 ± [0, jitter]
均匀随机偏移触发,避免多个监控以固定节奏同步请求上游。

- ent schema 新增 jitter_seconds 字段(默认 0),附迁移 151
- 校验:jitter >= 0 且 interval - jitter >= 15s(创建/更新均校验)
- runner 由固定 ticker 改为每轮重新随机化的 timer,0 抖动时行为不变
- 前端监控表单新增「随机抖动 (± 秒)」输入框,上限随间隔联动

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:09:53 +08:00
wucm667 c1c28ac7bb fix(gateway): 解压 zstd 上游响应体 2026-06-12 15:06:01 +08:00
wucm667 edfd5e3736 fix(apicompat): default tool strict to false 2026-06-12 15:01:58 +08:00
wangziwenunique f6e0ebc6bf fix: preserve Anthropic window cooldowns 2026-06-12 13:35:03 +08:00
DaydreamCodingandClaude Opus 4.8 b62b573f7f feat(openai): cyber_policy 硬阻断全链路透传、审计与计费
上游对单次请求下发 error.code=cyber_policy 硬阻断时,网关在所有端点
(/v1/responses、/v1/chat/completions、/v1/messages、WebSocket)及流式/
非流式路径下,将该结果原样透传给客户端,绝不 failover、换号或同步拦截;
命中后异步完成审计与计费:

- 风控中心记录 cyber_policy 留痕并发送通知邮件,落库先于发信,SMTP 阻塞
  不影响留痕
- ops 错误请求记录,状态码对齐客户端实际接收(流式 200 / 非流式 400)
- 用量明细标记 request_type=cyber,按上游真实 token 计费,HTTP 与
  WebSocket 计费口径统一,零 token 命中不误扣
- 会话级自动屏蔽(管理员开关,默认关):命中的会话在可配 TTL 内本地拦截
  不再发往上游,仅屏蔽该会话不影响同 Key 其他会话
- 封号计数排除开关:可选让 cyber 命中不计入自动封号,命中当次不判定且
  历史行在违规计数中一并排除

WebSocket 多轮连接下 cyber 标记按 turn 生命周期管理,逐轮独立检测与记录;
透传的错误响应不被兜底逻辑追加内容污染。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:47:01 +08:00
jjaw e4c255a77a fix:account expiry autopause index 2026-06-12 01:38:09 +08:00
feitianbubu b256f91141 fix(gateway): intercept max_tokens=1 haiku probes for streaming requests too 2026-06-11 20:46:47 +08:00
wucm667 f8c80bf038 fix(auth): apply promo codes to oauth signups 2026-06-11 18:55:27 +08:00
wucm667 65559ac589 fix(antigravity): merge system role messages 2026-06-11 18:37:00 +08:00
方程andClaude Opus 4.7 6bf6722bae chore: gofmt test file
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 17:45:09 +08:00
方程andClaude Opus 4.7 0da1fe28ef fix(openai): prevent false image billing on text-only /v1/responses requests
Two defects in the image output counter could inflate imageCount for
text-only responses, causing per-image charges instead of token billing:

1. addDataArray counted every element in a top-level "data" array
   without checking whether each element contained actual image output
   (url/b64_json).  Non-image objects in a "data" array are now skipped.

2. addImageOutputItem accepted image_generation.completed items with
   no result/b64_json/url, inflating the count.  These items are now
   filtered the same way as every other item type.

Fixes #3196

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 16:56:43 +08:00
jjaw 727ac3f689 fix: add app_session_terminated to non-retryable refresh errors / 将 app_session_terminated 添加到不可重试的刷新错误中 2026-06-10 22:32:04 +08:00
alfadb ad13585456 fix(docker): ship docs/legal in build context for admin-compliance gate
LegalDocumentView.vue (admin-compliance acknowledgement gate) build-time
imports ../../../../docs/legal/*.md?raw. The Docker image build broke
because the frontend-builder stage only COPYs frontend/ (never docs/) and
.dockerignore excludes both docs/ and *.md from the build context.

Upstream CI runs `pnpm build` from the repo root (docs/ resolvable via
../docs/) and never exercises the Docker path, so this stayed hidden until
the buildkit package job surfaced "Could not resolve docs/legal/...md?raw".

Fix:
- COPY docs/legal/ into /app/docs/legal in Dockerfile and deploy/Dockerfile
  so it sits beside /app/frontend (WORKDIR), matching the relative import.
  Only the required subtree is copied to keep the build dependency minimal.
- Re-include docs/legal/*.md in .dockerignore so buildkit ships the subtree.
2026-06-10 16:34:19 +08:00
github-actions[bot] e34ad2b194 chore: sync VERSION to 0.1.136 [skip ci] 2026-06-10 07:02:12 +00:00
jjaw fa8f1749f5 fix: treat invalid_refresh_token as non-retryable / 将 invalid_refresh_token 判定为不可重试 2026-06-10 14:21:44 +08:00
jjaw 46bd7968a3 fix: reuse OpenAI failover error body 2026-06-09 21:56:35 +08:00
wucm667 da30c59923 fix(openai): fail over image server errors 2026-06-09 13:57:45 +08:00
wucm667 a67b10f468 fix(gateway): anchor responses fallback to input 2026-06-09 13:53:19 +08:00
964 changed files with 171845 additions and 59974 deletions
+5
View File
@@ -11,6 +11,11 @@
*.md
!deploy/DOCKER.md
docs/
# admin-compliance gate (LegalDocumentView.vue) build-time imports
# docs/legal/*.md?raw into the frontend bundle; keep that subtree in the
# Docker build context even though docs/ is otherwise excluded.
!docs/legal/
!docs/legal/*.md
# IDE
.idea/
+2 -2
View File
@@ -5,14 +5,14 @@ exceptions:
severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2023-30533)"
mitigation: "Load only on export; restrict export permissions and data scope"
expires_on: "2026-07-06"
expires_on: "2026-10-06"
owner: "security@your-domain"
- package: xlsx
advisory: "GHSA-5pgg-2g8v-p4x9"
severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2024-22363)"
mitigation: "Load only on export; restrict export permissions and data scope"
expires_on: "2026-07-06"
expires_on: "2026-10-06"
owner: "security@your-domain"
- package: lodash
advisory: "GHSA-r5fr-rjxr-66jc"
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
- name: Unit tests
working-directory: backend
run: make test-unit
@@ -33,7 +33,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
version: 9
- name: Setup Node.js
@@ -60,7 +60,7 @@ jobs:
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
+3
View File
@@ -12,6 +12,9 @@ permissions:
pull-requests: write
statuses: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
cla-check:
if: |
+2 -2
View File
@@ -58,7 +58,7 @@ jobs:
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
version: 9
@@ -115,7 +115,7 @@ jobs:
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
# Docker setup for GoReleaser
- name: Set up QEMU
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
- name: Run govulncheck
working-directory: backend
run: |
@@ -35,7 +35,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
version: 9
- name: Set up Node.js
+2
View File
@@ -119,6 +119,8 @@ tests
CLAUDE.md
.claude
scripts
!backend/scripts/
!backend/scripts/resolve-version.sh
.code-review-state
#openspec/
code-reviews/
+15 -5
View File
@@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1.7
# =============================================================================
# Sub2API Multi-Stage Dockerfile
# =============================================================================
@@ -7,16 +8,18 @@
# =============================================================================
ARG NODE_IMAGE=node:24-alpine
ARG GOLANG_IMAGE=golang:1.26.4-alpine
ARG GOLANG_IMAGE=golang:1.26.5-alpine
ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine
ARG GOPROXY=https://goproxy.cn,direct
ARG GOSUMDB=sum.golang.google.cn
ARG NPM_CONFIG_REGISTRY=
# -----------------------------------------------------------------------------
# Stage 1: Frontend Builder
# -----------------------------------------------------------------------------
FROM ${NODE_IMAGE} AS frontend-builder
ARG NPM_CONFIG_REGISTRY
WORKDIR /app/frontend
@@ -25,10 +28,17 @@ RUN corepack enable && corepack prepare pnpm@9 --activate
# Install dependencies first (better caching)
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
RUN --mount=type=cache,id=sub2api-pnpm-store,target=/root/.local/share/pnpm/store \
if [ -n "${NPM_CONFIG_REGISTRY}" ]; then pnpm config set registry "${NPM_CONFIG_REGISTRY}"; fi && \
pnpm install --frozen-lockfile --prefer-offline
# Copy frontend source and build
# Copy frontend source and build.
# LegalDocumentView.vue (admin-compliance gate) build-time imports
# ../../../../docs/legal/*.md?raw, so docs/legal/ must sit beside frontend/
# in the image (WORKDIR /app/frontend -> resolves to /app/docs/legal/*.md).
# Copy only that subtree to keep the build dependency minimal.
COPY frontend/ ./
COPY docs/legal/ /app/docs/legal/
RUN pnpm run build
# -----------------------------------------------------------------------------
@@ -62,9 +72,9 @@ COPY backend/ ./
COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist
# Build the binary (BuildType=release for CI builds, embed frontend)
# Version precedence: build arg VERSION > cmd/server/VERSION
# Version precedence: build arg VERSION > exact git tag > cmd/server/VERSION
RUN VERSION_VALUE="${VERSION}" && \
if [ -z "${VERSION_VALUE}" ]; then VERSION_VALUE="$(tr -d '\r\n' < ./cmd/server/VERSION)"; fi && \
if [ -z "${VERSION_VALUE}" ]; then VERSION_VALUE="$(./scripts/resolve-version.sh)"; fi && \
DATE_VALUE="${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
CGO_ENABLED=0 GOOS=linux go build \
-tags embed \
+161 -55
View File
@@ -12,7 +12,7 @@
**AI API Gateway Platform for Subscription Quota Distribution**
English | [日本語](README_JA.md)
English | [中文](README_CN.md) | [日本語](README_JA.md)
</div>
@@ -23,26 +23,11 @@ Please read the following carefully before using this project:
- **🚨 Terms of Service Risk**: Using this project may violate the terms of service of Anthropic and other upstream providers. Please review the relevant providers' user agreements before use; all risks arising from such use are borne solely by the user.
- **⚖️ Compliant Use**: Use this project only in compliance with the laws and regulations of your country or region. Any unlawful use is strictly prohibited.
- **📖 Disclaimer**: This project is provided for technical learning and research purposes only. The authors assume no liability for account bans, service interruptions, data loss, or any other direct or indirect damages resulting from the use of this project.
## Overview
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
## Features
- **Multi-Account Management** - Support multiple upstream account types (OAuth, API Key)
- **API Key Distribution** - Generate and manage API Keys for users
- **Precise Billing** - Token-level usage tracking and cost calculation
- **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate limits
- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md))
- **Admin Dashboard** - Web interface for monitoring and management
- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard
- **🚫 No Commercial Authorization**: The developers of this project have never authorized any individual or organization to conduct any form of commercial operation based on this project. Any commercial activity conducted in the name of or based on this project is unrelated to this project and its developers, and all resulting disputes, losses, and legal liabilities shall be borne solely by the party conducting such activity.
## ❤️ Sponsors
> [Want to appear here?](mailto:support@pincc.ai)
> [Want to appear here?](mailto:support@sub2api.org)
<table>
@@ -56,21 +41,16 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
<td>Thanks to ETok.ai for sponsoring this project! ETok.ai is dedicated to building a one-stop AI programming tool service platform. We offer professional Claude Code packages and technical community services, with support for Google Gemini and OpenAI Codex. Through carefully designed plans and a professional tech community, we provide developers with reliable service guarantees and continuous technical support, making AI-assisted programming a true productivity tool. Click <a href="https://etok.ai">here</a> to register!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Thanks to APIKEY.FUN for sponsoring this project! <a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> is one of the core contributors to the sub2api open-source project, dedicated to providing open, stable, and cost-effective AI API access. The platform supports API relay services for Claude, OpenAI, Gemini, and other popular models, with pricing starting from as low as 7% of the original rate. Register via the exclusive link: <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> to enjoy a permanent 5% discount on all recharges.</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for sub2api users: if you register via <a href="https://aigocode.com/invite/SUB2API">this link</a>, you'll receive an extra 10% bonus credit on your first top-up!</td>
</tr>
<tr>
<td width="180"><a href="https://code.silkapi.com/register?aff=SUB2API"><img src="assets/partners/logos/silkapi.png" alt="silkapi" width="150"></a></td>
<td>Thanks to SilkAPI for sponsoring this project! <a href="https://code.silkapi.com/register?aff=SUB2API">SilkAPI</a> is a relay service built on Sub2API, specializing in providing high-speed and stable Codex API relay.</td>
</tr>
<tr>
<td width="180"><a href="https://ylscode.com/"><img src="assets/partners/logos/ylscode.png" alt="ylscode" width="150"></a></td>
<td>Thanks to YLS Code for sponsoring this project! <a href="https://ylscode.com/">YLS Code</a> is dedicated to building secure enterprise-grade Coding Agent productivity services, offering stable and fast Codex / Claude / Gemini subscription services along with pay-as-you-go API options for flexible choices. Register now for a limited-time 3-day Codex trial bonus!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>Thanks to AICodeMirror for sponsoring this project! AICodeMirror provides official high-stability relay services for Claude Code / Codex / Gemini CLI, with enterprise-grade concurrency, fast invoicing, and 24/7 dedicated technical support. Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original price, with extra discounts on top-ups! AICodeMirror offers special benefits for sub2api users: register via <a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">this link</a> to enjoy 20% off your first top-up, and enterprise customers can get up to 25% off!</td>
@@ -97,12 +77,6 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=fu2E"><img src="assets/partners/logos/runapi.png" alt="RunAPI" width="150"></a></td>
<td>Thanks to RunAPI for sponsoring this project! <a href="https://runapi.co/register?aff=fu2E">RunAPI</a> is an efficient and stable API platform and OpenRouter alternative. With one API Key, you can access 150+ popular models including OpenAI, Claude, Gemini, DeepSeek, and Grok, with pricing as low as 10% of the original rate. It is highly stable and seamlessly compatible with tools such as Claude Code and OpenClaw.
</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=sub2api"><img src="assets/partners/logos/unity2.png" alt="unity2" width="150"></a></td>
<td>Thanks to Unity2 for sponsoring this project! <a href="https://unity2.ai/register?source=sub2api">Unity2</a> is a high-performance AI model API relay for individuals, teams, and enterprises, handling 30B+ tokens/day with 5000 RPM concurrency. One API Key works across Claude Code, Codex, OpenAI models, IDE plugins, and Agent workflows, with balance billing, bundled subscriptions, enterprise invoicing, and 1-on-1 support. <a href="https://unity2.ai/register?source=sub2api">Register</a> to claim $2 in balance, plus $10 more by joining the official group — up to $12 in free credit.
@@ -121,8 +95,87 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
</td>
</tr>
<tr>
<td width="180"><a href="https://apikl.ai"><img src="assets/partners/logos/apikl.png" alt="apikl" width="150"></a></td>
<td>Thanks to Apikl for sponsoring this project! Built on Sub2API, the platform provides developers with relay services for Codex / Claude series models, focusing on long-term stability, high-speed direct connections, and excellent cost-effectiveness. It offers pay-as-you-go balance billing, enterprise-grade official invoices, and one-on-one dedicated support. <a href="https://apikl.ai">Register now</a> for a 1:1 top-up bonus — double your balance!
</td>
</tr>
<tr>
<td width="180"><a href="https://tokeneum.ai"><img src="assets/partners/logos/tokeneum.png" alt="tokeneum" width="150"></a></td>
<td>Thanks to TokenEum for sponsoring this project! <a href="https://tokeneum.ai">TokenEum</a> is a comprehensive AI model aggregation platform and intelligent agent development company. It brings together top-tier international models — including Claude, Gemini, and OpenAI — alongside leading open-source models such as GLM, Qwen, and Kimi, offering a wide range of options across different quality and price tiers to suit every need. TokenEum also provides access to cutting-edge video generation models like Seedance2.0 and Happy Horse. Committed to transparency and honest business practices, TokenEum ensures all model information is accurate and reliable. Visit <a href="https://tokeneum.ai">tokeneum.ai</a> to get started.
</td>
</tr>
<tr>
<td width="180"><a href="https://666api.work/sub2api"><img src="assets/partners/logos/666api.jpg" alt="666api" width="150"></a></td>
<td>Thanks to 666api for sponsoring this project! <a href="https://666api.work/sub2api">666api</a> is an all-in-one platform offering:<br>
⚡ API Relay — Pay-as-you-go access to global models sourced 100% from official providers, up to 75% off official pricing<br>
&nbsp;&nbsp;&nbsp;&nbsp;Exclusive: Zhipu GLM 50% off · DeepSeek V4-pro 50% off · Seedance 2.0 8% off (whitelisted) · HappyHorse Overseas 30% off (whitelisted)<br>
🔑 GPT Subscription Accounts (same-origin IP included) · Global Residential IP <br>
💰 Invoices supported
</td>
</tr>
<tr>
<td width="180"><a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html"><img src="assets/partners/logos/byteplus.png" alt="BytePlus" width="150"></a></td>
<td>Thanks to Dola seed for sponsoring this project! Dola Seed 2.0 is a full‑modal general large model independently developed by ByteDance for the global market. Built on a unified multimodal architecture, it supports joint understanding and generation of text, images, audio, and video. It natively enables agent collaboration, with strong reasoning, long‑task execution, tool integration, and coding capabilities. It is widely applicable to smart cockpits, personal assistants, education, customer support, marketing, retail, and other scenarios. It excels in multimodal perception, end‑to‑end complex task delivery, stable interaction, and data security, and is readily accessible and deployable via the ModelArk platform.Register via <a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html">this link</a> to get 500,000 tokens of free inference quota per model.<a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html"> >>中国大陆地区的开发者请点击这里</a></td>
</tr>
<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>Thanks to Suixiang AI Gateway for sponsoring this project! <a href="https://sui-xiang.com/">Suixiang AI Gateway</a> is a reliable and efficient API relay service provider offering relay services for Claude, Codex, Gemini, and more. A privacy-focused relay — no data reselling, no model dilution; privacy, transparency, and lightning-fast after-sales support. New accounts get ¥0.5 in trial credit daily by signing in; top-ups are 1:1, no subscription required, pay-as-you-go. Multi-line redundancy, cross-region disaster recovery, automatic failover, and uninterrupted long-link SSE. 99.9% availability — critical calls never fall behind.
</td>
</tr>
<tr>
<td width="180"><a href="https://www.miyaip.com/?invitecode=sub2api"><img src="assets/partners/logos/miyaip.png" alt="miyaip" width="150"></a></td>
<td>Thanks to MiyaIP for sponsoring this project! <a href="https://www.miyaip.com/?invitecode=sub2api">MiyaIP</a> is a platform dedicated to global residential proxy network services, committed to providing high-quality, pure overseas residential IP resources for enterprise developers, cross-border business teams, and AI application users. It delivers stable, independent overseas network environments for AI platforms, overseas SaaS, and other online services, supporting multi-region access testing and project environment isolation. Ideal for development and testing scenarios that require access to overseas AI services, such as: AI model platform access, AI development testing, AI SaaS service usage, AI API debugging, and multi-region network environment validation.
</td>
</tr>
<tr>
<td width="180"><a href="https://anpin.ai"><img src="assets/partners/logos/anpin.jpg" alt="anpin" width="150"></a></td>
<td>Thanks to <a href="https://anpin.ai">anpin.ai</a> for sponsoring this project! anpin.ai is a premium AI relay service platform dedicated to advancing AI accessibility. With an advanced technical architecture and globally distributed deployment, it provides users with a direct high-speed channel to the world's top-tier large language models.<br>
Self-built primary account pool: 1-3s ultra-fast response, supports channel-partner distribution<br>
Extreme stability: multi-line intelligent routing + redundant backup system, ensuring year-round high-availability operation;<br>
Model authenticity: no content intervention or secondary filtering — experience the purest, most powerful native model capabilities.<br>
1:1 top-up, enterprise-grade service with invoicing available. Anpin AI is not just a relay — it's your secure, reliable, and efficient bridge to the frontier world of intelligence.
</td>
</tr>
<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>Thanks to Proxy4Free for sponsoring this project! Proxy4Free is a data proxy service provider for developers and AI applications, offering residential proxies, static residential proxies, ISP proxies, and datacenter proxies for scenarios such as Web Scraping, Browser Automation, and AI Agents. With global IP resources, stable connections, and flexible switching, it helps developers improve data collection success rates and reduce the risk of IP bans. Register via <a href="https://www.proxy4free.com/?keyword=4yjqecpc">this link</a> to get started and easily build more stable and efficient automation workflows.
</td>
</tr>
<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 Thanks to FastAIToken for sponsoring this project! <a href="http://www.fastaitoken.com/register">FastAIToken</a> is an AI API aggregation platform for developers, supporting mainstream large models such as OpenAI, Claude, and Gemini. Top-up at 1:1 — 1 CNY = 1 USD of API credit — letting developers use the world's leading large model services at lower cost and with greater convenience.<br>
🚀 The platform offers a variety of channels to choose from: an ultra-low-price 0.02x OpenAI promotional group (limited time), groups as low as 0.25x OpenAI, 0.7x Claude with 95% fixed cache, and a 1.2x Claude Max channel. It also provides a public status page showing real-time availability, latency, and operating status of each group for transparent and reliable service, plus 7×24 human technical support (not bots) with fast responses to developer needs.
</td>
</tr>
</table>
## Overview
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
## Features
- **Multi-Account Management** - Support multiple upstream account types (OAuth, API Key)
- **API Key Distribution** - Generate and manage API Keys for users
- **Precise Billing** - Token-level usage tracking and cost calculation
- **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate limits
- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md))
- **Admin Dashboard** - Web interface for monitoring and management
- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard
## Ecosystem
Community projects that extend or integrate with Sub2API:
@@ -419,7 +472,8 @@ pnpm run build
# 4. Build backend with embedded frontend
cd ../backend
go build -tags embed -o sub2api ./cmd/server
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server
# 5. Create configuration file
cp ../deploy/config.example.yaml ./config.yaml
@@ -482,20 +536,20 @@ Additional security-related options are available in `config.yaml`:
**⚠️ Security Warning: HTTP URL Configuration**
When `security.url_allowlist.enabled=false`, the system performs minimal URL validation by default, **rejecting HTTP URLs** and only allowing HTTPS. To allow HTTP URLs (e.g., for development or internal testing), you must explicitly set:
When `security.url_allowlist.enabled=false`, the system performs minimal URL validation and **allows HTTP URLs by default** (dev-friendly mode; Docker Compose deployments use the same default). For production, explicitly tighten this to HTTPS-only:
```yaml
security:
url_allowlist:
enabled: false # Disable allowlist checks
allow_insecure_http: true # Allow HTTP URLs (⚠️ INSECURE)
allow_insecure_http: false # HTTPS only (recommended for production)
```
**Or via environment variable:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**Risks of allowing HTTP:**
@@ -509,7 +563,7 @@ SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
- ✅ Testing account connectivity before obtaining HTTPS
- ❌ Production environments (use HTTPS only)
**Example error without this setting:**
**Example error for HTTP URLs when `allow_insecure_http: false` is set:**
```
Invalid base URL: invalid url scheme: http
```
@@ -520,6 +574,25 @@ If you disable URL validation or response header filtering, harden your network
- Enforce TLS-only outbound traffic
- Strip sensitive upstream response headers at the proxy
#### ⚠️ Important: Creating the Admin Account
The initial admin account is **only created via the setup wizard** (served at `http://<host>:8080` on first run). The `default.admin_email` / `default.admin_password` fields in `config.yaml` are **not used** to create it — they exist in the template for historical reasons.
Because step 5 above pre-creates `config.yaml`, the setup wizard will be **skipped on first run**: the server detects an existing config and boots straight into normal mode with an empty `users` table, so the first login attempt fails with `invalid email or password`.
**Two ways to create the admin account:**
1. **Recommended — let the wizard generate `config.yaml`:** Skip step 5 (do not run the `cp`). Start `./sub2api` directly; the setup wizard at `http://localhost:8080` walks you through database, Redis, and admin account setup, then writes `config.yaml` for you.
2. **If you already created `config.yaml`:** Temporarily move it aside so the wizard can trigger on first run, then restore it afterwards:
```bash
mv config.yaml config.yaml.bak
./sub2api # wizard runs at http://localhost:8080 and writes a fresh config.yaml
# stop the server (Ctrl+C) once the wizard completes, then restore your config:
mv config.yaml.bak config.yaml
./sub2api # restart in normal mode and log in with the admin you just created
```
```bash
# 6. Run the application
./sub2api
@@ -559,6 +632,55 @@ Simple Mode is designed for individual developers or internal teams who want qui
---
## Grok / xAI OAuth Support
Sub2API supports Grok subscription accounts through xAI OAuth and forwards OpenAI-compatible Responses traffic to xAI.
### Supported Scope
- Platform name: `grok`
- Account type: OAuth subscription accounts
- Public Responses targets: `/v1/responses`, `/responses`, and `/backend-api/codex/responses`, forwarded to `${XAI_BASE_URL:-https://api.x.ai/v1}/responses`
- Public Claude-compatible target: `/v1/messages`, converted to xAI Responses and returned as Anthropic Messages output for Claude CLI style clients
- Public Chat Completions targets: `/v1/chat/completions` and `/chat/completions`, forwarded to `${XAI_BASE_URL:-https://api.x.ai/v1}/chat/completions`
- Codex CLI style Responses WebSocket ingress is accepted on the Responses targets and bridged to xAI HTTP/SSE Responses upstream
- Initial text models: `grok-4.3`, `grok-build-0.1`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`, and `grok-4.20-multi-agent-0309`
- Media targets for Grok groups: `/v1/images/generations`, `/images/generations`, `/v1/images/edits`, `/images/edits`, `/v1/videos/generations`, `/videos/generations`, `/v1/videos/{request_id}`, and `/videos/{request_id}`. Generation requests require the group image-generation permission.
- Media models: `grok-imagine`, `grok-imagine-image-quality`, `grok-imagine-image`, `grok-imagine-edit`, `grok-imagine-video`, and `grok-imagine-video-1.5`
- Out of scope for this provider: TTS, transcription, browser automation, cookies, and Grok web scraping
### OAuth Configuration
The Grok OAuth flow uses PKCE and does not require committing private secrets. The default client details follow the public xAI OAuth flow used by compatible clients, and every value can be overridden by environment variable:
| Variable | Default |
|----------|---------|
| `XAI_OAUTH_CLIENT_ID` | Public xAI OAuth client ID |
| `XAI_OAUTH_SCOPE` | `openid profile email offline_access grok-cli:access api:access` |
| `XAI_OAUTH_REDIRECT_URI` | `http://127.0.0.1:56121/callback` |
| `XAI_OAUTH_AUTHORIZE_URL` | `https://auth.x.ai/oauth2/authorize` |
| `XAI_OAUTH_TOKEN_URL` | `https://auth.x.ai/oauth2/token` |
| `XAI_BASE_URL` | `https://api.x.ai/v1` |
Administrators can create or reauthorize Grok accounts from the dashboard, or use the admin API:
| Endpoint | Purpose |
|----------|---------|
| `POST /api/v1/admin/grok/oauth/auth-url` | Generate an xAI OAuth authorization URL |
| `POST /api/v1/admin/grok/oauth/exchange-code` | Exchange a callback URL, query string, or code for OAuth credentials |
| `POST /api/v1/admin/grok/oauth/refresh-token` | Validate or refresh a Grok refresh token |
| `POST /api/v1/admin/grok/accounts/:id/refresh` | Refresh an existing Grok account |
Credential storage reuses the existing account JSON fields: `access_token`, `refresh_token`, `token_type`, `expires_at`, optional `email`, optional `subscription_tier`, and `entitlement_status`.
### Usage And Quota Display
xAI quota is passive. Sub2API does not invent subscription quota values; it records whitelisted xAI rate-limit headers from successful or rate-limited upstream responses when xAI sends them. Before the first usable upstream response, the dashboard shows quota as unknown and still displays local Sub2API usage stats.
`401` responses mark the account as needing reauthorization. `403` responses are treated as entitlement or subscription-tier failures instead of token-refresh loops. `429` responses use `Retry-After` or a short cooldown to temporarily remove the account from scheduling.
---
## Antigravity Support
Sub2API supports [Antigravity](https://antigravity.so/) accounts. After authorization, dedicated endpoints are available for Claude and Gemini models.
@@ -583,12 +705,6 @@ Antigravity accounts support optional **hybrid scheduling**. When enabled, the g
> **⚠️ Warning**: Anthropic Claude and Antigravity Claude **cannot be mixed within the same conversation context**. Use groups to isolate them properly.
### Known Issues
In Claude Code, Plan Mode cannot exit automatically. (Normally when using the native Claude API, after planning is complete, Claude Code will pop up options for users to approve or reject the plan.)
**Workaround**: Press `Shift + Tab` to manually exit Plan Mode, then type your response to approve or reject the plan.
---
## Project Structure
@@ -619,16 +735,6 @@ sub2api/
└── install.sh # One-click installation script
```
## Disclaimer
> **Please read carefully before using this project:**
>
> :rotating_light: **Terms of Service Risk**: Using this project may violate Anthropic's Terms of Service. Please read Anthropic's user agreement carefully before use. All risks arising from the use of this project are borne solely by the user.
>
> :book: **Disclaimer**: This project is for technical learning and research purposes only. The author assumes no responsibility for account suspension, service interruption, or any other losses caused by the use of this project.
---
## Star History
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
+781
View File
@@ -0,0 +1,781 @@
# Sub2API
<div align="center">
[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/)
[![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
**AI API 网关平台 - 订阅配额分发管理**
[English](README.md) | 中文 | [日本語](README_JA.md)
</div>
## ⚠️ 重要提醒
使用本项目前,请务必仔细阅读以下内容:
- **🚨 服务条款风险**:使用本项目可能违反 Anthropic 等上游服务商的服务条款。请在使用前仔细阅读相关服务商的用户协议,由此产生的一切风险由用户自行承担。
- **⚖️ 合规使用**:请在符合您所在国家或地区法律法规的前提下使用本项目,严禁将其用于任何违法违规用途。
- **📖 免责声明**:本项目仅供技术学习与研究使用,作者不对因使用本项目导致的账户封禁、服务中断、数据丢失或其他任何直接或间接损失承担责任。
- **🚫 无商业授权**:本项目从未授权任何个人或组织基于本项目开展任何形式的商业化运营。任何以本项目名义或基于本项目从事的商业行为均与本项目及其开发者无关,由此产生的一切纠纷、损失和法律责任由行为主体自行承担。
## ❤️ 赞助商
> [想出现在这里?](mailto:support@sub2api.org)
<table>
<tr>
<td width="180"><a href="https://www.openmodel.ai?ref=sub2api"><img src="assets/partners/logos/openmodel.jpg" alt="openmodel" width="150"></a></td>
<td>一个API,顶级模型随便用!<a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> 专注于生产级、高可用的 AI API 网关,让你的应用真正做到高速稳定:自动故障转移、智能选最优渠道、生产级 SLA 保障。远超单一供应商的 SLA,让稳定性成为您的核心竞争力。</td>
</tr>
<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>感谢 ETok.ai 赞助了本项目!ETok.ai 致力于打造一站式 AI 编程工具服务平台。我们提供 Claude Code 专业套餐及技术社群服务,同时支持 Google Gemini 和 OpenAI Codex。通过精心设计的套餐方案和专业的技术社群,为开发者提供稳定的服务保障和持续的技术支持,让 AI 辅助编程真正成为开发者的生产力工具。点击<a href="https://etok.ai">这里</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>感谢 APIKEY.FUN 赞助了本项目!<a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> 是 sub2api 开源项目的核心贡献者之一,致力于提供开放、稳定、高性价比的 AI API 接入服务。平台支持 Claude、OpenAI、Gemini 等热门模型的 API 中转服务,价格低至官方原价的 7%。通过专属链接 <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> 注册,可享受所有充值永久 95 折优惠。</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>感谢 AIGoCode 赞助了本项目!AIGoCode 是一站式集成 Claude Code、Codex 以及最新 Gemini 模型的综合平台,为您提供稳定、高效、高性价比的 AI 编程服务。平台提供灵活的订阅方案,零封号风险,免 VPN 直连,响应极速。AIGoCode 为 sub2api 用户准备了专属福利:通过<a href="https://aigocode.com/invite/SUB2API">此链接</a>注册,首次充值可额外获得 10% 赠送额度!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>感谢 AICodeMirror 赞助了本项目!AICodeMirror 提供 Claude Code / Codex / Gemini CLI 官方高稳定性中转服务,企业级并发、快速开票、7×24 小时专属技术支持。Claude Code / Codex / Gemini 官方通道低至原价 38% / 2% / 9%,充值更享额外折扣!AICodeMirror 为 sub2api 用户提供专属福利:通过<a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">此链接</a>注册,首次充值立享 8 折优惠,企业客户最高可享 75 折!</td>
</tr>
<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>感谢 BmoPlus 赞助了本项目!BmoPlus 是一家专为AI订阅重度用户打造的可靠 AI 账号代充服务商,提供稳定的 ChatGPT Plus / ChatGPT Pro(全程质保) / Claude Pro / Super Grok / Gemini Pro 的官方代充&成品账号。 通过<a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus AI成品号专卖/代充</a>注册下单的用户,可享GPT 官网订阅一折 的震撼价格!</td>
</tr>
<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>感谢 Bestproxy 赞助了本项目!<a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> 是一家提供高纯度住宅IP,支持一号一IP独享,结合真实家庭网络与指纹隔离,可实现链路环境隔离,降低关联风控概率。</td>
</tr>
<tr>
<td width="180"><a href="https://pateway.ai/?ch=1tsfr51"><img src="assets/partners/logos/pateway.png" alt="pateway" width="150"></a></td>
<td>感谢 PatewayAI 赞助了本项目!PatewayAI 是一家面向重度 AI 开发者、专注官方直连的高品质模型 API 中转服务商。提供 Claude 全系列与 Codex 系列模型,100% 官方源直供,不掺假不注水,欢迎检验。计费透明,Token 级账单可逐笔核验。
同时支持企业级高并发,并为企业客户提供了专业的管理平台,企业客户可签订正式合同并开具发票,更多详情进入官网获取联系方式。
现在通过 <a href="https://pateway.ai/?ch=1tsfr51">此链接</a> 注册即送 $3 试用额度,用户充值低至 6 折,邀请好友双向赠送,邀请奖励可达 $150。</td>
</tr>
<tr>
<td width="180"><a href="https://api.pptoken.org/register?promo=SUB2API"><img src="assets/partners/logos/pptoken.png" alt="pptoken" width="150"></a></td>
<td>感谢 PPToken.org 赞助本项目! <a href="https://api.pptoken.org/register?promo=SUB2API">PPToken.org</a> 主打 GPT 系列模型 API 中转服务,支持 Codex、Claude Code、OpenAI 兼容客户端及 Gemini CLI 等工具接入。充值 1:1,1 元=1 美元额度;GPT 模型最低 0.16 倍倍率,综合成本约为官方价格的 0.22 折,最快首字 Token 约 1 秒,适合开发者低成本、高响应速度接入 GPT 模型能力。技术支持: 7×24 小时真人响应(不是机器人),群内@技术,10 分钟内有回复 。赞助商福利:前 200 名用户通过 <a href="https://api.pptoken.org/register?promo=SUB2API">[专属注册链接]</a> 注册,输入优惠码 `SUB2API`,可领取 Codex / Claude Code 免费试用额度,无门槛、不绑卡。
</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=sub2api"><img src="assets/partners/logos/unity2.png" alt="unity2" width="150"></a></td>
<td>感谢 Unity2 赞助本项目! <a href="https://unity2.ai/register?source=sub2api">Unity2</a> 是面向个人开发者、团队、企业的高性能 AI 模型 API 中转平台,长期服务国内头部企业,日均承载超 300 亿 token 调用,支持 5000 RPM 级高并发。一个 API Key 即可适配 Claude Code、Codex、OpenAI 模型、IDE 插件和 Agent 工作流等场景。具备企业级稳定供应能力,在高并发、持续调用和团队集中采购场景下依然保持低延迟、高可用。同时支持余额计费、组合订阅、首充优惠、企业开票、专属 1v1 对接,适合个人高频使用和企业长期接入。现在注册 Unity2.ai 可领取 $2 余额,加入官方群再送 $10 余额,合计最高可领 $12 免费额度,适合先体验后长期使用。<a href="https://unity2.ai/register?source=sub2api">注册链接</a>
</td>
</tr>
<tr>
<td width="180"><a href="https://veilx.io/#/hello/SJRBRVDV"><img src="assets/partners/logos/veilx.png" alt="veilx" width="150"></a></td>
<td>感谢 Veilx 赞助本项目! <a href="https://veilx.io/#/hello/SJRBRVDV">Veilx</a> CDN 专为超大规模 API 请求场景打造,针对 AI 中转站业务与 AI API 调用链路进行了深度优化,轻松应对高并发、高频请求与大流量传输,为开发者与企业提供更快、更稳、更低延迟的加速体验。无论是 OpenAI、Claude、Gemini 等 AI 接口中转,还是聊天、绘图、Embedding、流式输出等复杂场景,Veilx 都能显著提升响应速度与连接稳定性,有效降低网络波动带来的超时与失败问题。同时,Veilx 提供中国三网优化回国极速线路,大幅提升中国大陆地区访问海外 AI 服务的速度与稳定性,特别适合全球 AI 中转平台、海外 AI SaaS、跨境业务与高并发 API 系统部署。专为 AI API 而生,让你的 AI 中转服务更快、更稳、更省心。<a href="https://veilx.io/#/hello/SJRBRVDV">购买地址</a>
</td>
</tr>
<tr>
<td width="180"><a href="https://roxybrowser.com/invite/bgGKG7"><img src="assets/partners/logos/RoxyBrowser.png" alt="RoxyBrowser" width="150"></a></td>
<td>感谢 RoxyBrowser 赞助本项目!<a href="https://roxybrowser.com/invite/bgGKG7">RoxyBrowser</a> 是 Sub2API 的理想搭档:内置原生 Roxy AI Agent 与高质量原生住宅 IP,支持通过简单命令实现批量自动化,显著提升多账号管理的安全性与效率!点击<a href="https://roxybrowser.com/invite/bgGKG7">此链接</a>注册,可领取免费住宅 IP 套餐与终身 9 折优惠。
</td>
</tr>
<tr>
<td width="180"><a href="https://apikl.ai"><img src="assets/partners/logos/apikl.png" alt="apikl" width="150"></a></td>
<td>感谢 Apikl 赞助本项目!平台基于 Sub2API 搭建,为开发者提供 Codex / Claude 系列模型的中转服务,专注于长期稳定、高速直连与高性价比。支持按量计费的余额结算、企业级正规发票及一对一专属对接。<a href="https://apikl.ai">立即注册</a>即享充值 1:1 赠送 — 余额翻倍!
</td>
</tr>
<tr>
<td width="180"><a href="https://tokeneum.ai"><img src="assets/partners/logos/tokeneum.png" alt="tokeneum" width="150"></a></td>
<td>感谢 TokenEum 赞助本项目!<a href="https://tokeneum.ai">TokenEum</a> 是一家综合性 AI 模型聚合平台与智能体开发公司,汇聚 Claude、Gemini、OpenAI 等国际顶级模型,以及 GLM、Qwen、Kimi 等主流开源模型,提供不同质量与价格梯度的丰富选择,满足多样化需求。平台还接入了 Seedance2.0、Happy Horse 等前沿视频生成模型。秉持透明诚信的经营理念,TokenEum 确保所有模型信息真实可靠。访问 <a href="https://tokeneum.ai">tokeneum.ai</a> 开始使用。
</td>
</tr>
<tr>
<td width="180"><a href="https://666api.work/sub2api"><img src="assets/partners/logos/666api.jpg" alt="666api" width="150"></a></td>
<td>感谢 666api 赞助本项目!<a href="https://666api.work/sub2api">666api</a> 是一站式综合服务平台,提供:<br>
⚡ API 中转 — 全球模型按量计费接入,100% 官方源直供,最高 75 折优惠<br>
&nbsp;&nbsp;&nbsp;&nbsp;独家特惠:智谱 GLM 5 折 · DeepSeek V4-pro 5 折 · Seedance 2.0 0.8 折(白名单)· HappyHorse 海外版 3 折(白名单)<br>
🔑 GPT 订阅账号(含同源 IP)· 全球住宅 IP<br>
💰 支持开票
</td>
</tr>
<tr>
<td width="180"><a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html"><img src="assets/partners/logos/huoshan.png" alt="HuoShan" width="150"></a></td>
<td>感谢火山方舟 Agent Plan 模型赞助了本项目!方舟 Agent Plan 模型订阅套餐集成了包含 Doubao-Seed、Doubao-Seedance、Doubao-Seedream 等在内的字节跳动自研 SOTA 级模型,覆盖文本、代码、图像、视频等多模态任务。最新支持 MiniMax-M3、DeepSeek-V4 系列、GLM-5.1、Doubao-Seed-2.0 系列、Kimi-K2.6 等模型,工具不限。超全模态模型与 Harness 升级一步到位,深度支持 Agent 框架与 AI 编程工具。一次订阅,可以为不同任务切换合适的 AI 引擎。方舟 Coding Plan 为 Sub2Api 的用户提供了专属福利:通过<a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html">此链接</a>订阅方舟 Coding Plan,新客户首两个月享 2.5 折优惠 <a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html">>>For developers outside Mainland China, please click here</a></td>
</tr>
<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>感谢 随想AI网关 赞助本项目!<a href="https://sui-xiang.com/">随想AI网关</a> 是一家可靠高效的 API 中继服务提供商,提供 Claude、Codex、Gemini 等的中继服务。注重隐私的中转站·无数据倒卖·无模型掺水,隐私,透明,极速售后。新账户注册每日签到就送 0.5 元测试额度,充值额度 1:1,无需订阅,按量付费。多线路冗余、跨区域容灾、自动故障切换,长链路 SSE 不中断。99.9% 可用性,关键调用从不掉队。
</td>
</tr>
<tr>
<td width="180"><a href="https://www.miyaip.com/?invitecode=sub2api"><img src="assets/partners/logos/miyaip.png" alt="miyaip" width="150"></a></td>
<td>感谢 MiyaIP 赞助本项目!<a href="https://www.miyaip.com/?invitecode=sub2api">MiyaIP</a> 是一家专注于全球住宅代理网络服务的平台,致力于为企业开发者、跨境业务团队及AI 应用用户提供高质量、纯净的海外住宅 IP 资源。为 AI 平台、海外 SaaS 及其他在线服务提供稳定、独立的海外网络环境,支持多地区访问测试和项目环境隔离。适用于需要访问海外 AI 服务的开发和测试场景,例如:AI 模型平台访问、AI 开发测试、AI SaaS 服务使用、AI API 调试、多地区网络环境验证
</td>
</tr>
<tr>
<td width="180"><a href="https://anpin.ai"><img src="assets/partners/logos/anpin.jpg" alt="anpin" width="150"></a></td>
<td>感谢 <a href="https://anpin.ai">anpin.ai</a> 赞助本项目!anpin.ai 是一家致力于推动 AI 普惠的高端 AI 中转服务平台。我们以先进的技术架构和全球分布式部署,为用户提供直达国际顶尖大模型的高速通道。<br>
自建一手号池:1-3S超快响应 支持同行分发<br>
极致稳定:多线智能路由 + 冗余备份系统,确保服务全年无休、高可用运行;<br>
模型真实性:不做任何内容干预与二次过滤,让您体验到最纯粹、最强大的原生模型能力。<br>
充值1:1 企业级服务可开票,安品Ai不只是中转站,更是您连接前沿智能世界的安全、可靠、高效桥梁
</td>
</tr>
<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>感谢 Proxy4Free 赞助本项目!Proxy4Free 是面向开发者和 AI 应用的数据代理服务商,提供住宅代理、静态住宅代理、ISP 代理及数据中心代理等多种代理解决方案,适用于 Web Scraping、Browser Automation、AI Agent 等场景。支持全球 IP 资源、稳定连接与灵活切换,帮助开发者提升数据采集成功率,降低 IP 封禁风险。通过<a href="https://www.proxy4free.com/?keyword=4yjqecpc">此链接注册</a>即可开始体验,轻松构建更稳定、高效的自动化工作流。
</td>
</tr>
<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 感谢 FastAIToken 对本项目的赞助! <a href="http://www.fastaitoken.com/register">FastAIToken</a> 是面向开发者的 AI API 聚合平台,支持 OpenAI、Claude、Gemini 等主流大模型,充值 1:1,1 元 = 1 美元 API 额度,让开发者以更低成本、更便捷地使用全球领先的大模型服务。<br>
🚀 平台提供多种渠道自由选择:超级低价的0.02x OpenAI 福利分组(限时)、低至 0.25x OpenAI 分组、0.7x Claude 95%固定缓存、1.2x Claude Max 渠道;同时提供公开状态页,实时展示各分组的可用率、延迟及运行状态,服务透明可靠,并提供 7×24 小时真人技术支持(非机器人),快速响应开发者需求。
</td>
</tr>
</table>
## 项目概述
Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务,平台负责鉴权、计费、负载均衡和请求转发。
## 核心功能
- **多账号管理** - 支持多种上游账号类型(OAuth、API Key)
- **API Key 分发** - 为用户生成和管理 API Key
- **精确计费** - Token 级别的用量追踪和成本计算
- **智能调度** - 智能账号选择,支持粘性会话
- **并发控制** - 用户级和账号级并发限制
- **速率限制** - 可配置的请求和 Token 速率限制
- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe,用户自助充值,无需独立部署支付服务([配置指南](docs/PAYMENT_CN.md))
- **管理后台** - Web 界面进行监控和管理
- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如工单等),扩展管理后台功能
## 生态项目
围绕 Sub2API 的社区扩展与集成项目:
| 项目 | 说明 | 功能 |
|------|------|------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~自助支付系统~~ | **已内置** — 支付功能已集成到 Sub2API 中,无需独立部署。详见 [支付配置指南](docs/PAYMENT_CN.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用(iOS/Android/Web),支持用户管理、账号管理、监控看板、多后端切换;基于 Expo + React Native 构建 |
## 技术栈
| 组件 | 技术 |
|------|------|
| 后端 | Go 1.25.7, Gin, Ent |
| 前端 | Vue 3.4+, Vite 5+, TailwindCSS |
| 数据库 | PostgreSQL 15+ |
| 缓存/队列 | Redis 7+ |
---
## Nginx 反向代理注意事项
通过 Nginx 反向代理 Sub2API(或 CRS 服务)并搭配 Codex CLI 使用时,需要在 Nginx 配置的 `http` 块中添加:
```nginx
underscores_in_headers on;
```
Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),这会导致多账号环境下的粘性会话功能失效。
---
## 部署方式
### 方式一:脚本安装(推荐)
一键安装脚本,自动从 GitHub Releases 下载预编译的二进制文件。
#### 前置条件
- Linux 服务器(amd64 或 arm64)
- PostgreSQL 15+(已安装并运行)
- Redis 7+(已安装并运行)
- Root 权限
#### 安装步骤
```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
```
脚本会自动:
1. 检测系统架构
2. 下载最新版本
3. 安装二进制文件到 `/opt/sub2api`
4. 创建 systemd 服务
5. 配置系统用户和权限
#### 安装后配置
```bash
# 1. 启动服务
sudo systemctl start sub2api
# 2. 设置开机自启
sudo systemctl enable sub2api
# 3. 在浏览器中打开设置向导
# http://你的服务器IP:8080
```
设置向导将引导你完成:
- 数据库配置
- Redis 配置
- 管理员账号创建
#### 升级
可以直接在 **管理后台** 左上角点击 **检测更新** 按钮进行在线升级。
网页升级功能支持:
- 自动检测新版本
- 一键下载并应用更新
- 支持回滚
#### 常用命令
```bash
# 查看状态
sudo systemctl status sub2api
# 查看日志
sudo journalctl -u sub2api -f
# 重启服务
sudo systemctl restart sub2api
# 卸载
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
```
---
### 方式二:Docker Compose(推荐)
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
#### 前置条件
- Docker 20.10+
- Docker Compose v2+
#### 快速开始(一键部署)
使用自动化部署脚本快速搭建:
```bash
# 创建部署目录
mkdir -p sub2api-deploy && cd sub2api-deploy
# 下载并运行部署准备脚本
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f sub2api
```
**脚本功能:**
- 下载 `docker-compose.local.yml`(本地保存为 `docker-compose.yml`)和 `.env.example`
- 自动生成安全凭证(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)
- 创建 `.env` 文件并填充自动生成的密钥
- 创建数据目录(使用本地目录,便于备份和迁移)
- 显示生成的凭证供你记录
#### 手动部署
如果你希望手动配置:
```bash
# 1. 克隆仓库
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
# 2. 复制环境配置文件
cp .env.example .env
# 3. 编辑配置(生成安全密码)
nano .env
```
**`.env` 必须配置项:**
```bash
# PostgreSQL 密码(必需)
POSTGRES_PASSWORD=your_secure_password_here
# JWT 密钥(推荐 - 重启后保持用户登录状态)
JWT_SECRET=your_jwt_secret_here
# TOTP 加密密钥(推荐 - 重启后保留双因素认证)
TOTP_ENCRYPTION_KEY=your_totp_key_here
# 可选:管理员账号
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password
# 可选:自定义端口
SERVER_PORT=8080
```
**生成安全密钥:**
```bash
# 生成 JWT_SECRET
openssl rand -hex 32
# 生成 TOTP_ENCRYPTION_KEY
openssl rand -hex 32
# 生成 POSTGRES_PASSWORD
openssl rand -hex 32
```
```bash
# 4. 创建数据目录(本地版)
mkdir -p data postgres_data redis_data
# 5. 启动所有服务
# 选项 A:本地目录版(推荐 - 易于迁移)
docker compose -f docker-compose.local.yml up -d
# 选项 B:命名卷版(简单设置)
docker compose up -d
# 6. 查看状态
docker compose -f docker-compose.local.yml ps
# 7. 查看日志
docker compose -f docker-compose.local.yml logs -f sub2api
```
#### 部署版本对比
| 版本 | 数据存储 | 迁移便利性 | 适用场景 |
|------|---------|-----------|---------|
| **docker-compose.local.yml** | 本地目录 | ✅ 简单(打包整个目录) | 生产环境、频繁备份 |
| **docker-compose.yml** | 命名卷 | ⚠️ 需要 docker 命令 | 简单设置 |
**推荐:** 使用 `docker-compose.local.yml`(脚本部署)以便更轻松地管理数据。
#### 启用“数据管理”功能(datamanagementd)
如需启用管理后台“数据管理”,需要额外部署宿主机数据管理进程 `datamanagementd`。
关键点:
- 主进程固定探测:`/tmp/sub2api-datamanagement.sock`
- 只有该 Socket 可连通时,数据管理功能才会开启
- Docker 场景需将宿主机 Socket 挂载到容器同路径
详细部署步骤见:`deploy/DATAMANAGEMENTD_CN.md`
#### 访问
在浏览器中打开 `http://你的服务器IP:8080`
如果管理员密码是自动生成的,在日志中查找:
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```
#### 升级
```bash
# 拉取最新镜像并重建容器
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```
#### 轻松迁移(本地目录版)
使用 `docker-compose.local.yml` 时,可以轻松迁移到新服务器:
```bash
# 源服务器
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
# 传输到新服务器
scp sub2api-complete.tar.gz user@new-server:/path/
# 新服务器
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -d
```
#### 常用命令
```bash
# 停止所有服务
docker compose -f docker-compose.local.yml down
# 重启
docker compose -f docker-compose.local.yml restart
# 查看所有日志
docker compose -f docker-compose.local.yml logs -f
# 删除所有数据(谨慎!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```
---
### 方式三:源码编译
从源码编译安装,适合开发或定制需求。
#### 前置条件
- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+
#### 编译步骤
```bash
# 1. 克隆仓库
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api
# 2. 安装 pnpm(如果还没有安装)
npm install -g pnpm
# 3. 编译前端
cd frontend
pnpm install
pnpm run build
# 构建产物输出到 ../backend/internal/web/dist/
# 4. 编译后端(嵌入前端)
cd ../backend
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server
# 5. 创建配置文件
cp ../deploy/config.example.yaml ./config.yaml
# 6. 编辑配置
nano config.yaml
```
> **注意:** `-tags embed` 参数会将前端嵌入到二进制文件中。不使用此参数编译的程序将不包含前端界面。
**`config.yaml` 关键配置:**
```yaml
server:
host: "0.0.0.0"
port: 8080
mode: "release"
database:
host: "localhost"
port: 5432
user: "postgres"
password: "your_password"
dbname: "sub2api"
redis:
host: "localhost"
port: 6379
password: ""
jwt:
secret: "change-this-to-a-secure-random-string"
expire_hour: 24
default:
user_concurrency: 5
user_balance: 0
api_key_prefix: "sk-"
rate_multiplier: 1.0
```
### Sora 功能状态(暂不可用)
> ⚠️ 当前 Sora 相关功能因上游接入与媒体链路存在技术问题,暂时不可用。
> 现阶段请勿在生产环境依赖 Sora 能力。
> 文档中的 `gateway.sora_*` 配置仅作预留,待技术问题修复后再恢复可用。
### Sora 媒体签名 URL(功能恢复后可选)
当配置 `gateway.sora_media_signing_key` 且 `gateway.sora_media_signed_url_ttl_seconds > 0` 时,网关会将 Sora 输出的媒体地址改写为临时签名 URL(`/sora/media-signed/...`)。这样无需 API Key 即可在浏览器中直接访问,且具备过期控制与防篡改能力(签名包含 path + query)。
```yaml
gateway:
# /sora/media 是否强制要求 API Key(默认 false)
sora_media_require_api_key: false
# 媒体临时签名密钥(为空则禁用签名)
sora_media_signing_key: "your-signing-key"
# 临时签名 URL 有效期(秒)
sora_media_signed_url_ttl_seconds: 900
```
> 若未配置签名密钥,`/sora/media-signed` 将返回 503。
> 如需更严格的访问控制,可将 `sora_media_require_api_key` 设为 true,仅允许携带 API Key 的 `/sora/media` 访问。
访问策略说明:
- `/sora/media`:内部调用或客户端携带 API Key 才能下载
- `/sora/media-signed`:外部可访问,但有签名 + 过期控制
`config.yaml` 还支持以下安全相关配置:
- `cors.allowed_origins` 配置 CORS 白名单
- `security.url_allowlist` 配置上游/价格数据/CRS 主机白名单
- `security.url_allowlist.enabled` 可关闭 URL 校验(慎用)
- `security.url_allowlist.allow_insecure_http` 关闭校验时允许 HTTP URL
- `security.url_allowlist.allow_private_hosts` 允许私有/本地 IP 地址
- `security.response_headers.enabled` 可启用可配置响应头过滤(关闭时使用默认白名单)
- `security.csp` 配置 Content-Security-Policy
- `billing.circuit_breaker` 计费异常时 fail-closed
- `server.trusted_proxies` 启用可信代理解析 X-Forwarded-For
- `turnstile.required` 在 release 模式强制启用 Turnstile
**网关防御纵深建议(重点)**
- `gateway.upstream_response_read_max_bytes`:限制非流式上游响应读取大小(默认 `8MB`),用于防止异常响应导致内存放大。
- `gateway.proxy_probe_response_read_max_bytes`:限制代理探测响应读取大小(默认 `1MB`)。
- `gateway.gemini_debug_response_headers`:默认 `false`,仅在排障时短时开启,避免高频请求日志开销。
- `/auth/register`、`/auth/login`、`/auth/login/2fa`、`/auth/send-verify-code` 已提供服务端兜底限流(Redis 故障时 fail-close)。
- 推荐将 WAF/CDN 作为第一层防护,服务端限流与响应读取上限作为第二层兜底;两层同时保留,避免旁路流量与误配置风险。
**⚠️ 安全警告:HTTP URL 配置**
当 `security.url_allowlist.enabled=false` 时,系统仅执行最小 URL 校验,且**默认允许 HTTP URL**(开发友好模式,Docker Compose 部署的默认值一致)。生产环境建议显式收紧为仅允许 HTTPS:
```yaml
security:
url_allowlist:
enabled: false # 禁用白名单检查
allow_insecure_http: false # 仅允许 HTTPS(生产环境推荐)
```
**或通过环境变量:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**允许 HTTP 的风险:**
- API 密钥和数据以**明文传输**(可被截获)
- 易受**中间人攻击 (MITM)**
- **不适合生产环境**
**适用场景:**
- ✅ 开发/测试环境的本地服务器(http://localhost)
- ✅ 内网可信端点
- ✅ 获取 HTTPS 前测试账号连通性
- ❌ 生产环境(仅使用 HTTPS)
**设置 `allow_insecure_http: false` 后,HTTP URL 会返回如下错误:**
```
Invalid base URL: invalid url scheme: http
```
如关闭 URL 校验或响应头过滤,请加强网络层防护:
- 出站访问白名单限制上游域名/IP
- 阻断私网/回环/链路本地地址
- 强制仅允许 TLS 出站
- 在反向代理层移除敏感响应头
#### ⚠️ 重要:创建管理员账号
初始管理员账号**只能通过 setup 向导创建**(首次启动时访问 `http://<host>:8080`)。`config.yaml` 中的 `default.admin_email` / `default.admin_password` 字段**不会被用来创建管理员**——它们只是出于历史原因保留在模板里。
由于上面第 5 步预先创建了 `config.yaml`,**setup 向导在首次启动时会被跳过**:服务检测到 config 已存在,会直接进入正常模式,此时 `users` 表为空,首次登录会返回 `invalid email or password`。
**创建管理员的两种方式:**
1. **推荐——让向导自动生成 `config.yaml`:** 跳过上面的第 5 步(不要执行 `cp`)。直接运行 `./sub2api`,访问 `http://localhost:8080`,向导会引导你完成数据库、Redis 和管理员账号配置,并自动写出 `config.yaml`。
2. **如果你已经创建了 `config.yaml`:** 首次启动前先把它临时移走以触发向导,完成后再恢复:
```bash
mv config.yaml config.yaml.bak
./sub2api # 向导在 http://localhost:8080 启动,并生成新的 config.yaml
# 向导完成后 Ctrl+C 停服,再恢复你的配置:
mv config.yaml.bak config.yaml
./sub2api # 重启进入正常模式,用刚创建的管理员登录
```
```bash
# 6. 运行应用
./sub2api
```
#### HTTP/2 (h2c) 与 HTTP/1.1 回退
后端明文端口默认支持 h2c,并保留 HTTP/1.1 回退用于 WebSocket 与旧客户端。浏览器通常不支持 h2c,性能收益主要在反向代理或内网链路。
**反向代理示例(Caddy):**
```caddyfile
transport http {
versions h2c h1
}
```
**验证:**
```bash
# h2c prior knowledge
curl --http2-prior-knowledge -I http://localhost:8080/health
# HTTP/1.1 回退
curl --http1.1 -I http://localhost:8080/health
# WebSocket 回退验证(需管理员 token)
websocat -H="Sec-WebSocket-Protocol: sub2api-admin, jwt.<ADMIN_TOKEN>" ws://localhost:8080/api/v1/admin/ops/ws/qps
```
#### 开发模式
```bash
# 后端(支持热重载)
cd backend
go run ./cmd/server
# 前端(支持热重载)
cd frontend
pnpm run dev
```
#### 代码生成
修改 `backend/ent/schema` 后,需要重新生成 Ent + Wire:
```bash
cd backend
go generate ./ent
go generate ./cmd/server
```
---
## 简易模式
简易模式适合个人开发者或内部团队快速使用,不依赖完整 SaaS 功能。
- 启用方式:设置环境变量 `RUN_MODE=simple`
- 功能差异:隐藏 SaaS 相关功能,跳过计费流程
- 安全注意事项:生产环境需同时设置 `SIMPLE_MODE_CONFIRM=true` 才允许启动
---
## Antigravity 使用说明
Sub2API 支持 [Antigravity](https://antigravity.so/) 账户,授权后可通过专用端点访问 Claude 和 Gemini 模型。
### 专用端点
| 端点 | 模型 |
|------|------|
| `/antigravity/v1/messages` | Claude 模型 |
| `/antigravity/v1beta/` | Gemini 模型 |
### Claude Code 配置示例
```bash
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
```
### 混合调度模式
Antigravity 账户支持可选的**混合调度**功能。开启后,通用端点 `/v1/messages` 和 `/v1beta/` 也会调度该账户。
> **⚠️ 注意**:Anthropic Claude 和 Antigravity Claude **不能在同一上下文中混合使用**,请通过分组功能做好隔离。
---
## 项目结构
```
sub2api/
├── backend/ # Go 后端服务
│ ├── cmd/server/ # 应用入口
│ ├── internal/ # 内部模块
│ │ ├── config/ # 配置管理
│ │ ├── model/ # 数据模型
│ │ ├── service/ # 业务逻辑
│ │ ├── handler/ # HTTP 处理器
│ │ └── gateway/ # API 网关核心
│ └── resources/ # 静态资源
│
├── frontend/ # Vue 3 前端
│ └── src/
│ ├── api/ # API 调用
│ ├── stores/ # 状态管理
│ ├── views/ # 页面组件
│ └── components/ # 通用组件
│
└── deploy/ # 部署文件
├── docker-compose.yml # Docker Compose 配置
├── .env.example # Docker Compose 环境变量
├── config.example.yaml # 二进制部署完整配置文件
└── install.sh # 一键安装脚本
```
## Star History
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture>
</a>
---
## 许可证
本项目基于 [GNU 宽通用公共许可证 v3.0](LICENSE)(或更高版本)授权。
Copyright (c) 2026 Wesley Liddick
---
<div align="center">
**如果觉得有用,请给个 Star 支持一下!**
</div>
+108 -55
View File
@@ -12,7 +12,7 @@
**サブスクリプションクォータ配分のための AI API ゲートウェイプラットフォーム**
[English](README.md) | 日本語
[English](README.md) | [中文](README_CN.md) | 日本語
</div>
@@ -23,26 +23,11 @@
- **🚨 利用規約のリスク**:本プロジェクトの使用は、Anthropic をはじめとする上流プロバイダーの利用規約に違反する可能性があります。ご利用前に各プロバイダーのユーザー規約を必ずご確認ください。使用により生じるすべてのリスクはユーザーご自身が負うものとします。
- **⚖️ 法令遵守**:お住まいの国または地域の法令を遵守した上で本プロジェクトをご利用ください。いかなる違法な目的での使用も固く禁じます。
- **📖 免責事項**:本プロジェクトは技術的な学習および研究の目的でのみ提供されます。本プロジェクトの使用により生じたアカウントの停止、サービスの中断、データの損失、その他一切の直接的または間接的な損害について、作者は一切の責任を負いません。
## 概要
Sub2API は、AI 製品のサブスクリプションから API クォータを配分・管理するために設計された AI API ゲートウェイプラットフォームです。ユーザーはプラットフォームが生成した API キーを通じて上流の AI サービスにアクセスでき、プラットフォームは認証、課金、負荷分散、リクエスト転送を処理します。
## 機能
- **マルチアカウント管理** - 複数の上流アカウントタイプ(OAuth、APIキー)をサポート
- **APIキー配布** - ユーザー向けの APIキーの生成と管理
- **精密な課金** - トークンレベルの使用量追跡とコスト計算
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要([設定ガイド](docs/PAYMENT.md))
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
- **外部システム連携** - 外部システム(チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
- **🚫 商用利用の非許諾**:本プロジェクトの開発者は、いかなる個人または組織に対しても、本プロジェクトを利用したいかなる形態の商業運営も一切許諾していません。本プロジェクトの名義で、または本プロジェクトに基づいて行われる商業行為はすべて本プロジェクトおよびその開発者とは無関係であり、それにより生じる一切の紛争、損失、法的責任は行為者自身が負うものとします。
## ❤️ スポンサー
> [こちらに掲載しませんか?](mailto:support@pincc.ai)
> [こちらに掲載しませんか?](mailto:support@sub2api.org)
<table>
@@ -56,21 +41,16 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを
<td>ETok.ai のご支援に感謝します!ETok.ai はワンストップ AI プログラミングツールサービスプラットフォームの構築に取り組んでいます。Claude Code の専用プランと技術コミュニティサービスを提供し、Google Gemini や OpenAI Codex もサポートしています。丁寧に設計されたプランと専門的な技術コミュニティを通じて、開発者に安定したサービス保証と継続的な技術サポートを提供し、AI アシスト プログラミングを真の生産性向上ツールにします。<a href="https://etok.ai">こちら</a>から登録!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>APIKEY.FUN のご支援に感謝します!<a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> は sub2api オープンソースプロジェクトのコアコントリビューターの一つであり、オープンで安定した、コストパフォーマンスに優れた AI API アクセスサービスの提供に取り組んでいます。プラットフォームは Claude、OpenAI、Gemini など人気モデルの API 中継サービスをサポートし、価格は公式料金のわずか 7% から。専用リンク <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> から登録すると、すべてのチャージで永久 5% 割引をご利用いただけます。</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>AIGoCode のご支援に感謝します!AIGoCode は Claude Code、Codex、最新の Gemini モデルを統合したオールインワンプラットフォームで、安定的かつ効率的でコストパフォーマンスに優れた AI コーディングサービスを提供します。柔軟なサブスクリプションプラン、アカウント停止リスクゼロ、VPN 不要の直接アクセス、超高速レスポンスが特長です。AIGoCode は sub2api ユーザー向けに特別特典を用意しています:<a href="https://aigocode.com/invite/SUB2API">こちらのリンク</a>から登録すると、初回チャージ時に 10% のボーナスクレジットを追加プレゼント!</td>
</tr>
<tr>
<td width="180"><a href="https://code.silkapi.com/register?aff=SUB2API"><img src="assets/partners/logos/silkapi.png" alt="silkapi" width="150"></a></td>
<td>SilkAPI のご支援に感謝します!<a href="https://code.silkapi.com/register?aff=SUB2API">SilkAPI</a> は Sub2API をベースに構築された中継サービスで、高速かつ安定した Codex API 中継の提供に特化しています。</td>
</tr>
<tr>
<td width="180"><a href="https://ylscode.com/"><img src="assets/partners/logos/ylscode.png" alt="ylscode" width="150"></a></td>
<td>YLS Code のご支援に感謝します!<a href="https://ylscode.com/">YLS Code</a> は安全なエンタープライズグレードの Coding Agent 生産性サービスの構築に取り組んでおり、安定かつ高速な Codex / Claude / Gemini サブスクリプションサービスと従量課金 API の柔軟なプランを提供しています。期間限定で新規登録者に 3 日間の Codex 試用特典をプレゼント中!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>AICodeMirror のご支援に感謝します!AICodeMirror は Claude Code / Codex / Gemini CLI の公式高安定性リレーサービスを提供しており、エンタープライズグレードの同時実行、迅速な請求書発行、24時間年中無休の専属テクニカルサポートを備えています。Claude Code / Codex / Gemini の公式チャネルを定価の 38% / 2% / 9% で利用可能、チャージ時にはさらに追加割引!AICodeMirror は sub2api ユーザー向けに特別特典を提供中:<a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">こちらのリンク</a>から登録すると、初回チャージが 20% オフ、法人のお客様は最大 25% オフ!</td>
@@ -99,12 +79,6 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを
</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=fu2E"><img src="assets/partners/logos/runapi.png" alt="RunAPI" width="150"></a></td>
<td>RunAPI のご支援に感謝します!<a href="https://runapi.co/register?aff=fu2E">RunAPI</a> は効率的で安定した API プラットフォームで、OpenRouter の代替として利用できます。1つの API キーで OpenAI、Claude、Gemini、DeepSeek、Grok など 150以上の主要モデルにアクセスでき、価格は最低 10% から。非常に安定しており、Claude Code や OpenClaw などのツールとシームレスに互換します。
</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=sub2api"><img src="assets/partners/logos/unity2.png" alt="unity2" width="150"></a></td>
<td>Unity2 のご支援に感謝します!<a href="https://unity2.ai/register?source=sub2api">Unity2</a> は個人開発者、チーム、企業向けの高性能 AI モデル API 中継プラットフォームです。中国の大手企業に長期にわたりサービスを提供しており、1日あたり 300 億以上のトークン呼び出しを処理し、5000 RPM 級の高並列性をサポートします。1つの API キーで Claude Code、Codex、OpenAI モデル、IDE プラグイン、Agent ワークフローなど様々なシナリオに対応できます。エンタープライズグレードの安定供給能力を備え、高並列・継続的な呼び出し・チームの集中購入シーンでも低レイテンシと高可用性を維持します。残高課金、組み合わせサブスクリプション、初回チャージ特典、企業向け請求書発行、専属 1v1 サポートにも対応しており、個人の頻繁な利用にも企業の長期導入にも適しています。今 Unity2.ai に登録すると $2 の残高、公式グループに参加するとさらに $10 の残高がもらえ、合計最大 $12 の無料クレジットを獲得できます — 試用後に長期利用したい方に最適です。<a href="https://unity2.ai/register?source=sub2api">登録リンク</a>
@@ -123,8 +97,83 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを
</td>
</tr>
<tr>
<td width="180"><a href="https://apikl.ai"><img src="assets/partners/logos/apikl.png" alt="apikl" width="150"></a></td>
<td>Apikl のご支援に感謝します!Sub2API をベースに構築された本プラットフォームは、開発者向けに Codex / Claude シリーズモデルの中継サービスを提供しています。長期安定性、高速直結、高いコストパフォーマンスを重視し、従量課金の残高ベース課金、エンタープライズグレードの正規請求書、1対1の専属サポートを提供します。<a href="https://apikl.ai">今すぐ登録</a>でチャージ 1:1 ボーナス — 残高が倍に!
</td>
</tr>
<tr>
<td width="180"><a href="https://tokeneum.ai"><img src="assets/partners/logos/tokeneum.png" alt="tokeneum" width="150"></a></td>
<td>TokenEum のご支援に感謝します!<a href="https://tokeneum.ai">TokenEum</a> は総合的な AI モデル集約プラットフォームおよびインテリジェントエージェント開発企業です。Claude、Gemini、OpenAI などの世界トップクラスのモデルに加え、GLM、Qwen、Kimi などの主要なオープンソースモデルも集約しており、品質と価格の異なる豊富な選択肢を提供してあらゆるニーズに対応します。また、Seedance2.0 や Happy Horse などの最先端の動画生成モデルも利用可能です。TokenEum は透明性と誠実なビジネスを重視し、すべてのモデル情報の正確性と信頼性を保証します。<a href="https://tokeneum.ai">tokeneum.ai</a> でぜひお試しください。
</td>
</tr>
<tr>
<td width="180"><a href="https://666api.work/sub2api"><img src="assets/partners/logos/666api.jpg" alt="666api" width="150"></a></td>
<td>666api のご支援に感謝します!<a href="https://666api.work/sub2api">666api</a> はオールインワンプラットフォームで、以下を提供しています:⚡ API 中継 — グローバルモデルへの従量課金アクセス、100% 公式ソースから直接供給、公式価格の最大 75% オフ。独占特典:Zhipu GLM 50% オフ・DeepSeek V4-pro 50% オフ・Seedance2.0 8% オフ(ホワイトリスト)・HappyHorse Overseas 30% オフ(ホワイトリスト)🔑 GPT サブスクリプションアカウント — 同源 IP 込み・グローバル住宅 IP 💰 請求書発行対応
</td>
</tr>
<tr>
<td width="180"><a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html"><img src="assets/partners/logos/byteplus.png" alt="BytePlus" width="150"></a></td>
<td>Dola seed のご支援に感謝します!Dola Seed 2.0 は、ByteDance がグローバル市場向けに独自開発した全モーダル汎用大規模モデルです。統一されたマルチモーダルアーキテクチャに基づき、テキスト・画像・音声・動画の共同理解と生成をサポートします。エージェント協調をネイティブに有効化し、強力な推論・長期タスク実行・ツール統合・コーディング能力を備えています。スマートコックピット、パーソナルアシスタント、教育、カスタマーサポート、マーケティング、リテールなど多様なシナリオに広く適用可能です。マルチモーダル知覚、エンドツーエンドの複雑タスク配信、安定したインタラクション、データセキュリティに優れ、ModelArk プラットフォームを通じて容易にアクセス・デプロイできます。<a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html">こちらのリンク</a>から登録すると、各モデルにつき 50 万トークンの無料推論クォータを獲得できます。<a href="https://dis.chatdesks.cn/chatdesk/hsyqsub2api.html"> >>中国大陸地域の開発者はこちらをクリック</a></td>
</tr>
<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>随想AI ゲートウェイのご支援に感謝します!<a href="https://sui-xiang.com/">随想AI ゲートウェイ</a> は信頼性と効率に優れた API 中継サービスプロバイダーで、Claude、Codex、Gemini などの中継サービスを提供しています。プライバシー重視の中継ステーション・データ転売なし・モデル水増しなし、プライバシー・透明性・超高速アフターサービス。新規アカウント登録後、毎日サインインで 0.5 元のテストクレジットがもらえ、チャージは 1:1、サブスクリプション不要、従量課金。マルチライン冗長、クロスリージョン災害復旧、自動フェイルオーバー、長時間 SSE 接続が途切れません。99.9% の可用性、重要な呼び出しは決して遅れません。
</td>
</tr>
<tr>
<td width="180"><a href="https://www.miyaip.com/?invitecode=sub2api"><img src="assets/partners/logos/miyaip.png" alt="miyaip" width="150"></a></td>
<td>MiyaIP のご支援に感謝します!<a href="https://www.miyaip.com/?invitecode=sub2api">MiyaIP</a> はグローバル住宅プロキシネットワークサービスに特化したプラットフォームで、企業開発者、越境ビジネスチーム、AI アプリケーションユーザーに高品質で純粋な海外住宅 IP リソースを提供することに注力しています。AI プラットフォーム、海外 SaaS、その他のオンラインサービスに安定した独立した海外ネットワーク環境を提供し、複数地域でのアクセステストやプロジェクト環境の分離をサポートします。海外 AI サービスへのアクセスが必要な開発・テストシナリオに最適です。例:AI モデルプラットフォームアクセス、AI 開発テスト、AI SaaS サービス利用、AI API デバッグ、複数地域のネットワーク環境検証など。
</td>
</tr>
<tr>
<td width="180"><a href="https://anpin.ai"><img src="assets/partners/logos/anpin.jpg" alt="anpin" width="150"></a></td>
<td><a href="https://anpin.ai">anpin.ai</a> のご支援に感謝します!anpin.ai は AI の普及推進に取り組むハイエンド AI 中継サービスプラットフォームです。先進的な技術アーキテクチャとグローバル分散デプロイメントにより、国際トップクラスの大規模モデルへの高速直通ルートを提供します。<br>
自社構築の一次アカウントプール:1-3秒の超高速応答、同業者への配布に対応<br>
極めて高い安定性:マルチライン・インテリジェントルーティング + 冗長バックアップシステムにより、年中無休の高可用性運用を確保;<br>
モデルの真正性:一切のコンテンツ介入や二次フィルタリングを行わず、最も純粋で強力なネイティブモデル能力を体験できます。<br>
チャージ 1:1、エンタープライズグレードサービスは請求書発行可能。安品 AI は単なる中継所ではなく、最先端のインテリジェント世界へと繋がる安全・信頼性・高効率の橋渡し役です。
</td>
</tr>
<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>Proxy4Free のご支援に感謝します!Proxy4Free は開発者と AI アプリケーション向けのデータプロキシサービスプロバイダーで、住宅プロキシ、静的住宅プロキシ、ISP プロキシ、データセンタープロキシなど多様なプロキシソリューションを提供しており、Web Scraping、Browser Automation、AI Agent などのシナリオに適しています。グローバル IP リソース、安定した接続、柔軟な切り替えをサポートし、開発者のデータ収集成功率の向上と IP ブロックリスクの低減を支援します。<a href="https://www.proxy4free.com/?keyword=4yjqecpc">こちらのリンクから登録</a>して、より安定した効率的な自動化ワークフローを簡単に構築しましょう。
</td>
</tr>
<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 FastAIToken のご支援に感謝します!<a href="http://www.fastaitoken.com/register">FastAIToken</a> は開発者向けの AI API アグリゲーションプラットフォームで、OpenAI、Claude、Gemini などの主要な大規模モデルに対応しています。チャージは 1:1(1 元 = 1 米ドル分の API クレジット)で、開発者がより低コスト・より手軽に世界トップクラスの大規模モデルサービスを利用できます。<br>
🚀 プラットフォームでは多彩なチャネルを自由に選択できます:超低価格の 0.02x OpenAI 特典グループ(期間限定)、最低 0.25x の OpenAI グループ、0.7x Claude(95% 固定キャッシュ)、1.2x Claude Max チャネル。さらに、各グループの可用率・レイテンシ・稼働状況をリアルタイムで表示する公開ステータスページを提供し、透明で信頼できるサービスを実現。7×24 時間の有人テクニカルサポート(ボットではありません)により、開発者のニーズに迅速に対応します。
</td>
</tr>
</table>
## 概要
Sub2API は、AI 製品のサブスクリプションから API クォータを配分・管理するために設計された AI API ゲートウェイプラットフォームです。ユーザーはプラットフォームが生成した API キーを通じて上流の AI サービスにアクセスでき、プラットフォームは認証、課金、負荷分散、リクエスト転送を処理します。
## 機能
- **マルチアカウント管理** - 複数の上流アカウントタイプ(OAuth、APIキー)をサポート
- **APIキー配布** - ユーザー向けの APIキーの生成と管理
- **精密な課金** - トークンレベルの使用量追跡とコスト計算
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要([設定ガイド](docs/PAYMENT.md))
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
- **外部システム連携** - 外部システム(チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
## エコシステム
Sub2API を拡張・統合するコミュニティプロジェクト:
@@ -421,7 +470,8 @@ pnpm run build
# 4. フロントエンドを組み込んだバックエンドをビルド
cd ../backend
go build -tags embed -o sub2api ./cmd/server
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server
# 5. 設定ファイルを作成
cp ../deploy/config.example.yaml ./config.yaml
@@ -484,20 +534,20 @@ default:
**⚠️ セキュリティ警告: HTTP URL 設定**
`security.url_allowlist.enabled=false` の場合、システムはデフォルトで最小限の URL バリデーションを行い、**HTTP URL を拒否**して HTTPS のみを許可します。HTTP URL を許可するには(開発環境や内部テスト用など)、以下を明示的に設定する必要があります:
`security.url_allowlist.enabled=false` の場合、システムは最小限の URL バリデーションのみを行い、**デフォルトで HTTP URL を許可**します(開発フレンドリーモード。Docker Compose デプロイのデフォルトも同じです)。本番環境では、以下のように明示的に HTTPS のみに制限することを推奨します:
```yaml
security:
url_allowlist:
enabled: false # 許可リストチェックを無効化
allow_insecure_http: true # HTTP URL を許可(⚠️ セキュリティリスクあり)
allow_insecure_http: false # HTTPS のみ許可(本番環境推奨)
```
**または環境変数で設定:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**HTTP を許可するリスク:**
@@ -511,7 +561,7 @@ SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
- ✅ HTTPS 取得前のアカウント接続テスト
- ❌ 本番環境(HTTPS のみを使用)
**この設定なしで表示されるエラー例:**
**`allow_insecure_http: false` 設定時に HTTP URL で表示されるエラー例:**
```
Invalid base URL: invalid url scheme: http
```
@@ -522,6 +572,25 @@ URL バリデーションまたはレスポンスヘッダーフィルタリン
- TLS のみのアウトバウンドトラフィックを強制
- プロキシで機密性の高い上流レスポンスヘッダーを除去
#### ⚠️ 重要:管理者アカウントの作成
初期管理者アカウントは**セットアップウィザード経由でのみ作成**されます(初回起動時に `http://<host>:8080` にアクセス)。`config.yaml` の `default.admin_email` / `default.admin_password` フィールドは**管理者作成には使われません**。テンプレートに残っているのは歴史的経緯によるものです。
上記ステップ 5 で事前に `config.yaml` を作成しているため、**初回起動時にセットアップウィザードはスキップされます**。サーバーは既存の config を検出して通常モードで直接起動し、この時点では `users` テーブルが空のため、初回ログインは `invalid email or password` を返します。
**管理者アカウントを作成する 2 つの方法:**
1. **推奨 — ウィザードに `config.yaml` を自動生成させる:** 上記ステップ 5 をスキップします(`cp` を実行しない)。`./sub2api` を直接起動し、`http://localhost:8080` にアクセスすると、セットアップウィザードがデータベース・Redis・管理者アカウントの設定を案内し、`config.yaml` を自動生成します。
2. **すでに `config.yaml` を作成してしまった場合:** 初回起動前に一時的に退避してウィザードを発生させ、完了後に戻します:
```bash
mv config.yaml config.yaml.bak
./sub2api # ウィザードが http://localhost:8080 で起動し、新しい config.yaml を生成します
# ウィザード完了後、Ctrl+C でサーバーを停止し、設定を復元します:
mv config.yaml.bak config.yaml
./sub2api # 通常モードで再起動し、作成した管理者でログインします
```
```bash
# 6. アプリケーションを実行
./sub2api
@@ -585,12 +654,6 @@ Antigravity アカウントはオプションの**ハイブリッドスケジュ
> **⚠️ 警告**: Anthropic Claude と Antigravity Claude は**同じ会話コンテキスト内で混在させることはできません**。グループを使用して適切に分離してください。
### 既知の問題
Claude Code では、Plan Mode を自動的に終了できません。(通常、ネイティブの Claude API を使用する場合、計画が完了すると Claude Code はユーザーに計画を承認または拒否するオプションをポップアップ表示します。)
**回避策**: `Shift + Tab` を押して手動で Plan Mode を終了し、計画を承認または拒否するためのレスポンスを入力してください。
---
## プロジェクト構成
@@ -621,16 +684,6 @@ sub2api/
└── install.sh # ワンクリックインストールスクリプト
```
## 免責事項
> **本プロジェクトをご利用の前に、以下をよくお読みください:**
>
> :rotating_light: **利用規約違反のリスク**: 本プロジェクトの使用は Anthropic の利用規約に違反する可能性があります。使用前に Anthropic のユーザー契約をよくお読みください。本プロジェクトの使用に起因するすべてのリスクは、ユーザー自身が負うものとします。
>
> :book: **免責事項**: 本プロジェクトは技術的な学習および研究目的のみで提供されています。作者は、本プロジェクトの使用によるアカウント停止、サービス中断、その他の損失について一切の責任を負いません。
---
## スター履歴
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

+3 -2
View File
@@ -1,4 +1,4 @@
FROM golang:1.26.4-alpine
FROM golang:1.26.5-alpine
WORKDIR /app
@@ -15,7 +15,8 @@ RUN go mod download
COPY . .
# 构建应用
RUN go build -o main ./cmd/server/
RUN VERSION_VALUE="$(./scripts/resolve-version.sh)" && \
go build -ldflags="-s -w -X main.Version=${VERSION_VALUE}" -o main ./cmd/server/
# 暴露端口
EXPOSE 8080
+1 -1
View File
@@ -1,6 +1,6 @@
.PHONY: build generate test test-unit test-integration test-e2e
VERSION ?= $(shell tr -d '\r\n' < ./cmd/server/VERSION)
VERSION ?= $(shell ./scripts/resolve-version.sh)
LDFLAGS ?= -s -w -X main.Version=$(VERSION)
build:
+1 -1
View File
@@ -1 +1 @@
0.1.135
0.1.146
+21
View File
@@ -85,6 +85,8 @@ func provideCleanup(
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
batchImageCleanup *service.BatchImageCleanupService,
batchImageWorker *service.BatchImageWorkerRuntime,
pricing *service.PricingService,
emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService,
@@ -94,6 +96,7 @@ func provideCleanup(
openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService,
grokOAuth *service.GrokOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
@@ -166,6 +169,18 @@ func provideCleanup(
}
return nil
}},
{"BatchImageCleanupService", func() error {
if batchImageCleanup != nil {
batchImageCleanup.Stop()
}
return nil
}},
{"BatchImageWorkerRuntime", func() error {
if batchImageWorker != nil {
batchImageWorker.Stop()
}
return nil
}},
{"TokenRefreshService", func() error {
tokenRefresh.Stop()
return nil
@@ -222,6 +237,12 @@ func provideCleanup(
antigravityOAuth.Stop()
return nil
}},
{"GrokOAuthService", func() error {
if grokOAuth != nil {
grokOAuth.Stop()
}
return nil
}},
{"OpenAIWSPool", func() error {
if openAIGateway != nil {
openAIGateway.CloseOpenAIWSPool()
+51 -14
View File
@@ -67,7 +67,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
serviceUserPlatformQuotaRepository := repository.NewUserPlatformQuotaServiceAdapter(userPlatformQuotaRepository)
billingCacheService := service.ProvideBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, userRPMCache, userGroupRateRepository, configConfig, serviceUserPlatformQuotaRepository)
apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.ProvideAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig, billingCacheService)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
apiKeyService := service.ProvideAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig, billingCacheService, concurrencyService)
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
@@ -92,10 +96,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
usageLogRepository := repository.NewUsageLogRepository(client, db)
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
opsRepository := repository.NewOpsRepository(db)
schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
batchImageRepository := repository.NewBatchImageRepository(db)
batchImageQueue := repository.NewBatchImageQueue(redisClient, configConfig)
batchImageDownloadLimiter := repository.NewBatchImageDownloadLimiter(redisClient, configConfig)
usageBillingRepository := repository.NewUsageBillingRepository(client, db)
gatewayCache := repository.NewGatewayCache(redisClient)
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
@@ -134,6 +137,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
channelRepository := repository.NewChannelRepository(db)
channelService := service.NewChannelService(channelRepository, groupRepository, apiKeyAuthCacheInvalidator, pricingService)
modelPricingResolver := service.NewModelPricingResolver(channelService, billingService)
batchImageModelPricingResolver := service.ProvideBatchImageModelPricingResolver(modelPricingResolver)
batchImagePublicService := service.NewBatchImagePublicService(batchImageRepository, accountRepository, groupRepository, userGroupRateRepository, batchImageQueue, batchImageModelPricingResolver, usageBillingRepository, apiKeyAuthCacheInvalidator, configConfig)
batchImageDownloadService := service.NewBatchImageDownloadService(batchImageRepository, accountRepository, batchImageDownloadLimiter, configConfig)
batchImageCleanupService := service.ProvideBatchImageCleanupService(batchImageRepository, accountRepository, configConfig)
batchImageWorkerRuntime := service.ProvideBatchImageWorkerRuntime(batchImageRepository, accountRepository, batchImageQueue, usageBillingRepository, usageLogRepository, batchImageModelPricingResolver, apiKeyAuthCacheInvalidator, configConfig)
notificationEmailService := service.NewNotificationEmailService(settingRepository, emailService)
balanceNotifyService := service.ProvideBalanceNotifyService(emailService, settingRepository, accountRepository, notificationEmailService)
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService, channelService, modelPricingResolver, balanceNotifyService, serviceUserPlatformQuotaRepository)
@@ -141,7 +149,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
privacyClientFactory := providePrivacyClientFactory()
openAIOAuthService := service.ProvideOpenAIOAuthService(proxyRepository, openAIOAuthClient, privacyClientFactory)
openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oAuthRefreshAPI)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, modelPricingResolver, channelService, balanceNotifyService, settingService, serviceUserPlatformQuotaRepository)
grokOAuthClient := repository.NewGrokOAuthClient()
grokOAuthService := service.NewGrokOAuthService(proxyRepository, grokOAuthClient)
grokTokenProvider := service.ProvideGrokTokenProvider(accountRepository, geminiTokenCache, grokOAuthService, oAuthRefreshAPI, tempUnschedCache)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, grokTokenProvider, modelPricingResolver, channelService, balanceNotifyService, settingService, serviceUserPlatformQuotaRepository)
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient()
driveClient := repository.NewGeminiDriveClient()
@@ -178,9 +189,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
groupHandler := admin.NewGroupHandler(adminService, dashboardService, groupCapacityService)
claudeUsageFetcher := repository.NewClaudeUsageFetcher(httpUpstream)
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
grokQuotaFetcher := service.NewGrokQuotaFetcher()
openAIQuotaService := service.ProvideOpenAIQuotaService(accountRepository, proxyRepository, openAITokenProvider, privacyClientFactory)
usageCache := service.NewUsageCache()
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache, tlsFingerprintProfileService)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, claudeTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService)
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, grokQuotaFetcher, openAIQuotaService, usageCache, identityCache, tlsFingerprintProfileService)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, claudeTokenProvider, grokTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService)
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator)
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
@@ -191,9 +204,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
backupService := service.ProvideBackupService(settingRepository, configConfig, secretEncryptor, backupObjectStoreFactory, dbDumper)
backupHandler := admin.NewBackupHandler(backupService, userService)
oAuthHandler := admin.NewOAuthHandler(oAuthService)
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService, openAIQuotaService)
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService)
grokQuotaService := service.ProvideGrokQuotaService(accountRepository, proxyRepository, grokTokenProvider, httpUpstream)
grokOAuthHandler := admin.NewGrokOAuthHandler(grokOAuthService, adminService, grokQuotaService)
proxyHandler := admin.NewProxyHandler(adminService)
adminRedeemHandler := admin.NewRedeemHandler(adminService, redeemService)
promoHandler := admin.NewPromoHandler(promoService)
@@ -241,20 +256,21 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
paymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService)
affiliateHandler := admin.NewAffiliateHandler(affiliateService, adminService)
complianceHandler := admin.NewComplianceHandler(settingService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, contentModerationHandler, paymentHandler, affiliateHandler, complianceHandler)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, grokOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, contentModerationHandler, paymentHandler, affiliateHandler, complianceHandler)
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, contentModerationService, userMessageQueueService, configConfig, settingService)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, contentModerationService, configConfig)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, contentModerationService, opsService, configConfig)
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo, notificationEmailService)
totpHandler := handler.NewTotpHandler(totpService)
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService)
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
availableChannelHandler := handler.NewAvailableChannelHandler(channelService, apiKeyService, settingService)
batchImageHandler := handler.NewBatchImageHandler(batchImagePublicService, batchImageDownloadService, batchImageCleanupService)
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, availableChannelHandler, idempotencyCoordinator, idempotencyCleanupService)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, availableChannelHandler, batchImageHandler, idempotencyCoordinator, idempotencyCleanupService)
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
@@ -265,7 +281,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig, proxyRepository)
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig, channelMonitorService, settingRepository, opsService)
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oAuthRefreshAPI, openAIGatewayService)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, grokOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oAuthRefreshAPI, openAIGatewayService)
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
proxyExpiryService := service.ProvideProxyExpiryService(proxyRepository)
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository, settingRepository, notificationEmailService, leaderLockCache, db)
@@ -273,7 +289,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService, leaderLockCache, db)
channelMonitorRunner := service.ProvideChannelMonitorRunner(channelMonitorService, settingService)
userPlatformQuotaUsageFlusher := service.ProvideUserPlatformQuotaUsageFlusher(configConfig, billingCache, serviceUserPlatformQuotaRepository, timingWheelService)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, proxyExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService, channelMonitorRunner, userPlatformQuotaUsageFlusher)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, proxyExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, batchImageCleanupService, batchImageWorkerRuntime, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, grokOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService, channelMonitorRunner, userPlatformQuotaUsageFlusher)
application := &Application{
Server: httpServer,
Cleanup: v,
@@ -315,6 +331,8 @@ func provideCleanup(
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
batchImageCleanup *service.BatchImageCleanupService,
batchImageWorker *service.BatchImageWorkerRuntime,
pricing *service.PricingService,
emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService,
@@ -324,6 +342,7 @@ func provideCleanup(
openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService,
grokOAuth *service.GrokOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
@@ -395,6 +414,18 @@ func provideCleanup(
}
return nil
}},
{"BatchImageCleanupService", func() error {
if batchImageCleanup != nil {
batchImageCleanup.Stop()
}
return nil
}},
{"BatchImageWorkerRuntime", func() error {
if batchImageWorker != nil {
batchImageWorker.Stop()
}
return nil
}},
{"TokenRefreshService", func() error {
tokenRefresh.Stop()
return nil
@@ -451,6 +482,12 @@ func provideCleanup(
antigravityOAuth.Stop()
return nil
}},
{"GrokOAuthService", func() error {
if grokOAuth != nil {
grokOAuth.Stop()
}
return nil
}},
{"OpenAIWSPool", func() error {
if openAIGateway != nil {
openAIGateway.CloseOpenAIWSPool()
+3
View File
@@ -65,6 +65,8 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
subscriptionExpirySvc,
&service.UsageCleanupService{},
idempotencyCleanupSvc,
&service.BatchImageCleanupService{},
nil, // batchImageWorker
pricingSvc,
emailQueueSvc,
billingCacheSvc,
@@ -74,6 +76,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
openAIOAuthSvc,
geminiOAuthSvc,
antigravityOAuthSvc,
nil, // grokOAuth
nil, // openAIGateway
nil, // scheduledTestRunner
nil, // backupSvc
+64 -5
View File
@@ -77,6 +77,10 @@ type Account struct {
SessionWindowEnd *time.Time `json:"session_window_end,omitempty"`
// SessionWindowStatus holds the value of the "session_window_status" field.
SessionWindowStatus *string `json:"session_window_status,omitempty"`
// Parent account id for a linked spark shadow (NULL = normal).
ParentAccountID *int64 `json:"parent_account_id,omitempty"`
// 'global' (default) or 'spark' (shadow reads codex_bengalfox).
QuotaDimension account.QuotaDimension `json:"quota_dimension,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AccountQuery when eager-loading is set.
Edges AccountEdges `json:"edges"`
@@ -89,13 +93,17 @@ type AccountEdges struct {
Groups []*Group `json:"groups,omitempty"`
// Proxy holds the value of the proxy edge.
Proxy *Proxy `json:"proxy,omitempty"`
// Parent holds the value of the parent edge.
Parent *Account `json:"parent,omitempty"`
// Children holds the value of the children edge.
Children []*Account `json:"children,omitempty"`
// UsageLogs holds the value of the usage_logs edge.
UsageLogs []*UsageLog `json:"usage_logs,omitempty"`
// AccountGroups holds the value of the account_groups edge.
AccountGroups []*AccountGroup `json:"account_groups,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [4]bool
loadedTypes [6]bool
}
// GroupsOrErr returns the Groups value or an error if the edge
@@ -118,10 +126,30 @@ func (e AccountEdges) ProxyOrErr() (*Proxy, error) {
return nil, &NotLoadedError{edge: "proxy"}
}
// ParentOrErr returns the Parent value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AccountEdges) ParentOrErr() (*Account, error) {
if e.Parent != nil {
return e.Parent, nil
} else if e.loadedTypes[2] {
return nil, &NotFoundError{label: account.Label}
}
return nil, &NotLoadedError{edge: "parent"}
}
// ChildrenOrErr returns the Children value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) ChildrenOrErr() ([]*Account, error) {
if e.loadedTypes[3] {
return e.Children, nil
}
return nil, &NotLoadedError{edge: "children"}
}
// UsageLogsOrErr returns the UsageLogs value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) UsageLogsOrErr() ([]*UsageLog, error) {
if e.loadedTypes[2] {
if e.loadedTypes[4] {
return e.UsageLogs, nil
}
return nil, &NotLoadedError{edge: "usage_logs"}
@@ -130,7 +158,7 @@ func (e AccountEdges) UsageLogsOrErr() ([]*UsageLog, error) {
// AccountGroupsOrErr returns the AccountGroups value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) AccountGroupsOrErr() ([]*AccountGroup, error) {
if e.loadedTypes[3] {
if e.loadedTypes[5] {
return e.AccountGroups, nil
}
return nil, &NotLoadedError{edge: "account_groups"}
@@ -147,9 +175,9 @@ func (*Account) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullBool)
case account.FieldRateMultiplier:
values[i] = new(sql.NullFloat64)
case account.FieldID, account.FieldProxyID, account.FieldProxyFallbackOriginID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
case account.FieldID, account.FieldProxyID, account.FieldProxyFallbackOriginID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority, account.FieldParentAccountID:
values[i] = new(sql.NullInt64)
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus, account.FieldQuotaDimension:
values[i] = new(sql.NullString)
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldTempUnschedulableUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
values[i] = new(sql.NullTime)
@@ -368,6 +396,19 @@ func (_m *Account) assignValues(columns []string, values []any) error {
_m.SessionWindowStatus = new(string)
*_m.SessionWindowStatus = value.String
}
case account.FieldParentAccountID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field parent_account_id", values[i])
} else if value.Valid {
_m.ParentAccountID = new(int64)
*_m.ParentAccountID = value.Int64
}
case account.FieldQuotaDimension:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field quota_dimension", values[i])
} else if value.Valid {
_m.QuotaDimension = account.QuotaDimension(value.String)
}
default:
_m.selectValues.Set(columns[i], values[i])
}
@@ -391,6 +432,16 @@ func (_m *Account) QueryProxy() *ProxyQuery {
return NewAccountClient(_m.config).QueryProxy(_m)
}
// QueryParent queries the "parent" edge of the Account entity.
func (_m *Account) QueryParent() *AccountQuery {
return NewAccountClient(_m.config).QueryParent(_m)
}
// QueryChildren queries the "children" edge of the Account entity.
func (_m *Account) QueryChildren() *AccountQuery {
return NewAccountClient(_m.config).QueryChildren(_m)
}
// QueryUsageLogs queries the "usage_logs" edge of the Account entity.
func (_m *Account) QueryUsageLogs() *UsageLogQuery {
return NewAccountClient(_m.config).QueryUsageLogs(_m)
@@ -542,6 +593,14 @@ func (_m *Account) String() string {
builder.WriteString("session_window_status=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ParentAccountID; v != nil {
builder.WriteString("parent_account_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("quota_dimension=")
builder.WriteString(fmt.Sprintf("%v", _m.QuotaDimension))
builder.WriteByte(')')
return builder.String()
}
+90
View File
@@ -3,6 +3,7 @@
package account
import (
"fmt"
"time"
"entgo.io/ent"
@@ -73,10 +74,18 @@ const (
FieldSessionWindowEnd = "session_window_end"
// FieldSessionWindowStatus holds the string denoting the session_window_status field in the database.
FieldSessionWindowStatus = "session_window_status"
// FieldParentAccountID holds the string denoting the parent_account_id field in the database.
FieldParentAccountID = "parent_account_id"
// FieldQuotaDimension holds the string denoting the quota_dimension field in the database.
FieldQuotaDimension = "quota_dimension"
// EdgeGroups holds the string denoting the groups edge name in mutations.
EdgeGroups = "groups"
// EdgeProxy holds the string denoting the proxy edge name in mutations.
EdgeProxy = "proxy"
// EdgeParent holds the string denoting the parent edge name in mutations.
EdgeParent = "parent"
// EdgeChildren holds the string denoting the children edge name in mutations.
EdgeChildren = "children"
// EdgeUsageLogs holds the string denoting the usage_logs edge name in mutations.
EdgeUsageLogs = "usage_logs"
// EdgeAccountGroups holds the string denoting the account_groups edge name in mutations.
@@ -95,6 +104,14 @@ const (
ProxyInverseTable = "proxies"
// ProxyColumn is the table column denoting the proxy relation/edge.
ProxyColumn = "proxy_id"
// ParentTable is the table that holds the parent relation/edge.
ParentTable = "accounts"
// ParentColumn is the table column denoting the parent relation/edge.
ParentColumn = "parent_account_id"
// ChildrenTable is the table that holds the children relation/edge.
ChildrenTable = "accounts"
// ChildrenColumn is the table column denoting the children relation/edge.
ChildrenColumn = "parent_account_id"
// UsageLogsTable is the table that holds the usage_logs relation/edge.
UsageLogsTable = "usage_logs"
// UsageLogsInverseTable is the table name for the UsageLog entity.
@@ -143,6 +160,8 @@ var Columns = []string{
FieldSessionWindowStart,
FieldSessionWindowEnd,
FieldSessionWindowStatus,
FieldParentAccountID,
FieldQuotaDimension,
}
var (
@@ -203,6 +222,32 @@ var (
SessionWindowStatusValidator func(string) error
)
// QuotaDimension defines the type for the "quota_dimension" enum field.
type QuotaDimension string
// QuotaDimensionGlobal is the default value of the QuotaDimension enum.
const DefaultQuotaDimension = QuotaDimensionGlobal
// QuotaDimension values.
const (
QuotaDimensionGlobal QuotaDimension = "global"
QuotaDimensionSpark QuotaDimension = "spark"
)
func (qd QuotaDimension) String() string {
return string(qd)
}
// QuotaDimensionValidator is a validator for the "quota_dimension" field enum values. It is called by the builders before save.
func QuotaDimensionValidator(qd QuotaDimension) error {
switch qd {
case QuotaDimensionGlobal, QuotaDimensionSpark:
return nil
default:
return fmt.Errorf("account: invalid enum value for quota_dimension field: %q", qd)
}
}
// OrderOption defines the ordering options for the Account queries.
type OrderOption func(*sql.Selector)
@@ -346,6 +391,16 @@ func BySessionWindowStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSessionWindowStatus, opts...).ToFunc()
}
// ByParentAccountID orders the results by the parent_account_id field.
func ByParentAccountID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldParentAccountID, opts...).ToFunc()
}
// ByQuotaDimension orders the results by the quota_dimension field.
func ByQuotaDimension(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQuotaDimension, opts...).ToFunc()
}
// ByGroupsCount orders the results by groups count.
func ByGroupsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
@@ -367,6 +422,27 @@ func ByProxyField(field string, opts ...sql.OrderTermOption) OrderOption {
}
}
// ByParentField orders the results by parent field.
func ByParentField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newParentStep(), sql.OrderByField(field, opts...))
}
}
// ByChildrenCount orders the results by children count.
func ByChildrenCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newChildrenStep(), opts...)
}
}
// ByChildren orders the results by children terms.
func ByChildren(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newChildrenStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByUsageLogsCount orders the results by usage_logs count.
func ByUsageLogsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
@@ -408,6 +484,20 @@ func newProxyStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.M2O, false, ProxyTable, ProxyColumn),
)
}
func newParentStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn),
)
}
func newChildrenStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn),
)
}
func newUsageLogsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
+101
View File
@@ -190,6 +190,11 @@ func SessionWindowStatus(v string) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSessionWindowStatus, v))
}
// ParentAccountID applies equality check predicate on the "parent_account_id" field. It's identical to ParentAccountIDEQ.
func ParentAccountID(v int64) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldParentAccountID, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldCreatedAt, v))
@@ -1550,6 +1555,56 @@ func SessionWindowStatusContainsFold(v string) predicate.Account {
return predicate.Account(sql.FieldContainsFold(FieldSessionWindowStatus, v))
}
// ParentAccountIDEQ applies the EQ predicate on the "parent_account_id" field.
func ParentAccountIDEQ(v int64) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldParentAccountID, v))
}
// ParentAccountIDNEQ applies the NEQ predicate on the "parent_account_id" field.
func ParentAccountIDNEQ(v int64) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldParentAccountID, v))
}
// ParentAccountIDIn applies the In predicate on the "parent_account_id" field.
func ParentAccountIDIn(vs ...int64) predicate.Account {
return predicate.Account(sql.FieldIn(FieldParentAccountID, vs...))
}
// ParentAccountIDNotIn applies the NotIn predicate on the "parent_account_id" field.
func ParentAccountIDNotIn(vs ...int64) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldParentAccountID, vs...))
}
// ParentAccountIDIsNil applies the IsNil predicate on the "parent_account_id" field.
func ParentAccountIDIsNil() predicate.Account {
return predicate.Account(sql.FieldIsNull(FieldParentAccountID))
}
// ParentAccountIDNotNil applies the NotNil predicate on the "parent_account_id" field.
func ParentAccountIDNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldParentAccountID))
}
// QuotaDimensionEQ applies the EQ predicate on the "quota_dimension" field.
func QuotaDimensionEQ(v QuotaDimension) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldQuotaDimension, v))
}
// QuotaDimensionNEQ applies the NEQ predicate on the "quota_dimension" field.
func QuotaDimensionNEQ(v QuotaDimension) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldQuotaDimension, v))
}
// QuotaDimensionIn applies the In predicate on the "quota_dimension" field.
func QuotaDimensionIn(vs ...QuotaDimension) predicate.Account {
return predicate.Account(sql.FieldIn(FieldQuotaDimension, vs...))
}
// QuotaDimensionNotIn applies the NotIn predicate on the "quota_dimension" field.
func QuotaDimensionNotIn(vs ...QuotaDimension) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldQuotaDimension, vs...))
}
// HasGroups applies the HasEdge predicate on the "groups" edge.
func HasGroups() predicate.Account {
return predicate.Account(func(s *sql.Selector) {
@@ -1596,6 +1651,52 @@ func HasProxyWith(preds ...predicate.Proxy) predicate.Account {
})
}
// HasParent applies the HasEdge predicate on the "parent" edge.
func HasParent() predicate.Account {
return predicate.Account(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, ParentTable, ParentColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasParentWith applies the HasEdge predicate on the "parent" edge with a given conditions (other predicates).
func HasParentWith(preds ...predicate.Account) predicate.Account {
return predicate.Account(func(s *sql.Selector) {
step := newParentStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasChildren applies the HasEdge predicate on the "children" edge.
func HasChildren() predicate.Account {
return predicate.Account(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChildrenTable, ChildrenColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasChildrenWith applies the HasEdge predicate on the "children" edge with a given conditions (other predicates).
func HasChildrenWith(preds ...predicate.Account) predicate.Account {
return predicate.Account(func(s *sql.Selector) {
step := newChildrenStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasUsageLogs applies the HasEdge predicate on the "usage_logs" edge.
func HasUsageLogs() predicate.Account {
return predicate.Account(func(s *sql.Selector) {
+211
View File
@@ -391,6 +391,34 @@ func (_c *AccountCreate) SetNillableSessionWindowStatus(v *string) *AccountCreat
return _c
}
// SetParentAccountID sets the "parent_account_id" field.
func (_c *AccountCreate) SetParentAccountID(v int64) *AccountCreate {
_c.mutation.SetParentAccountID(v)
return _c
}
// SetNillableParentAccountID sets the "parent_account_id" field if the given value is not nil.
func (_c *AccountCreate) SetNillableParentAccountID(v *int64) *AccountCreate {
if v != nil {
_c.SetParentAccountID(*v)
}
return _c
}
// SetQuotaDimension sets the "quota_dimension" field.
func (_c *AccountCreate) SetQuotaDimension(v account.QuotaDimension) *AccountCreate {
_c.mutation.SetQuotaDimension(v)
return _c
}
// SetNillableQuotaDimension sets the "quota_dimension" field if the given value is not nil.
func (_c *AccountCreate) SetNillableQuotaDimension(v *account.QuotaDimension) *AccountCreate {
if v != nil {
_c.SetQuotaDimension(*v)
}
return _c
}
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
func (_c *AccountCreate) AddGroupIDs(ids ...int64) *AccountCreate {
_c.mutation.AddGroupIDs(ids...)
@@ -411,6 +439,40 @@ func (_c *AccountCreate) SetProxy(v *Proxy) *AccountCreate {
return _c.SetProxyID(v.ID)
}
// SetParentID sets the "parent" edge to the Account entity by ID.
func (_c *AccountCreate) SetParentID(id int64) *AccountCreate {
_c.mutation.SetParentID(id)
return _c
}
// SetNillableParentID sets the "parent" edge to the Account entity by ID if the given value is not nil.
func (_c *AccountCreate) SetNillableParentID(id *int64) *AccountCreate {
if id != nil {
_c = _c.SetParentID(*id)
}
return _c
}
// SetParent sets the "parent" edge to the Account entity.
func (_c *AccountCreate) SetParent(v *Account) *AccountCreate {
return _c.SetParentID(v.ID)
}
// AddChildIDs adds the "children" edge to the Account entity by IDs.
func (_c *AccountCreate) AddChildIDs(ids ...int64) *AccountCreate {
_c.mutation.AddChildIDs(ids...)
return _c
}
// AddChildren adds the "children" edges to the Account entity.
func (_c *AccountCreate) AddChildren(v ...*Account) *AccountCreate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddChildIDs(ids...)
}
// AddUsageLogIDs adds the "usage_logs" edge to the UsageLog entity by IDs.
func (_c *AccountCreate) AddUsageLogIDs(ids ...int64) *AccountCreate {
_c.mutation.AddUsageLogIDs(ids...)
@@ -515,6 +577,10 @@ func (_c *AccountCreate) defaults() error {
v := account.DefaultSchedulable
_c.mutation.SetSchedulable(v)
}
if _, ok := _c.mutation.QuotaDimension(); !ok {
v := account.DefaultQuotaDimension
_c.mutation.SetQuotaDimension(v)
}
return nil
}
@@ -584,6 +650,14 @@ func (_c *AccountCreate) check() error {
return &ValidationError{Name: "session_window_status", err: fmt.Errorf(`ent: validator failed for field "Account.session_window_status": %w`, err)}
}
}
if _, ok := _c.mutation.QuotaDimension(); !ok {
return &ValidationError{Name: "quota_dimension", err: errors.New(`ent: missing required field "Account.quota_dimension"`)}
}
if v, ok := _c.mutation.QuotaDimension(); ok {
if err := account.QuotaDimensionValidator(v); err != nil {
return &ValidationError{Name: "quota_dimension", err: fmt.Errorf(`ent: validator failed for field "Account.quota_dimension": %w`, err)}
}
}
return nil
}
@@ -723,6 +797,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldSessionWindowStatus, field.TypeString, value)
_node.SessionWindowStatus = &value
}
if value, ok := _c.mutation.QuotaDimension(); ok {
_spec.SetField(account.FieldQuotaDimension, field.TypeEnum, value)
_node.QuotaDimension = value
}
if nodes := _c.mutation.GroupsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
@@ -760,6 +838,39 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_node.ProxyID = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.ParentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: account.ParentTable,
Columns: []string{account.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.ParentAccountID = &nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.ChildrenIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.UsageLogsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1290,6 +1401,36 @@ func (u *AccountUpsert) ClearSessionWindowStatus() *AccountUpsert {
return u
}
// SetParentAccountID sets the "parent_account_id" field.
func (u *AccountUpsert) SetParentAccountID(v int64) *AccountUpsert {
u.Set(account.FieldParentAccountID, v)
return u
}
// UpdateParentAccountID sets the "parent_account_id" field to the value that was provided on create.
func (u *AccountUpsert) UpdateParentAccountID() *AccountUpsert {
u.SetExcluded(account.FieldParentAccountID)
return u
}
// ClearParentAccountID clears the value of the "parent_account_id" field.
func (u *AccountUpsert) ClearParentAccountID() *AccountUpsert {
u.SetNull(account.FieldParentAccountID)
return u
}
// SetQuotaDimension sets the "quota_dimension" field.
func (u *AccountUpsert) SetQuotaDimension(v account.QuotaDimension) *AccountUpsert {
u.Set(account.FieldQuotaDimension, v)
return u
}
// UpdateQuotaDimension sets the "quota_dimension" field to the value that was provided on create.
func (u *AccountUpsert) UpdateQuotaDimension() *AccountUpsert {
u.SetExcluded(account.FieldQuotaDimension)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
@@ -1874,6 +2015,41 @@ func (u *AccountUpsertOne) ClearSessionWindowStatus() *AccountUpsertOne {
})
}
// SetParentAccountID sets the "parent_account_id" field.
func (u *AccountUpsertOne) SetParentAccountID(v int64) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetParentAccountID(v)
})
}
// UpdateParentAccountID sets the "parent_account_id" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateParentAccountID() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateParentAccountID()
})
}
// ClearParentAccountID clears the value of the "parent_account_id" field.
func (u *AccountUpsertOne) ClearParentAccountID() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.ClearParentAccountID()
})
}
// SetQuotaDimension sets the "quota_dimension" field.
func (u *AccountUpsertOne) SetQuotaDimension(v account.QuotaDimension) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetQuotaDimension(v)
})
}
// UpdateQuotaDimension sets the "quota_dimension" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateQuotaDimension() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateQuotaDimension()
})
}
// Exec executes the query.
func (u *AccountUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
@@ -2624,6 +2800,41 @@ func (u *AccountUpsertBulk) ClearSessionWindowStatus() *AccountUpsertBulk {
})
}
// SetParentAccountID sets the "parent_account_id" field.
func (u *AccountUpsertBulk) SetParentAccountID(v int64) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetParentAccountID(v)
})
}
// UpdateParentAccountID sets the "parent_account_id" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateParentAccountID() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateParentAccountID()
})
}
// ClearParentAccountID clears the value of the "parent_account_id" field.
func (u *AccountUpsertBulk) ClearParentAccountID() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.ClearParentAccountID()
})
}
// SetQuotaDimension sets the "quota_dimension" field.
func (u *AccountUpsertBulk) SetQuotaDimension(v account.QuotaDimension) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetQuotaDimension(v)
})
}
// UpdateQuotaDimension sets the "quota_dimension" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateQuotaDimension() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateQuotaDimension()
})
}
// Exec executes the query.
func (u *AccountUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
+154 -1
View File
@@ -30,6 +30,8 @@ type AccountQuery struct {
predicates []predicate.Account
withGroups *GroupQuery
withProxy *ProxyQuery
withParent *AccountQuery
withChildren *AccountQuery
withUsageLogs *UsageLogQuery
withAccountGroups *AccountGroupQuery
modifiers []func(*sql.Selector)
@@ -113,6 +115,50 @@ func (_q *AccountQuery) QueryProxy() *ProxyQuery {
return query
}
// QueryParent chains the current query on the "parent" edge.
func (_q *AccountQuery) QueryParent() *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(account.Table, account.FieldID, selector),
sqlgraph.To(account.Table, account.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, account.ParentTable, account.ParentColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryChildren chains the current query on the "children" edge.
func (_q *AccountQuery) QueryChildren() *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(account.Table, account.FieldID, selector),
sqlgraph.To(account.Table, account.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, account.ChildrenTable, account.ChildrenColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryUsageLogs chains the current query on the "usage_logs" edge.
func (_q *AccountQuery) QueryUsageLogs() *UsageLogQuery {
query := (&UsageLogClient{config: _q.config}).Query()
@@ -351,6 +397,8 @@ func (_q *AccountQuery) Clone() *AccountQuery {
predicates: append([]predicate.Account{}, _q.predicates...),
withGroups: _q.withGroups.Clone(),
withProxy: _q.withProxy.Clone(),
withParent: _q.withParent.Clone(),
withChildren: _q.withChildren.Clone(),
withUsageLogs: _q.withUsageLogs.Clone(),
withAccountGroups: _q.withAccountGroups.Clone(),
// clone intermediate query.
@@ -381,6 +429,28 @@ func (_q *AccountQuery) WithProxy(opts ...func(*ProxyQuery)) *AccountQuery {
return _q
}
// WithParent tells the query-builder to eager-load the nodes that are connected to
// the "parent" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AccountQuery) WithParent(opts ...func(*AccountQuery)) *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withParent = query
return _q
}
// WithChildren tells the query-builder to eager-load the nodes that are connected to
// the "children" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AccountQuery) WithChildren(opts ...func(*AccountQuery)) *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withChildren = query
return _q
}
// WithUsageLogs tells the query-builder to eager-load the nodes that are connected to
// the "usage_logs" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AccountQuery) WithUsageLogs(opts ...func(*UsageLogQuery)) *AccountQuery {
@@ -481,9 +551,11 @@ func (_q *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco
var (
nodes = []*Account{}
_spec = _q.querySpec()
loadedTypes = [4]bool{
loadedTypes = [6]bool{
_q.withGroups != nil,
_q.withProxy != nil,
_q.withParent != nil,
_q.withChildren != nil,
_q.withUsageLogs != nil,
_q.withAccountGroups != nil,
}
@@ -522,6 +594,19 @@ func (_q *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco
return nil, err
}
}
if query := _q.withParent; query != nil {
if err := _q.loadParent(ctx, query, nodes, nil,
func(n *Account, e *Account) { n.Edges.Parent = e }); err != nil {
return nil, err
}
}
if query := _q.withChildren; query != nil {
if err := _q.loadChildren(ctx, query, nodes,
func(n *Account) { n.Edges.Children = []*Account{} },
func(n *Account, e *Account) { n.Edges.Children = append(n.Edges.Children, e) }); err != nil {
return nil, err
}
}
if query := _q.withUsageLogs; query != nil {
if err := _q.loadUsageLogs(ctx, query, nodes,
func(n *Account) { n.Edges.UsageLogs = []*UsageLog{} },
@@ -632,6 +717,71 @@ func (_q *AccountQuery) loadProxy(ctx context.Context, query *ProxyQuery, nodes
}
return nil
}
func (_q *AccountQuery) loadParent(ctx context.Context, query *AccountQuery, nodes []*Account, init func(*Account), assign func(*Account, *Account)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*Account)
for i := range nodes {
if nodes[i].ParentAccountID == nil {
continue
}
fk := *nodes[i].ParentAccountID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(account.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "parent_account_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *AccountQuery) loadChildren(ctx context.Context, query *AccountQuery, nodes []*Account, init func(*Account), assign func(*Account, *Account)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*Account)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(account.FieldParentAccountID)
}
query.Where(predicate.Account(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(account.ChildrenColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.ParentAccountID
if fk == nil {
return fmt.Errorf(`foreign-key "parent_account_id" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "parent_account_id" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *AccountQuery) loadUsageLogs(ctx context.Context, query *UsageLogQuery, nodes []*Account, init func(*Account), assign func(*Account, *UsageLog)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*Account)
@@ -724,6 +874,9 @@ func (_q *AccountQuery) querySpec() *sqlgraph.QuerySpec {
if _q.withProxy != nil {
_spec.Node.AddColumnOnce(account.FieldProxyID)
}
if _q.withParent != nil {
_spec.Node.AddColumnOnce(account.FieldParentAccountID)
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
+354
View File
@@ -530,6 +530,40 @@ func (_u *AccountUpdate) ClearSessionWindowStatus() *AccountUpdate {
return _u
}
// SetParentAccountID sets the "parent_account_id" field.
func (_u *AccountUpdate) SetParentAccountID(v int64) *AccountUpdate {
_u.mutation.SetParentAccountID(v)
return _u
}
// SetNillableParentAccountID sets the "parent_account_id" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableParentAccountID(v *int64) *AccountUpdate {
if v != nil {
_u.SetParentAccountID(*v)
}
return _u
}
// ClearParentAccountID clears the value of the "parent_account_id" field.
func (_u *AccountUpdate) ClearParentAccountID() *AccountUpdate {
_u.mutation.ClearParentAccountID()
return _u
}
// SetQuotaDimension sets the "quota_dimension" field.
func (_u *AccountUpdate) SetQuotaDimension(v account.QuotaDimension) *AccountUpdate {
_u.mutation.SetQuotaDimension(v)
return _u
}
// SetNillableQuotaDimension sets the "quota_dimension" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableQuotaDimension(v *account.QuotaDimension) *AccountUpdate {
if v != nil {
_u.SetQuotaDimension(*v)
}
return _u
}
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
func (_u *AccountUpdate) AddGroupIDs(ids ...int64) *AccountUpdate {
_u.mutation.AddGroupIDs(ids...)
@@ -550,6 +584,40 @@ func (_u *AccountUpdate) SetProxy(v *Proxy) *AccountUpdate {
return _u.SetProxyID(v.ID)
}
// SetParentID sets the "parent" edge to the Account entity by ID.
func (_u *AccountUpdate) SetParentID(id int64) *AccountUpdate {
_u.mutation.SetParentID(id)
return _u
}
// SetNillableParentID sets the "parent" edge to the Account entity by ID if the given value is not nil.
func (_u *AccountUpdate) SetNillableParentID(id *int64) *AccountUpdate {
if id != nil {
_u = _u.SetParentID(*id)
}
return _u
}
// SetParent sets the "parent" edge to the Account entity.
func (_u *AccountUpdate) SetParent(v *Account) *AccountUpdate {
return _u.SetParentID(v.ID)
}
// AddChildIDs adds the "children" edge to the Account entity by IDs.
func (_u *AccountUpdate) AddChildIDs(ids ...int64) *AccountUpdate {
_u.mutation.AddChildIDs(ids...)
return _u
}
// AddChildren adds the "children" edges to the Account entity.
func (_u *AccountUpdate) AddChildren(v ...*Account) *AccountUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddChildIDs(ids...)
}
// AddUsageLogIDs adds the "usage_logs" edge to the UsageLog entity by IDs.
func (_u *AccountUpdate) AddUsageLogIDs(ids ...int64) *AccountUpdate {
_u.mutation.AddUsageLogIDs(ids...)
@@ -597,6 +665,33 @@ func (_u *AccountUpdate) ClearProxy() *AccountUpdate {
return _u
}
// ClearParent clears the "parent" edge to the Account entity.
func (_u *AccountUpdate) ClearParent() *AccountUpdate {
_u.mutation.ClearParent()
return _u
}
// ClearChildren clears all "children" edges to the Account entity.
func (_u *AccountUpdate) ClearChildren() *AccountUpdate {
_u.mutation.ClearChildren()
return _u
}
// RemoveChildIDs removes the "children" edge to Account entities by IDs.
func (_u *AccountUpdate) RemoveChildIDs(ids ...int64) *AccountUpdate {
_u.mutation.RemoveChildIDs(ids...)
return _u
}
// RemoveChildren removes "children" edges to Account entities.
func (_u *AccountUpdate) RemoveChildren(v ...*Account) *AccountUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveChildIDs(ids...)
}
// ClearUsageLogs clears all "usage_logs" edges to the UsageLog entity.
func (_u *AccountUpdate) ClearUsageLogs() *AccountUpdate {
_u.mutation.ClearUsageLogs()
@@ -687,6 +782,11 @@ func (_u *AccountUpdate) check() error {
return &ValidationError{Name: "session_window_status", err: fmt.Errorf(`ent: validator failed for field "Account.session_window_status": %w`, err)}
}
}
if v, ok := _u.mutation.QuotaDimension(); ok {
if err := account.QuotaDimensionValidator(v); err != nil {
return &ValidationError{Name: "quota_dimension", err: fmt.Errorf(`ent: validator failed for field "Account.quota_dimension": %w`, err)}
}
}
return nil
}
@@ -843,6 +943,9 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.SessionWindowStatusCleared() {
_spec.ClearField(account.FieldSessionWindowStatus, field.TypeString)
}
if value, ok := _u.mutation.QuotaDimension(); ok {
_spec.SetField(account.FieldQuotaDimension, field.TypeEnum, value)
}
if _u.mutation.GroupsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
@@ -929,6 +1032,80 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ParentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: account.ParentTable,
Columns: []string{account.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ParentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: account.ParentTable,
Columns: []string{account.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ChildrenCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1493,6 +1670,40 @@ func (_u *AccountUpdateOne) ClearSessionWindowStatus() *AccountUpdateOne {
return _u
}
// SetParentAccountID sets the "parent_account_id" field.
func (_u *AccountUpdateOne) SetParentAccountID(v int64) *AccountUpdateOne {
_u.mutation.SetParentAccountID(v)
return _u
}
// SetNillableParentAccountID sets the "parent_account_id" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableParentAccountID(v *int64) *AccountUpdateOne {
if v != nil {
_u.SetParentAccountID(*v)
}
return _u
}
// ClearParentAccountID clears the value of the "parent_account_id" field.
func (_u *AccountUpdateOne) ClearParentAccountID() *AccountUpdateOne {
_u.mutation.ClearParentAccountID()
return _u
}
// SetQuotaDimension sets the "quota_dimension" field.
func (_u *AccountUpdateOne) SetQuotaDimension(v account.QuotaDimension) *AccountUpdateOne {
_u.mutation.SetQuotaDimension(v)
return _u
}
// SetNillableQuotaDimension sets the "quota_dimension" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableQuotaDimension(v *account.QuotaDimension) *AccountUpdateOne {
if v != nil {
_u.SetQuotaDimension(*v)
}
return _u
}
// AddGroupIDs adds the "groups" edge to the Group entity by IDs.
func (_u *AccountUpdateOne) AddGroupIDs(ids ...int64) *AccountUpdateOne {
_u.mutation.AddGroupIDs(ids...)
@@ -1513,6 +1724,40 @@ func (_u *AccountUpdateOne) SetProxy(v *Proxy) *AccountUpdateOne {
return _u.SetProxyID(v.ID)
}
// SetParentID sets the "parent" edge to the Account entity by ID.
func (_u *AccountUpdateOne) SetParentID(id int64) *AccountUpdateOne {
_u.mutation.SetParentID(id)
return _u
}
// SetNillableParentID sets the "parent" edge to the Account entity by ID if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableParentID(id *int64) *AccountUpdateOne {
if id != nil {
_u = _u.SetParentID(*id)
}
return _u
}
// SetParent sets the "parent" edge to the Account entity.
func (_u *AccountUpdateOne) SetParent(v *Account) *AccountUpdateOne {
return _u.SetParentID(v.ID)
}
// AddChildIDs adds the "children" edge to the Account entity by IDs.
func (_u *AccountUpdateOne) AddChildIDs(ids ...int64) *AccountUpdateOne {
_u.mutation.AddChildIDs(ids...)
return _u
}
// AddChildren adds the "children" edges to the Account entity.
func (_u *AccountUpdateOne) AddChildren(v ...*Account) *AccountUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddChildIDs(ids...)
}
// AddUsageLogIDs adds the "usage_logs" edge to the UsageLog entity by IDs.
func (_u *AccountUpdateOne) AddUsageLogIDs(ids ...int64) *AccountUpdateOne {
_u.mutation.AddUsageLogIDs(ids...)
@@ -1560,6 +1805,33 @@ func (_u *AccountUpdateOne) ClearProxy() *AccountUpdateOne {
return _u
}
// ClearParent clears the "parent" edge to the Account entity.
func (_u *AccountUpdateOne) ClearParent() *AccountUpdateOne {
_u.mutation.ClearParent()
return _u
}
// ClearChildren clears all "children" edges to the Account entity.
func (_u *AccountUpdateOne) ClearChildren() *AccountUpdateOne {
_u.mutation.ClearChildren()
return _u
}
// RemoveChildIDs removes the "children" edge to Account entities by IDs.
func (_u *AccountUpdateOne) RemoveChildIDs(ids ...int64) *AccountUpdateOne {
_u.mutation.RemoveChildIDs(ids...)
return _u
}
// RemoveChildren removes "children" edges to Account entities.
func (_u *AccountUpdateOne) RemoveChildren(v ...*Account) *AccountUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveChildIDs(ids...)
}
// ClearUsageLogs clears all "usage_logs" edges to the UsageLog entity.
func (_u *AccountUpdateOne) ClearUsageLogs() *AccountUpdateOne {
_u.mutation.ClearUsageLogs()
@@ -1663,6 +1935,11 @@ func (_u *AccountUpdateOne) check() error {
return &ValidationError{Name: "session_window_status", err: fmt.Errorf(`ent: validator failed for field "Account.session_window_status": %w`, err)}
}
}
if v, ok := _u.mutation.QuotaDimension(); ok {
if err := account.QuotaDimensionValidator(v); err != nil {
return &ValidationError{Name: "quota_dimension", err: fmt.Errorf(`ent: validator failed for field "Account.quota_dimension": %w`, err)}
}
}
return nil
}
@@ -1836,6 +2113,9 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if _u.mutation.SessionWindowStatusCleared() {
_spec.ClearField(account.FieldSessionWindowStatus, field.TypeString)
}
if value, ok := _u.mutation.QuotaDimension(); ok {
_spec.SetField(account.FieldQuotaDimension, field.TypeEnum, value)
}
if _u.mutation.GroupsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
@@ -1922,6 +2202,80 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ParentCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: account.ParentTable,
Columns: []string{account.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ParentIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: account.ParentTable,
Columns: []string{account.ParentColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ChildrenCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedChildrenIDs(); len(nodes) > 0 && !_u.mutation.ChildrenCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ChildrenIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: account.ChildrenTable,
Columns: []string{account.ChildrenColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
+158
View File
@@ -0,0 +1,158 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
)
// BatchImageEvent is the model entity for the BatchImageEvent schema.
type BatchImageEvent struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// JobID holds the value of the "job_id" field.
JobID string `json:"job_id,omitempty"`
// EventType holds the value of the "event_type" field.
EventType string `json:"event_type,omitempty"`
// Payload holds the value of the "payload" field.
Payload map[string]interface{} `json:"payload,omitempty"`
// EventHash holds the value of the "event_hash" field.
EventHash *string `json:"event_hash,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*BatchImageEvent) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case batchimageevent.FieldPayload:
values[i] = new([]byte)
case batchimageevent.FieldID:
values[i] = new(sql.NullInt64)
case batchimageevent.FieldJobID, batchimageevent.FieldEventType, batchimageevent.FieldEventHash:
values[i] = new(sql.NullString)
case batchimageevent.FieldCreatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the BatchImageEvent fields.
func (_m *BatchImageEvent) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case batchimageevent.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case batchimageevent.FieldJobID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field job_id", values[i])
} else if value.Valid {
_m.JobID = value.String
}
case batchimageevent.FieldEventType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field event_type", values[i])
} else if value.Valid {
_m.EventType = value.String
}
case batchimageevent.FieldPayload:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field payload", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Payload); err != nil {
return fmt.Errorf("unmarshal field payload: %w", err)
}
}
case batchimageevent.FieldEventHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field event_hash", values[i])
} else if value.Valid {
_m.EventHash = new(string)
*_m.EventHash = value.String
}
case batchimageevent.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the BatchImageEvent.
// This includes values selected through modifiers, order, etc.
func (_m *BatchImageEvent) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this BatchImageEvent.
// Note that you need to call BatchImageEvent.Unwrap() before calling this method if this BatchImageEvent
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *BatchImageEvent) Update() *BatchImageEventUpdateOne {
return NewBatchImageEventClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the BatchImageEvent entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *BatchImageEvent) Unwrap() *BatchImageEvent {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: BatchImageEvent is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *BatchImageEvent) String() string {
var builder strings.Builder
builder.WriteString("BatchImageEvent(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("job_id=")
builder.WriteString(_m.JobID)
builder.WriteString(", ")
builder.WriteString("event_type=")
builder.WriteString(_m.EventType)
builder.WriteString(", ")
builder.WriteString("payload=")
builder.WriteString(fmt.Sprintf("%v", _m.Payload))
builder.WriteString(", ")
if v := _m.EventHash; v != nil {
builder.WriteString("event_hash=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// BatchImageEvents is a parsable slice of BatchImageEvent.
type BatchImageEvents []*BatchImageEvent
@@ -0,0 +1,87 @@
// Code generated by ent, DO NOT EDIT.
package batchimageevent
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the batchimageevent type in the database.
Label = "batch_image_event"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldJobID holds the string denoting the job_id field in the database.
FieldJobID = "job_id"
// FieldEventType holds the string denoting the event_type field in the database.
FieldEventType = "event_type"
// FieldPayload holds the string denoting the payload field in the database.
FieldPayload = "payload"
// FieldEventHash holds the string denoting the event_hash field in the database.
FieldEventHash = "event_hash"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// Table holds the table name of the batchimageevent in the database.
Table = "batch_image_events"
)
// Columns holds all SQL columns for batchimageevent fields.
var Columns = []string{
FieldID,
FieldJobID,
FieldEventType,
FieldPayload,
FieldEventHash,
FieldCreatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// JobIDValidator is a validator for the "job_id" field. It is called by the builders before save.
JobIDValidator func(string) error
// EventTypeValidator is a validator for the "event_type" field. It is called by the builders before save.
EventTypeValidator func(string) error
// EventHashValidator is a validator for the "event_hash" field. It is called by the builders before save.
EventHashValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
)
// OrderOption defines the ordering options for the BatchImageEvent queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByJobID orders the results by the job_id field.
func ByJobID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldJobID, opts...).ToFunc()
}
// ByEventType orders the results by the event_type field.
func ByEventType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEventType, opts...).ToFunc()
}
// ByEventHash orders the results by the event_hash field.
func ByEventHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEventHash, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
+345
View File
@@ -0,0 +1,345 @@
// Code generated by ent, DO NOT EDIT.
package batchimageevent
import (
"time"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLTE(FieldID, id))
}
// JobID applies equality check predicate on the "job_id" field. It's identical to JobIDEQ.
func JobID(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldJobID, v))
}
// EventType applies equality check predicate on the "event_type" field. It's identical to EventTypeEQ.
func EventType(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldEventType, v))
}
// EventHash applies equality check predicate on the "event_hash" field. It's identical to EventHashEQ.
func EventHash(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldEventHash, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldCreatedAt, v))
}
// JobIDEQ applies the EQ predicate on the "job_id" field.
func JobIDEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldJobID, v))
}
// JobIDNEQ applies the NEQ predicate on the "job_id" field.
func JobIDNEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNEQ(FieldJobID, v))
}
// JobIDIn applies the In predicate on the "job_id" field.
func JobIDIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIn(FieldJobID, vs...))
}
// JobIDNotIn applies the NotIn predicate on the "job_id" field.
func JobIDNotIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotIn(FieldJobID, vs...))
}
// JobIDGT applies the GT predicate on the "job_id" field.
func JobIDGT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGT(FieldJobID, v))
}
// JobIDGTE applies the GTE predicate on the "job_id" field.
func JobIDGTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGTE(FieldJobID, v))
}
// JobIDLT applies the LT predicate on the "job_id" field.
func JobIDLT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLT(FieldJobID, v))
}
// JobIDLTE applies the LTE predicate on the "job_id" field.
func JobIDLTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLTE(FieldJobID, v))
}
// JobIDContains applies the Contains predicate on the "job_id" field.
func JobIDContains(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContains(FieldJobID, v))
}
// JobIDHasPrefix applies the HasPrefix predicate on the "job_id" field.
func JobIDHasPrefix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasPrefix(FieldJobID, v))
}
// JobIDHasSuffix applies the HasSuffix predicate on the "job_id" field.
func JobIDHasSuffix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasSuffix(FieldJobID, v))
}
// JobIDEqualFold applies the EqualFold predicate on the "job_id" field.
func JobIDEqualFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEqualFold(FieldJobID, v))
}
// JobIDContainsFold applies the ContainsFold predicate on the "job_id" field.
func JobIDContainsFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContainsFold(FieldJobID, v))
}
// EventTypeEQ applies the EQ predicate on the "event_type" field.
func EventTypeEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldEventType, v))
}
// EventTypeNEQ applies the NEQ predicate on the "event_type" field.
func EventTypeNEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNEQ(FieldEventType, v))
}
// EventTypeIn applies the In predicate on the "event_type" field.
func EventTypeIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIn(FieldEventType, vs...))
}
// EventTypeNotIn applies the NotIn predicate on the "event_type" field.
func EventTypeNotIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotIn(FieldEventType, vs...))
}
// EventTypeGT applies the GT predicate on the "event_type" field.
func EventTypeGT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGT(FieldEventType, v))
}
// EventTypeGTE applies the GTE predicate on the "event_type" field.
func EventTypeGTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGTE(FieldEventType, v))
}
// EventTypeLT applies the LT predicate on the "event_type" field.
func EventTypeLT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLT(FieldEventType, v))
}
// EventTypeLTE applies the LTE predicate on the "event_type" field.
func EventTypeLTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLTE(FieldEventType, v))
}
// EventTypeContains applies the Contains predicate on the "event_type" field.
func EventTypeContains(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContains(FieldEventType, v))
}
// EventTypeHasPrefix applies the HasPrefix predicate on the "event_type" field.
func EventTypeHasPrefix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasPrefix(FieldEventType, v))
}
// EventTypeHasSuffix applies the HasSuffix predicate on the "event_type" field.
func EventTypeHasSuffix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasSuffix(FieldEventType, v))
}
// EventTypeEqualFold applies the EqualFold predicate on the "event_type" field.
func EventTypeEqualFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEqualFold(FieldEventType, v))
}
// EventTypeContainsFold applies the ContainsFold predicate on the "event_type" field.
func EventTypeContainsFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContainsFold(FieldEventType, v))
}
// PayloadIsNil applies the IsNil predicate on the "payload" field.
func PayloadIsNil() predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIsNull(FieldPayload))
}
// PayloadNotNil applies the NotNil predicate on the "payload" field.
func PayloadNotNil() predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotNull(FieldPayload))
}
// EventHashEQ applies the EQ predicate on the "event_hash" field.
func EventHashEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldEventHash, v))
}
// EventHashNEQ applies the NEQ predicate on the "event_hash" field.
func EventHashNEQ(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNEQ(FieldEventHash, v))
}
// EventHashIn applies the In predicate on the "event_hash" field.
func EventHashIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIn(FieldEventHash, vs...))
}
// EventHashNotIn applies the NotIn predicate on the "event_hash" field.
func EventHashNotIn(vs ...string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotIn(FieldEventHash, vs...))
}
// EventHashGT applies the GT predicate on the "event_hash" field.
func EventHashGT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGT(FieldEventHash, v))
}
// EventHashGTE applies the GTE predicate on the "event_hash" field.
func EventHashGTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGTE(FieldEventHash, v))
}
// EventHashLT applies the LT predicate on the "event_hash" field.
func EventHashLT(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLT(FieldEventHash, v))
}
// EventHashLTE applies the LTE predicate on the "event_hash" field.
func EventHashLTE(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLTE(FieldEventHash, v))
}
// EventHashContains applies the Contains predicate on the "event_hash" field.
func EventHashContains(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContains(FieldEventHash, v))
}
// EventHashHasPrefix applies the HasPrefix predicate on the "event_hash" field.
func EventHashHasPrefix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasPrefix(FieldEventHash, v))
}
// EventHashHasSuffix applies the HasSuffix predicate on the "event_hash" field.
func EventHashHasSuffix(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldHasSuffix(FieldEventHash, v))
}
// EventHashIsNil applies the IsNil predicate on the "event_hash" field.
func EventHashIsNil() predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIsNull(FieldEventHash))
}
// EventHashNotNil applies the NotNil predicate on the "event_hash" field.
func EventHashNotNil() predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotNull(FieldEventHash))
}
// EventHashEqualFold applies the EqualFold predicate on the "event_hash" field.
func EventHashEqualFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEqualFold(FieldEventHash, v))
}
// EventHashContainsFold applies the ContainsFold predicate on the "event_hash" field.
func EventHashContainsFold(v string) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldContainsFold(FieldEventHash, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.FieldLTE(FieldCreatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.BatchImageEvent) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.BatchImageEvent) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.BatchImageEvent) predicate.BatchImageEvent {
return predicate.BatchImageEvent(sql.NotPredicates(p))
}
+714
View File
@@ -0,0 +1,714 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
)
// BatchImageEventCreate is the builder for creating a BatchImageEvent entity.
type BatchImageEventCreate struct {
config
mutation *BatchImageEventMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetJobID sets the "job_id" field.
func (_c *BatchImageEventCreate) SetJobID(v string) *BatchImageEventCreate {
_c.mutation.SetJobID(v)
return _c
}
// SetEventType sets the "event_type" field.
func (_c *BatchImageEventCreate) SetEventType(v string) *BatchImageEventCreate {
_c.mutation.SetEventType(v)
return _c
}
// SetPayload sets the "payload" field.
func (_c *BatchImageEventCreate) SetPayload(v map[string]interface{}) *BatchImageEventCreate {
_c.mutation.SetPayload(v)
return _c
}
// SetEventHash sets the "event_hash" field.
func (_c *BatchImageEventCreate) SetEventHash(v string) *BatchImageEventCreate {
_c.mutation.SetEventHash(v)
return _c
}
// SetNillableEventHash sets the "event_hash" field if the given value is not nil.
func (_c *BatchImageEventCreate) SetNillableEventHash(v *string) *BatchImageEventCreate {
if v != nil {
_c.SetEventHash(*v)
}
return _c
}
// SetCreatedAt sets the "created_at" field.
func (_c *BatchImageEventCreate) SetCreatedAt(v time.Time) *BatchImageEventCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *BatchImageEventCreate) SetNillableCreatedAt(v *time.Time) *BatchImageEventCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// Mutation returns the BatchImageEventMutation object of the builder.
func (_c *BatchImageEventCreate) Mutation() *BatchImageEventMutation {
return _c.mutation
}
// Save creates the BatchImageEvent in the database.
func (_c *BatchImageEventCreate) Save(ctx context.Context) (*BatchImageEvent, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *BatchImageEventCreate) SaveX(ctx context.Context) *BatchImageEvent {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *BatchImageEventCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *BatchImageEventCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_c *BatchImageEventCreate) defaults() {
if _, ok := _c.mutation.CreatedAt(); !ok {
v := batchimageevent.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *BatchImageEventCreate) check() error {
if _, ok := _c.mutation.JobID(); !ok {
return &ValidationError{Name: "job_id", err: errors.New(`ent: missing required field "BatchImageEvent.job_id"`)}
}
if v, ok := _c.mutation.JobID(); ok {
if err := batchimageevent.JobIDValidator(v); err != nil {
return &ValidationError{Name: "job_id", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.job_id": %w`, err)}
}
}
if _, ok := _c.mutation.EventType(); !ok {
return &ValidationError{Name: "event_type", err: errors.New(`ent: missing required field "BatchImageEvent.event_type"`)}
}
if v, ok := _c.mutation.EventType(); ok {
if err := batchimageevent.EventTypeValidator(v); err != nil {
return &ValidationError{Name: "event_type", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_type": %w`, err)}
}
}
if v, ok := _c.mutation.EventHash(); ok {
if err := batchimageevent.EventHashValidator(v); err != nil {
return &ValidationError{Name: "event_hash", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_hash": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "BatchImageEvent.created_at"`)}
}
return nil
}
func (_c *BatchImageEventCreate) sqlSave(ctx context.Context) (*BatchImageEvent, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
id := _spec.ID.Value.(int64)
_node.ID = int64(id)
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *BatchImageEventCreate) createSpec() (*BatchImageEvent, *sqlgraph.CreateSpec) {
var (
_node = &BatchImageEvent{config: _c.config}
_spec = sqlgraph.NewCreateSpec(batchimageevent.Table, sqlgraph.NewFieldSpec(batchimageevent.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.JobID(); ok {
_spec.SetField(batchimageevent.FieldJobID, field.TypeString, value)
_node.JobID = value
}
if value, ok := _c.mutation.EventType(); ok {
_spec.SetField(batchimageevent.FieldEventType, field.TypeString, value)
_node.EventType = value
}
if value, ok := _c.mutation.Payload(); ok {
_spec.SetField(batchimageevent.FieldPayload, field.TypeJSON, value)
_node.Payload = value
}
if value, ok := _c.mutation.EventHash(); ok {
_spec.SetField(batchimageevent.FieldEventHash, field.TypeString, value)
_node.EventHash = &value
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(batchimageevent.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.BatchImageEvent.Create().
// SetJobID(v).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.BatchImageEventUpsert) {
// SetJobID(v+v).
// }).
// Exec(ctx)
func (_c *BatchImageEventCreate) OnConflict(opts ...sql.ConflictOption) *BatchImageEventUpsertOne {
_c.conflict = opts
return &BatchImageEventUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *BatchImageEventCreate) OnConflictColumns(columns ...string) *BatchImageEventUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &BatchImageEventUpsertOne{
create: _c,
}
}
type (
// BatchImageEventUpsertOne is the builder for "upsert"-ing
// one BatchImageEvent node.
BatchImageEventUpsertOne struct {
create *BatchImageEventCreate
}
// BatchImageEventUpsert is the "OnConflict" setter.
BatchImageEventUpsert struct {
*sql.UpdateSet
}
)
// SetJobID sets the "job_id" field.
func (u *BatchImageEventUpsert) SetJobID(v string) *BatchImageEventUpsert {
u.Set(batchimageevent.FieldJobID, v)
return u
}
// UpdateJobID sets the "job_id" field to the value that was provided on create.
func (u *BatchImageEventUpsert) UpdateJobID() *BatchImageEventUpsert {
u.SetExcluded(batchimageevent.FieldJobID)
return u
}
// SetEventType sets the "event_type" field.
func (u *BatchImageEventUpsert) SetEventType(v string) *BatchImageEventUpsert {
u.Set(batchimageevent.FieldEventType, v)
return u
}
// UpdateEventType sets the "event_type" field to the value that was provided on create.
func (u *BatchImageEventUpsert) UpdateEventType() *BatchImageEventUpsert {
u.SetExcluded(batchimageevent.FieldEventType)
return u
}
// SetPayload sets the "payload" field.
func (u *BatchImageEventUpsert) SetPayload(v map[string]interface{}) *BatchImageEventUpsert {
u.Set(batchimageevent.FieldPayload, v)
return u
}
// UpdatePayload sets the "payload" field to the value that was provided on create.
func (u *BatchImageEventUpsert) UpdatePayload() *BatchImageEventUpsert {
u.SetExcluded(batchimageevent.FieldPayload)
return u
}
// ClearPayload clears the value of the "payload" field.
func (u *BatchImageEventUpsert) ClearPayload() *BatchImageEventUpsert {
u.SetNull(batchimageevent.FieldPayload)
return u
}
// SetEventHash sets the "event_hash" field.
func (u *BatchImageEventUpsert) SetEventHash(v string) *BatchImageEventUpsert {
u.Set(batchimageevent.FieldEventHash, v)
return u
}
// UpdateEventHash sets the "event_hash" field to the value that was provided on create.
func (u *BatchImageEventUpsert) UpdateEventHash() *BatchImageEventUpsert {
u.SetExcluded(batchimageevent.FieldEventHash)
return u
}
// ClearEventHash clears the value of the "event_hash" field.
func (u *BatchImageEventUpsert) ClearEventHash() *BatchImageEventUpsert {
u.SetNull(batchimageevent.FieldEventHash)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *BatchImageEventUpsertOne) UpdateNewValues() *BatchImageEventUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
if _, exists := u.create.mutation.CreatedAt(); exists {
s.SetIgnore(batchimageevent.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *BatchImageEventUpsertOne) Ignore() *BatchImageEventUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *BatchImageEventUpsertOne) DoNothing() *BatchImageEventUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the BatchImageEventCreate.OnConflict
// documentation for more info.
func (u *BatchImageEventUpsertOne) Update(set func(*BatchImageEventUpsert)) *BatchImageEventUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&BatchImageEventUpsert{UpdateSet: update})
}))
return u
}
// SetJobID sets the "job_id" field.
func (u *BatchImageEventUpsertOne) SetJobID(v string) *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetJobID(v)
})
}
// UpdateJobID sets the "job_id" field to the value that was provided on create.
func (u *BatchImageEventUpsertOne) UpdateJobID() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateJobID()
})
}
// SetEventType sets the "event_type" field.
func (u *BatchImageEventUpsertOne) SetEventType(v string) *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetEventType(v)
})
}
// UpdateEventType sets the "event_type" field to the value that was provided on create.
func (u *BatchImageEventUpsertOne) UpdateEventType() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateEventType()
})
}
// SetPayload sets the "payload" field.
func (u *BatchImageEventUpsertOne) SetPayload(v map[string]interface{}) *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetPayload(v)
})
}
// UpdatePayload sets the "payload" field to the value that was provided on create.
func (u *BatchImageEventUpsertOne) UpdatePayload() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdatePayload()
})
}
// ClearPayload clears the value of the "payload" field.
func (u *BatchImageEventUpsertOne) ClearPayload() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.ClearPayload()
})
}
// SetEventHash sets the "event_hash" field.
func (u *BatchImageEventUpsertOne) SetEventHash(v string) *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetEventHash(v)
})
}
// UpdateEventHash sets the "event_hash" field to the value that was provided on create.
func (u *BatchImageEventUpsertOne) UpdateEventHash() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateEventHash()
})
}
// ClearEventHash clears the value of the "event_hash" field.
func (u *BatchImageEventUpsertOne) ClearEventHash() *BatchImageEventUpsertOne {
return u.Update(func(s *BatchImageEventUpsert) {
s.ClearEventHash()
})
}
// Exec executes the query.
func (u *BatchImageEventUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for BatchImageEventCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *BatchImageEventUpsertOne) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
// Exec executes the UPSERT query and returns the inserted/updated ID.
func (u *BatchImageEventUpsertOne) ID(ctx context.Context) (id int64, err error) {
node, err := u.create.Save(ctx)
if err != nil {
return id, err
}
return node.ID, nil
}
// IDX is like ID, but panics if an error occurs.
func (u *BatchImageEventUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// BatchImageEventCreateBulk is the builder for creating many BatchImageEvent entities in bulk.
type BatchImageEventCreateBulk struct {
config
err error
builders []*BatchImageEventCreate
conflict []sql.ConflictOption
}
// Save creates the BatchImageEvent entities in the database.
func (_c *BatchImageEventCreateBulk) Save(ctx context.Context) ([]*BatchImageEvent, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*BatchImageEvent, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*BatchImageEventMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
spec.OnConflict = _c.conflict
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
if specs[i].ID.Value != nil {
id := specs[i].ID.Value.(int64)
nodes[i].ID = int64(id)
}
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *BatchImageEventCreateBulk) SaveX(ctx context.Context) []*BatchImageEvent {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *BatchImageEventCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *BatchImageEventCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.BatchImageEvent.CreateBulk(builders...).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.BatchImageEventUpsert) {
// SetJobID(v+v).
// }).
// Exec(ctx)
func (_c *BatchImageEventCreateBulk) OnConflict(opts ...sql.ConflictOption) *BatchImageEventUpsertBulk {
_c.conflict = opts
return &BatchImageEventUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *BatchImageEventCreateBulk) OnConflictColumns(columns ...string) *BatchImageEventUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &BatchImageEventUpsertBulk{
create: _c,
}
}
// BatchImageEventUpsertBulk is the builder for "upsert"-ing
// a bulk of BatchImageEvent nodes.
type BatchImageEventUpsertBulk struct {
create *BatchImageEventCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *BatchImageEventUpsertBulk) UpdateNewValues() *BatchImageEventUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
for _, b := range u.create.builders {
if _, exists := b.mutation.CreatedAt(); exists {
s.SetIgnore(batchimageevent.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.BatchImageEvent.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *BatchImageEventUpsertBulk) Ignore() *BatchImageEventUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *BatchImageEventUpsertBulk) DoNothing() *BatchImageEventUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the BatchImageEventCreateBulk.OnConflict
// documentation for more info.
func (u *BatchImageEventUpsertBulk) Update(set func(*BatchImageEventUpsert)) *BatchImageEventUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&BatchImageEventUpsert{UpdateSet: update})
}))
return u
}
// SetJobID sets the "job_id" field.
func (u *BatchImageEventUpsertBulk) SetJobID(v string) *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetJobID(v)
})
}
// UpdateJobID sets the "job_id" field to the value that was provided on create.
func (u *BatchImageEventUpsertBulk) UpdateJobID() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateJobID()
})
}
// SetEventType sets the "event_type" field.
func (u *BatchImageEventUpsertBulk) SetEventType(v string) *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetEventType(v)
})
}
// UpdateEventType sets the "event_type" field to the value that was provided on create.
func (u *BatchImageEventUpsertBulk) UpdateEventType() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateEventType()
})
}
// SetPayload sets the "payload" field.
func (u *BatchImageEventUpsertBulk) SetPayload(v map[string]interface{}) *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetPayload(v)
})
}
// UpdatePayload sets the "payload" field to the value that was provided on create.
func (u *BatchImageEventUpsertBulk) UpdatePayload() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdatePayload()
})
}
// ClearPayload clears the value of the "payload" field.
func (u *BatchImageEventUpsertBulk) ClearPayload() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.ClearPayload()
})
}
// SetEventHash sets the "event_hash" field.
func (u *BatchImageEventUpsertBulk) SetEventHash(v string) *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.SetEventHash(v)
})
}
// UpdateEventHash sets the "event_hash" field to the value that was provided on create.
func (u *BatchImageEventUpsertBulk) UpdateEventHash() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.UpdateEventHash()
})
}
// ClearEventHash clears the value of the "event_hash" field.
func (u *BatchImageEventUpsertBulk) ClearEventHash() *BatchImageEventUpsertBulk {
return u.Update(func(s *BatchImageEventUpsert) {
s.ClearEventHash()
})
}
// Exec executes the query.
func (u *BatchImageEventUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
return u.create.err
}
for i, b := range u.create.builders {
if len(b.conflict) != 0 {
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the BatchImageEventCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for BatchImageEventCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *BatchImageEventUpsertBulk) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
+88
View File
@@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageEventDelete is the builder for deleting a BatchImageEvent entity.
type BatchImageEventDelete struct {
config
hooks []Hook
mutation *BatchImageEventMutation
}
// Where appends a list predicates to the BatchImageEventDelete builder.
func (_d *BatchImageEventDelete) Where(ps ...predicate.BatchImageEvent) *BatchImageEventDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *BatchImageEventDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageEventDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *BatchImageEventDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(batchimageevent.Table, sqlgraph.NewFieldSpec(batchimageevent.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// BatchImageEventDeleteOne is the builder for deleting a single BatchImageEvent entity.
type BatchImageEventDeleteOne struct {
_d *BatchImageEventDelete
}
// Where appends a list predicates to the BatchImageEventDelete builder.
func (_d *BatchImageEventDeleteOne) Where(ps ...predicate.BatchImageEvent) *BatchImageEventDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *BatchImageEventDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{batchimageevent.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageEventDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}
+564
View File
@@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageEventQuery is the builder for querying BatchImageEvent entities.
type BatchImageEventQuery struct {
config
ctx *QueryContext
order []batchimageevent.OrderOption
inters []Interceptor
predicates []predicate.BatchImageEvent
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the BatchImageEventQuery builder.
func (_q *BatchImageEventQuery) Where(ps ...predicate.BatchImageEvent) *BatchImageEventQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *BatchImageEventQuery) Limit(limit int) *BatchImageEventQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *BatchImageEventQuery) Offset(offset int) *BatchImageEventQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *BatchImageEventQuery) Unique(unique bool) *BatchImageEventQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *BatchImageEventQuery) Order(o ...batchimageevent.OrderOption) *BatchImageEventQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first BatchImageEvent entity from the query.
// Returns a *NotFoundError when no BatchImageEvent was found.
func (_q *BatchImageEventQuery) First(ctx context.Context) (*BatchImageEvent, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{batchimageevent.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *BatchImageEventQuery) FirstX(ctx context.Context) *BatchImageEvent {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first BatchImageEvent ID from the query.
// Returns a *NotFoundError when no BatchImageEvent ID was found.
func (_q *BatchImageEventQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{batchimageevent.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *BatchImageEventQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single BatchImageEvent entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one BatchImageEvent entity is found.
// Returns a *NotFoundError when no BatchImageEvent entities are found.
func (_q *BatchImageEventQuery) Only(ctx context.Context) (*BatchImageEvent, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{batchimageevent.Label}
default:
return nil, &NotSingularError{batchimageevent.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *BatchImageEventQuery) OnlyX(ctx context.Context) *BatchImageEvent {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only BatchImageEvent ID in the query.
// Returns a *NotSingularError when more than one BatchImageEvent ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *BatchImageEventQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{batchimageevent.Label}
default:
err = &NotSingularError{batchimageevent.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *BatchImageEventQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of BatchImageEvents.
func (_q *BatchImageEventQuery) All(ctx context.Context) ([]*BatchImageEvent, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*BatchImageEvent, *BatchImageEventQuery]()
return withInterceptors[[]*BatchImageEvent](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *BatchImageEventQuery) AllX(ctx context.Context) []*BatchImageEvent {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of BatchImageEvent IDs.
func (_q *BatchImageEventQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(batchimageevent.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *BatchImageEventQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *BatchImageEventQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*BatchImageEventQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *BatchImageEventQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *BatchImageEventQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *BatchImageEventQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the BatchImageEventQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *BatchImageEventQuery) Clone() *BatchImageEventQuery {
if _q == nil {
return nil
}
return &BatchImageEventQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]batchimageevent.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.BatchImageEvent{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// JobID string `json:"job_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.BatchImageEvent.Query().
// GroupBy(batchimageevent.FieldJobID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *BatchImageEventQuery) GroupBy(field string, fields ...string) *BatchImageEventGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &BatchImageEventGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = batchimageevent.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// JobID string `json:"job_id,omitempty"`
// }
//
// client.BatchImageEvent.Query().
// Select(batchimageevent.FieldJobID).
// Scan(ctx, &v)
func (_q *BatchImageEventQuery) Select(fields ...string) *BatchImageEventSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &BatchImageEventSelect{BatchImageEventQuery: _q}
sbuild.label = batchimageevent.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a BatchImageEventSelect configured with the given aggregations.
func (_q *BatchImageEventQuery) Aggregate(fns ...AggregateFunc) *BatchImageEventSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *BatchImageEventQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !batchimageevent.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *BatchImageEventQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*BatchImageEvent, error) {
var (
nodes = []*BatchImageEvent{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*BatchImageEvent).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &BatchImageEvent{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *BatchImageEventQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *BatchImageEventQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(batchimageevent.Table, batchimageevent.Columns, sqlgraph.NewFieldSpec(batchimageevent.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, batchimageevent.FieldID)
for i := range fields {
if fields[i] != batchimageevent.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *BatchImageEventQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(batchimageevent.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = batchimageevent.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *BatchImageEventQuery) ForUpdate(opts ...sql.LockOption) *BatchImageEventQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *BatchImageEventQuery) ForShare(opts ...sql.LockOption) *BatchImageEventQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// BatchImageEventGroupBy is the group-by builder for BatchImageEvent entities.
type BatchImageEventGroupBy struct {
selector
build *BatchImageEventQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *BatchImageEventGroupBy) Aggregate(fns ...AggregateFunc) *BatchImageEventGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *BatchImageEventGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageEventQuery, *BatchImageEventGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *BatchImageEventGroupBy) sqlScan(ctx context.Context, root *BatchImageEventQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// BatchImageEventSelect is the builder for selecting fields of BatchImageEvent entities.
type BatchImageEventSelect struct {
*BatchImageEventQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *BatchImageEventSelect) Aggregate(fns ...AggregateFunc) *BatchImageEventSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *BatchImageEventSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageEventQuery, *BatchImageEventSelect](ctx, _s.BatchImageEventQuery, _s, _s.inters, v)
}
func (_s *BatchImageEventSelect) sqlScan(ctx context.Context, root *BatchImageEventQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
+377
View File
@@ -0,0 +1,377 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageEventUpdate is the builder for updating BatchImageEvent entities.
type BatchImageEventUpdate struct {
config
hooks []Hook
mutation *BatchImageEventMutation
}
// Where appends a list predicates to the BatchImageEventUpdate builder.
func (_u *BatchImageEventUpdate) Where(ps ...predicate.BatchImageEvent) *BatchImageEventUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetJobID sets the "job_id" field.
func (_u *BatchImageEventUpdate) SetJobID(v string) *BatchImageEventUpdate {
_u.mutation.SetJobID(v)
return _u
}
// SetNillableJobID sets the "job_id" field if the given value is not nil.
func (_u *BatchImageEventUpdate) SetNillableJobID(v *string) *BatchImageEventUpdate {
if v != nil {
_u.SetJobID(*v)
}
return _u
}
// SetEventType sets the "event_type" field.
func (_u *BatchImageEventUpdate) SetEventType(v string) *BatchImageEventUpdate {
_u.mutation.SetEventType(v)
return _u
}
// SetNillableEventType sets the "event_type" field if the given value is not nil.
func (_u *BatchImageEventUpdate) SetNillableEventType(v *string) *BatchImageEventUpdate {
if v != nil {
_u.SetEventType(*v)
}
return _u
}
// SetPayload sets the "payload" field.
func (_u *BatchImageEventUpdate) SetPayload(v map[string]interface{}) *BatchImageEventUpdate {
_u.mutation.SetPayload(v)
return _u
}
// ClearPayload clears the value of the "payload" field.
func (_u *BatchImageEventUpdate) ClearPayload() *BatchImageEventUpdate {
_u.mutation.ClearPayload()
return _u
}
// SetEventHash sets the "event_hash" field.
func (_u *BatchImageEventUpdate) SetEventHash(v string) *BatchImageEventUpdate {
_u.mutation.SetEventHash(v)
return _u
}
// SetNillableEventHash sets the "event_hash" field if the given value is not nil.
func (_u *BatchImageEventUpdate) SetNillableEventHash(v *string) *BatchImageEventUpdate {
if v != nil {
_u.SetEventHash(*v)
}
return _u
}
// ClearEventHash clears the value of the "event_hash" field.
func (_u *BatchImageEventUpdate) ClearEventHash() *BatchImageEventUpdate {
_u.mutation.ClearEventHash()
return _u
}
// Mutation returns the BatchImageEventMutation object of the builder.
func (_u *BatchImageEventUpdate) Mutation() *BatchImageEventMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *BatchImageEventUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *BatchImageEventUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *BatchImageEventUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *BatchImageEventUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *BatchImageEventUpdate) check() error {
if v, ok := _u.mutation.JobID(); ok {
if err := batchimageevent.JobIDValidator(v); err != nil {
return &ValidationError{Name: "job_id", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.job_id": %w`, err)}
}
}
if v, ok := _u.mutation.EventType(); ok {
if err := batchimageevent.EventTypeValidator(v); err != nil {
return &ValidationError{Name: "event_type", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_type": %w`, err)}
}
}
if v, ok := _u.mutation.EventHash(); ok {
if err := batchimageevent.EventHashValidator(v); err != nil {
return &ValidationError{Name: "event_hash", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_hash": %w`, err)}
}
}
return nil
}
func (_u *BatchImageEventUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(batchimageevent.Table, batchimageevent.Columns, sqlgraph.NewFieldSpec(batchimageevent.FieldID, field.TypeInt64))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.JobID(); ok {
_spec.SetField(batchimageevent.FieldJobID, field.TypeString, value)
}
if value, ok := _u.mutation.EventType(); ok {
_spec.SetField(batchimageevent.FieldEventType, field.TypeString, value)
}
if value, ok := _u.mutation.Payload(); ok {
_spec.SetField(batchimageevent.FieldPayload, field.TypeJSON, value)
}
if _u.mutation.PayloadCleared() {
_spec.ClearField(batchimageevent.FieldPayload, field.TypeJSON)
}
if value, ok := _u.mutation.EventHash(); ok {
_spec.SetField(batchimageevent.FieldEventHash, field.TypeString, value)
}
if _u.mutation.EventHashCleared() {
_spec.ClearField(batchimageevent.FieldEventHash, field.TypeString)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{batchimageevent.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// BatchImageEventUpdateOne is the builder for updating a single BatchImageEvent entity.
type BatchImageEventUpdateOne struct {
config
fields []string
hooks []Hook
mutation *BatchImageEventMutation
}
// SetJobID sets the "job_id" field.
func (_u *BatchImageEventUpdateOne) SetJobID(v string) *BatchImageEventUpdateOne {
_u.mutation.SetJobID(v)
return _u
}
// SetNillableJobID sets the "job_id" field if the given value is not nil.
func (_u *BatchImageEventUpdateOne) SetNillableJobID(v *string) *BatchImageEventUpdateOne {
if v != nil {
_u.SetJobID(*v)
}
return _u
}
// SetEventType sets the "event_type" field.
func (_u *BatchImageEventUpdateOne) SetEventType(v string) *BatchImageEventUpdateOne {
_u.mutation.SetEventType(v)
return _u
}
// SetNillableEventType sets the "event_type" field if the given value is not nil.
func (_u *BatchImageEventUpdateOne) SetNillableEventType(v *string) *BatchImageEventUpdateOne {
if v != nil {
_u.SetEventType(*v)
}
return _u
}
// SetPayload sets the "payload" field.
func (_u *BatchImageEventUpdateOne) SetPayload(v map[string]interface{}) *BatchImageEventUpdateOne {
_u.mutation.SetPayload(v)
return _u
}
// ClearPayload clears the value of the "payload" field.
func (_u *BatchImageEventUpdateOne) ClearPayload() *BatchImageEventUpdateOne {
_u.mutation.ClearPayload()
return _u
}
// SetEventHash sets the "event_hash" field.
func (_u *BatchImageEventUpdateOne) SetEventHash(v string) *BatchImageEventUpdateOne {
_u.mutation.SetEventHash(v)
return _u
}
// SetNillableEventHash sets the "event_hash" field if the given value is not nil.
func (_u *BatchImageEventUpdateOne) SetNillableEventHash(v *string) *BatchImageEventUpdateOne {
if v != nil {
_u.SetEventHash(*v)
}
return _u
}
// ClearEventHash clears the value of the "event_hash" field.
func (_u *BatchImageEventUpdateOne) ClearEventHash() *BatchImageEventUpdateOne {
_u.mutation.ClearEventHash()
return _u
}
// Mutation returns the BatchImageEventMutation object of the builder.
func (_u *BatchImageEventUpdateOne) Mutation() *BatchImageEventMutation {
return _u.mutation
}
// Where appends a list predicates to the BatchImageEventUpdate builder.
func (_u *BatchImageEventUpdateOne) Where(ps ...predicate.BatchImageEvent) *BatchImageEventUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *BatchImageEventUpdateOne) Select(field string, fields ...string) *BatchImageEventUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated BatchImageEvent entity.
func (_u *BatchImageEventUpdateOne) Save(ctx context.Context) (*BatchImageEvent, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *BatchImageEventUpdateOne) SaveX(ctx context.Context) *BatchImageEvent {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *BatchImageEventUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *BatchImageEventUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *BatchImageEventUpdateOne) check() error {
if v, ok := _u.mutation.JobID(); ok {
if err := batchimageevent.JobIDValidator(v); err != nil {
return &ValidationError{Name: "job_id", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.job_id": %w`, err)}
}
}
if v, ok := _u.mutation.EventType(); ok {
if err := batchimageevent.EventTypeValidator(v); err != nil {
return &ValidationError{Name: "event_type", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_type": %w`, err)}
}
}
if v, ok := _u.mutation.EventHash(); ok {
if err := batchimageevent.EventHashValidator(v); err != nil {
return &ValidationError{Name: "event_hash", err: fmt.Errorf(`ent: validator failed for field "BatchImageEvent.event_hash": %w`, err)}
}
}
return nil
}
func (_u *BatchImageEventUpdateOne) sqlSave(ctx context.Context) (_node *BatchImageEvent, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(batchimageevent.Table, batchimageevent.Columns, sqlgraph.NewFieldSpec(batchimageevent.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "BatchImageEvent.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, batchimageevent.FieldID)
for _, f := range fields {
if !batchimageevent.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != batchimageevent.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.JobID(); ok {
_spec.SetField(batchimageevent.FieldJobID, field.TypeString, value)
}
if value, ok := _u.mutation.EventType(); ok {
_spec.SetField(batchimageevent.FieldEventType, field.TypeString, value)
}
if value, ok := _u.mutation.Payload(); ok {
_spec.SetField(batchimageevent.FieldPayload, field.TypeJSON, value)
}
if _u.mutation.PayloadCleared() {
_spec.ClearField(batchimageevent.FieldPayload, field.TypeJSON)
}
if value, ok := _u.mutation.EventHash(); ok {
_spec.SetField(batchimageevent.FieldEventHash, field.TypeString, value)
}
if _u.mutation.EventHashCleared() {
_spec.ClearField(batchimageevent.FieldEventHash, field.TypeString)
}
_node = &BatchImageEvent{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{batchimageevent.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}
+320
View File
@@ -0,0 +1,320 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
)
// BatchImageItem is the model entity for the BatchImageItem schema.
type BatchImageItem struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// JobID holds the value of the "job_id" field.
JobID string `json:"job_id,omitempty"`
// CustomID holds the value of the "custom_id" field.
CustomID string `json:"custom_id,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// RequestHash holds the value of the "request_hash" field.
RequestHash *string `json:"request_hash,omitempty"`
// PromptPreview holds the value of the "prompt_preview" field.
PromptPreview *string `json:"prompt_preview,omitempty"`
// ProviderSourceObject holds the value of the "provider_source_object" field.
ProviderSourceObject *string `json:"provider_source_object,omitempty"`
// SourceLineNumber holds the value of the "source_line_number" field.
SourceLineNumber *int `json:"source_line_number,omitempty"`
// SourceByteOffset holds the value of the "source_byte_offset" field.
SourceByteOffset *int64 `json:"source_byte_offset,omitempty"`
// SourceByteLength holds the value of the "source_byte_length" field.
SourceByteLength *int64 `json:"source_byte_length,omitempty"`
// MimeType holds the value of the "mime_type" field.
MimeType *string `json:"mime_type,omitempty"`
// FileExtension holds the value of the "file_extension" field.
FileExtension *string `json:"file_extension,omitempty"`
// ImageCount holds the value of the "image_count" field.
ImageCount int `json:"image_count,omitempty"`
// ErrorCode holds the value of the "error_code" field.
ErrorCode *string `json:"error_code,omitempty"`
// ErrorMessage holds the value of the "error_message" field.
ErrorMessage *string `json:"error_message,omitempty"`
// BilledAmount holds the value of the "billed_amount" field.
BilledAmount *float64 `json:"billed_amount,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// IndexedAt holds the value of the "indexed_at" field.
IndexedAt *time.Time `json:"indexed_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*BatchImageItem) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case batchimageitem.FieldBilledAmount:
values[i] = new(sql.NullFloat64)
case batchimageitem.FieldID, batchimageitem.FieldSourceLineNumber, batchimageitem.FieldSourceByteOffset, batchimageitem.FieldSourceByteLength, batchimageitem.FieldImageCount:
values[i] = new(sql.NullInt64)
case batchimageitem.FieldJobID, batchimageitem.FieldCustomID, batchimageitem.FieldStatus, batchimageitem.FieldRequestHash, batchimageitem.FieldPromptPreview, batchimageitem.FieldProviderSourceObject, batchimageitem.FieldMimeType, batchimageitem.FieldFileExtension, batchimageitem.FieldErrorCode, batchimageitem.FieldErrorMessage:
values[i] = new(sql.NullString)
case batchimageitem.FieldCreatedAt, batchimageitem.FieldIndexedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the BatchImageItem fields.
func (_m *BatchImageItem) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case batchimageitem.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case batchimageitem.FieldJobID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field job_id", values[i])
} else if value.Valid {
_m.JobID = value.String
}
case batchimageitem.FieldCustomID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field custom_id", values[i])
} else if value.Valid {
_m.CustomID = value.String
}
case batchimageitem.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i])
} else if value.Valid {
_m.Status = value.String
}
case batchimageitem.FieldRequestHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field request_hash", values[i])
} else if value.Valid {
_m.RequestHash = new(string)
*_m.RequestHash = value.String
}
case batchimageitem.FieldPromptPreview:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field prompt_preview", values[i])
} else if value.Valid {
_m.PromptPreview = new(string)
*_m.PromptPreview = value.String
}
case batchimageitem.FieldProviderSourceObject:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_source_object", values[i])
} else if value.Valid {
_m.ProviderSourceObject = new(string)
*_m.ProviderSourceObject = value.String
}
case batchimageitem.FieldSourceLineNumber:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field source_line_number", values[i])
} else if value.Valid {
_m.SourceLineNumber = new(int)
*_m.SourceLineNumber = int(value.Int64)
}
case batchimageitem.FieldSourceByteOffset:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field source_byte_offset", values[i])
} else if value.Valid {
_m.SourceByteOffset = new(int64)
*_m.SourceByteOffset = value.Int64
}
case batchimageitem.FieldSourceByteLength:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field source_byte_length", values[i])
} else if value.Valid {
_m.SourceByteLength = new(int64)
*_m.SourceByteLength = value.Int64
}
case batchimageitem.FieldMimeType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field mime_type", values[i])
} else if value.Valid {
_m.MimeType = new(string)
*_m.MimeType = value.String
}
case batchimageitem.FieldFileExtension:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field file_extension", values[i])
} else if value.Valid {
_m.FileExtension = new(string)
*_m.FileExtension = value.String
}
case batchimageitem.FieldImageCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field image_count", values[i])
} else if value.Valid {
_m.ImageCount = int(value.Int64)
}
case batchimageitem.FieldErrorCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field error_code", values[i])
} else if value.Valid {
_m.ErrorCode = new(string)
*_m.ErrorCode = value.String
}
case batchimageitem.FieldErrorMessage:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field error_message", values[i])
} else if value.Valid {
_m.ErrorMessage = new(string)
*_m.ErrorMessage = value.String
}
case batchimageitem.FieldBilledAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field billed_amount", values[i])
} else if value.Valid {
_m.BilledAmount = new(float64)
*_m.BilledAmount = value.Float64
}
case batchimageitem.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case batchimageitem.FieldIndexedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field indexed_at", values[i])
} else if value.Valid {
_m.IndexedAt = new(time.Time)
*_m.IndexedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the BatchImageItem.
// This includes values selected through modifiers, order, etc.
func (_m *BatchImageItem) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this BatchImageItem.
// Note that you need to call BatchImageItem.Unwrap() before calling this method if this BatchImageItem
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *BatchImageItem) Update() *BatchImageItemUpdateOne {
return NewBatchImageItemClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the BatchImageItem entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *BatchImageItem) Unwrap() *BatchImageItem {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: BatchImageItem is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *BatchImageItem) String() string {
var builder strings.Builder
builder.WriteString("BatchImageItem(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("job_id=")
builder.WriteString(_m.JobID)
builder.WriteString(", ")
builder.WriteString("custom_id=")
builder.WriteString(_m.CustomID)
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
if v := _m.RequestHash; v != nil {
builder.WriteString("request_hash=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.PromptPreview; v != nil {
builder.WriteString("prompt_preview=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ProviderSourceObject; v != nil {
builder.WriteString("provider_source_object=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.SourceLineNumber; v != nil {
builder.WriteString("source_line_number=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SourceByteOffset; v != nil {
builder.WriteString("source_byte_offset=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SourceByteLength; v != nil {
builder.WriteString("source_byte_length=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.MimeType; v != nil {
builder.WriteString("mime_type=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.FileExtension; v != nil {
builder.WriteString("file_extension=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("image_count=")
builder.WriteString(fmt.Sprintf("%v", _m.ImageCount))
builder.WriteString(", ")
if v := _m.ErrorCode; v != nil {
builder.WriteString("error_code=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ErrorMessage; v != nil {
builder.WriteString("error_message=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.BilledAmount; v != nil {
builder.WriteString("billed_amount=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := _m.IndexedAt; v != nil {
builder.WriteString("indexed_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteByte(')')
return builder.String()
}
// BatchImageItems is a parsable slice of BatchImageItem.
type BatchImageItems []*BatchImageItem
@@ -0,0 +1,200 @@
// Code generated by ent, DO NOT EDIT.
package batchimageitem
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the batchimageitem type in the database.
Label = "batch_image_item"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldJobID holds the string denoting the job_id field in the database.
FieldJobID = "job_id"
// FieldCustomID holds the string denoting the custom_id field in the database.
FieldCustomID = "custom_id"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldRequestHash holds the string denoting the request_hash field in the database.
FieldRequestHash = "request_hash"
// FieldPromptPreview holds the string denoting the prompt_preview field in the database.
FieldPromptPreview = "prompt_preview"
// FieldProviderSourceObject holds the string denoting the provider_source_object field in the database.
FieldProviderSourceObject = "provider_source_object"
// FieldSourceLineNumber holds the string denoting the source_line_number field in the database.
FieldSourceLineNumber = "source_line_number"
// FieldSourceByteOffset holds the string denoting the source_byte_offset field in the database.
FieldSourceByteOffset = "source_byte_offset"
// FieldSourceByteLength holds the string denoting the source_byte_length field in the database.
FieldSourceByteLength = "source_byte_length"
// FieldMimeType holds the string denoting the mime_type field in the database.
FieldMimeType = "mime_type"
// FieldFileExtension holds the string denoting the file_extension field in the database.
FieldFileExtension = "file_extension"
// FieldImageCount holds the string denoting the image_count field in the database.
FieldImageCount = "image_count"
// FieldErrorCode holds the string denoting the error_code field in the database.
FieldErrorCode = "error_code"
// FieldErrorMessage holds the string denoting the error_message field in the database.
FieldErrorMessage = "error_message"
// FieldBilledAmount holds the string denoting the billed_amount field in the database.
FieldBilledAmount = "billed_amount"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldIndexedAt holds the string denoting the indexed_at field in the database.
FieldIndexedAt = "indexed_at"
// Table holds the table name of the batchimageitem in the database.
Table = "batch_image_items"
)
// Columns holds all SQL columns for batchimageitem fields.
var Columns = []string{
FieldID,
FieldJobID,
FieldCustomID,
FieldStatus,
FieldRequestHash,
FieldPromptPreview,
FieldProviderSourceObject,
FieldSourceLineNumber,
FieldSourceByteOffset,
FieldSourceByteLength,
FieldMimeType,
FieldFileExtension,
FieldImageCount,
FieldErrorCode,
FieldErrorMessage,
FieldBilledAmount,
FieldCreatedAt,
FieldIndexedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// JobIDValidator is a validator for the "job_id" field. It is called by the builders before save.
JobIDValidator func(string) error
// CustomIDValidator is a validator for the "custom_id" field. It is called by the builders before save.
CustomIDValidator func(string) error
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error
// RequestHashValidator is a validator for the "request_hash" field. It is called by the builders before save.
RequestHashValidator func(string) error
// ProviderSourceObjectValidator is a validator for the "provider_source_object" field. It is called by the builders before save.
ProviderSourceObjectValidator func(string) error
// MimeTypeValidator is a validator for the "mime_type" field. It is called by the builders before save.
MimeTypeValidator func(string) error
// FileExtensionValidator is a validator for the "file_extension" field. It is called by the builders before save.
FileExtensionValidator func(string) error
// DefaultImageCount holds the default value on creation for the "image_count" field.
DefaultImageCount int
// ErrorCodeValidator is a validator for the "error_code" field. It is called by the builders before save.
ErrorCodeValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
)
// OrderOption defines the ordering options for the BatchImageItem queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByJobID orders the results by the job_id field.
func ByJobID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldJobID, opts...).ToFunc()
}
// ByCustomID orders the results by the custom_id field.
func ByCustomID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCustomID, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByRequestHash orders the results by the request_hash field.
func ByRequestHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRequestHash, opts...).ToFunc()
}
// ByPromptPreview orders the results by the prompt_preview field.
func ByPromptPreview(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPromptPreview, opts...).ToFunc()
}
// ByProviderSourceObject orders the results by the provider_source_object field.
func ByProviderSourceObject(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderSourceObject, opts...).ToFunc()
}
// BySourceLineNumber orders the results by the source_line_number field.
func BySourceLineNumber(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSourceLineNumber, opts...).ToFunc()
}
// BySourceByteOffset orders the results by the source_byte_offset field.
func BySourceByteOffset(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSourceByteOffset, opts...).ToFunc()
}
// BySourceByteLength orders the results by the source_byte_length field.
func BySourceByteLength(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSourceByteLength, opts...).ToFunc()
}
// ByMimeType orders the results by the mime_type field.
func ByMimeType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMimeType, opts...).ToFunc()
}
// ByFileExtension orders the results by the file_extension field.
func ByFileExtension(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFileExtension, opts...).ToFunc()
}
// ByImageCount orders the results by the image_count field.
func ByImageCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldImageCount, opts...).ToFunc()
}
// ByErrorCode orders the results by the error_code field.
func ByErrorCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldErrorCode, opts...).ToFunc()
}
// ByErrorMessage orders the results by the error_message field.
func ByErrorMessage(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldErrorMessage, opts...).ToFunc()
}
// ByBilledAmount orders the results by the billed_amount field.
func ByBilledAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBilledAmount, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByIndexedAt orders the results by the indexed_at field.
func ByIndexedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIndexedAt, opts...).ToFunc()
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageItemDelete is the builder for deleting a BatchImageItem entity.
type BatchImageItemDelete struct {
config
hooks []Hook
mutation *BatchImageItemMutation
}
// Where appends a list predicates to the BatchImageItemDelete builder.
func (_d *BatchImageItemDelete) Where(ps ...predicate.BatchImageItem) *BatchImageItemDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *BatchImageItemDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageItemDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *BatchImageItemDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(batchimageitem.Table, sqlgraph.NewFieldSpec(batchimageitem.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// BatchImageItemDeleteOne is the builder for deleting a single BatchImageItem entity.
type BatchImageItemDeleteOne struct {
_d *BatchImageItemDelete
}
// Where appends a list predicates to the BatchImageItemDelete builder.
func (_d *BatchImageItemDeleteOne) Where(ps ...predicate.BatchImageItem) *BatchImageItemDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *BatchImageItemDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{batchimageitem.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageItemDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}
+564
View File
@@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageItemQuery is the builder for querying BatchImageItem entities.
type BatchImageItemQuery struct {
config
ctx *QueryContext
order []batchimageitem.OrderOption
inters []Interceptor
predicates []predicate.BatchImageItem
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the BatchImageItemQuery builder.
func (_q *BatchImageItemQuery) Where(ps ...predicate.BatchImageItem) *BatchImageItemQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *BatchImageItemQuery) Limit(limit int) *BatchImageItemQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *BatchImageItemQuery) Offset(offset int) *BatchImageItemQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *BatchImageItemQuery) Unique(unique bool) *BatchImageItemQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *BatchImageItemQuery) Order(o ...batchimageitem.OrderOption) *BatchImageItemQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first BatchImageItem entity from the query.
// Returns a *NotFoundError when no BatchImageItem was found.
func (_q *BatchImageItemQuery) First(ctx context.Context) (*BatchImageItem, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{batchimageitem.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *BatchImageItemQuery) FirstX(ctx context.Context) *BatchImageItem {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first BatchImageItem ID from the query.
// Returns a *NotFoundError when no BatchImageItem ID was found.
func (_q *BatchImageItemQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{batchimageitem.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *BatchImageItemQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single BatchImageItem entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one BatchImageItem entity is found.
// Returns a *NotFoundError when no BatchImageItem entities are found.
func (_q *BatchImageItemQuery) Only(ctx context.Context) (*BatchImageItem, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{batchimageitem.Label}
default:
return nil, &NotSingularError{batchimageitem.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *BatchImageItemQuery) OnlyX(ctx context.Context) *BatchImageItem {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only BatchImageItem ID in the query.
// Returns a *NotSingularError when more than one BatchImageItem ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *BatchImageItemQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{batchimageitem.Label}
default:
err = &NotSingularError{batchimageitem.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *BatchImageItemQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of BatchImageItems.
func (_q *BatchImageItemQuery) All(ctx context.Context) ([]*BatchImageItem, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*BatchImageItem, *BatchImageItemQuery]()
return withInterceptors[[]*BatchImageItem](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *BatchImageItemQuery) AllX(ctx context.Context) []*BatchImageItem {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of BatchImageItem IDs.
func (_q *BatchImageItemQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(batchimageitem.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *BatchImageItemQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *BatchImageItemQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*BatchImageItemQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *BatchImageItemQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *BatchImageItemQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *BatchImageItemQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the BatchImageItemQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *BatchImageItemQuery) Clone() *BatchImageItemQuery {
if _q == nil {
return nil
}
return &BatchImageItemQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]batchimageitem.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.BatchImageItem{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// JobID string `json:"job_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.BatchImageItem.Query().
// GroupBy(batchimageitem.FieldJobID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *BatchImageItemQuery) GroupBy(field string, fields ...string) *BatchImageItemGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &BatchImageItemGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = batchimageitem.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// JobID string `json:"job_id,omitempty"`
// }
//
// client.BatchImageItem.Query().
// Select(batchimageitem.FieldJobID).
// Scan(ctx, &v)
func (_q *BatchImageItemQuery) Select(fields ...string) *BatchImageItemSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &BatchImageItemSelect{BatchImageItemQuery: _q}
sbuild.label = batchimageitem.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a BatchImageItemSelect configured with the given aggregations.
func (_q *BatchImageItemQuery) Aggregate(fns ...AggregateFunc) *BatchImageItemSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *BatchImageItemQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !batchimageitem.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *BatchImageItemQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*BatchImageItem, error) {
var (
nodes = []*BatchImageItem{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*BatchImageItem).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &BatchImageItem{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *BatchImageItemQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *BatchImageItemQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(batchimageitem.Table, batchimageitem.Columns, sqlgraph.NewFieldSpec(batchimageitem.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, batchimageitem.FieldID)
for i := range fields {
if fields[i] != batchimageitem.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *BatchImageItemQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(batchimageitem.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = batchimageitem.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *BatchImageItemQuery) ForUpdate(opts ...sql.LockOption) *BatchImageItemQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *BatchImageItemQuery) ForShare(opts ...sql.LockOption) *BatchImageItemQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// BatchImageItemGroupBy is the group-by builder for BatchImageItem entities.
type BatchImageItemGroupBy struct {
selector
build *BatchImageItemQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *BatchImageItemGroupBy) Aggregate(fns ...AggregateFunc) *BatchImageItemGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *BatchImageItemGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageItemQuery, *BatchImageItemGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *BatchImageItemGroupBy) sqlScan(ctx context.Context, root *BatchImageItemQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// BatchImageItemSelect is the builder for selecting fields of BatchImageItem entities.
type BatchImageItemSelect struct {
*BatchImageItemQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *BatchImageItemSelect) Aggregate(fns ...AggregateFunc) *BatchImageItemSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *BatchImageItemSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageItemQuery, *BatchImageItemSelect](ctx, _s.BatchImageItemQuery, _s, _s.inters, v)
}
func (_s *BatchImageItemSelect) sqlScan(ctx context.Context, root *BatchImageItemQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
File diff suppressed because it is too large Load Diff
+609
View File
@@ -0,0 +1,609 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
)
// BatchImageJob is the model entity for the BatchImageJob schema.
type BatchImageJob struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// BatchID holds the value of the "batch_id" field.
BatchID string `json:"batch_id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// APIKeyID holds the value of the "api_key_id" field.
APIKeyID *int64 `json:"api_key_id,omitempty"`
// AccountID holds the value of the "account_id" field.
AccountID *int64 `json:"account_id,omitempty"`
// Provider holds the value of the "provider" field.
Provider string `json:"provider,omitempty"`
// Model holds the value of the "model" field.
Model string `json:"model,omitempty"`
// TaskName holds the value of the "task_name" field.
TaskName string `json:"task_name,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// ProviderJobName holds the value of the "provider_job_name" field.
ProviderJobName *string `json:"provider_job_name,omitempty"`
// ProviderInputRef holds the value of the "provider_input_ref" field.
ProviderInputRef *string `json:"provider_input_ref,omitempty"`
// ProviderOutputRef holds the value of the "provider_output_ref" field.
ProviderOutputRef *string `json:"provider_output_ref,omitempty"`
// GcsInputURI holds the value of the "gcs_input_uri" field.
GcsInputURI *string `json:"gcs_input_uri,omitempty"`
// GcsOutputURI holds the value of the "gcs_output_uri" field.
GcsOutputURI *string `json:"gcs_output_uri,omitempty"`
// ItemCount holds the value of the "item_count" field.
ItemCount int `json:"item_count,omitempty"`
// SuccessCount holds the value of the "success_count" field.
SuccessCount int `json:"success_count,omitempty"`
// FailCount holds the value of the "fail_count" field.
FailCount int `json:"fail_count,omitempty"`
// CancelledCount holds the value of the "cancelled_count" field.
CancelledCount int `json:"cancelled_count,omitempty"`
// EstimatedCost holds the value of the "estimated_cost" field.
EstimatedCost float64 `json:"estimated_cost,omitempty"`
// HoldAmount holds the value of the "hold_amount" field.
HoldAmount *float64 `json:"hold_amount,omitempty"`
// ActualCost holds the value of the "actual_cost" field.
ActualCost *float64 `json:"actual_cost,omitempty"`
// Currency holds the value of the "currency" field.
Currency string `json:"currency,omitempty"`
// HoldID holds the value of the "hold_id" field.
HoldID *string `json:"hold_id,omitempty"`
// IdempotencyKey holds the value of the "idempotency_key" field.
IdempotencyKey *string `json:"idempotency_key,omitempty"`
// RequestHash holds the value of the "request_hash" field.
RequestHash *string `json:"request_hash,omitempty"`
// ManifestHash holds the value of the "manifest_hash" field.
ManifestHash *string `json:"manifest_hash,omitempty"`
// RetryCount holds the value of the "retry_count" field.
RetryCount int `json:"retry_count,omitempty"`
// Version holds the value of the "version" field.
Version int `json:"version,omitempty"`
// OutputExpiresAt holds the value of the "output_expires_at" field.
OutputExpiresAt *time.Time `json:"output_expires_at,omitempty"`
// InputDeletedAt holds the value of the "input_deleted_at" field.
InputDeletedAt *time.Time `json:"input_deleted_at,omitempty"`
// OutputDeletedAt holds the value of the "output_deleted_at" field.
OutputDeletedAt *time.Time `json:"output_deleted_at,omitempty"`
// DownloadedAt holds the value of the "downloaded_at" field.
DownloadedAt *time.Time `json:"downloaded_at,omitempty"`
// UserDeletedAt holds the value of the "user_deleted_at" field.
UserDeletedAt *time.Time `json:"user_deleted_at,omitempty"`
// LastErrorCode holds the value of the "last_error_code" field.
LastErrorCode *string `json:"last_error_code,omitempty"`
// LastErrorMessage holds the value of the "last_error_message" field.
LastErrorMessage *string `json:"last_error_message,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// SubmittedAt holds the value of the "submitted_at" field.
SubmittedAt *time.Time `json:"submitted_at,omitempty"`
// StartedAt holds the value of the "started_at" field.
StartedAt *time.Time `json:"started_at,omitempty"`
// FinishedAt holds the value of the "finished_at" field.
FinishedAt *time.Time `json:"finished_at,omitempty"`
// SettledAt holds the value of the "settled_at" field.
SettledAt *time.Time `json:"settled_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*BatchImageJob) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case batchimagejob.FieldEstimatedCost, batchimagejob.FieldHoldAmount, batchimagejob.FieldActualCost:
values[i] = new(sql.NullFloat64)
case batchimagejob.FieldID, batchimagejob.FieldUserID, batchimagejob.FieldAPIKeyID, batchimagejob.FieldAccountID, batchimagejob.FieldItemCount, batchimagejob.FieldSuccessCount, batchimagejob.FieldFailCount, batchimagejob.FieldCancelledCount, batchimagejob.FieldRetryCount, batchimagejob.FieldVersion:
values[i] = new(sql.NullInt64)
case batchimagejob.FieldBatchID, batchimagejob.FieldProvider, batchimagejob.FieldModel, batchimagejob.FieldTaskName, batchimagejob.FieldStatus, batchimagejob.FieldProviderJobName, batchimagejob.FieldProviderInputRef, batchimagejob.FieldProviderOutputRef, batchimagejob.FieldGcsInputURI, batchimagejob.FieldGcsOutputURI, batchimagejob.FieldCurrency, batchimagejob.FieldHoldID, batchimagejob.FieldIdempotencyKey, batchimagejob.FieldRequestHash, batchimagejob.FieldManifestHash, batchimagejob.FieldLastErrorCode, batchimagejob.FieldLastErrorMessage:
values[i] = new(sql.NullString)
case batchimagejob.FieldOutputExpiresAt, batchimagejob.FieldInputDeletedAt, batchimagejob.FieldOutputDeletedAt, batchimagejob.FieldDownloadedAt, batchimagejob.FieldUserDeletedAt, batchimagejob.FieldCreatedAt, batchimagejob.FieldUpdatedAt, batchimagejob.FieldSubmittedAt, batchimagejob.FieldStartedAt, batchimagejob.FieldFinishedAt, batchimagejob.FieldSettledAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the BatchImageJob fields.
func (_m *BatchImageJob) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case batchimagejob.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case batchimagejob.FieldBatchID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field batch_id", values[i])
} else if value.Valid {
_m.BatchID = value.String
}
case batchimagejob.FieldUserID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value.Valid {
_m.UserID = value.Int64
}
case batchimagejob.FieldAPIKeyID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field api_key_id", values[i])
} else if value.Valid {
_m.APIKeyID = new(int64)
*_m.APIKeyID = value.Int64
}
case batchimagejob.FieldAccountID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field account_id", values[i])
} else if value.Valid {
_m.AccountID = new(int64)
*_m.AccountID = value.Int64
}
case batchimagejob.FieldProvider:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider", values[i])
} else if value.Valid {
_m.Provider = value.String
}
case batchimagejob.FieldModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field model", values[i])
} else if value.Valid {
_m.Model = value.String
}
case batchimagejob.FieldTaskName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field task_name", values[i])
} else if value.Valid {
_m.TaskName = value.String
}
case batchimagejob.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i])
} else if value.Valid {
_m.Status = value.String
}
case batchimagejob.FieldProviderJobName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_job_name", values[i])
} else if value.Valid {
_m.ProviderJobName = new(string)
*_m.ProviderJobName = value.String
}
case batchimagejob.FieldProviderInputRef:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_input_ref", values[i])
} else if value.Valid {
_m.ProviderInputRef = new(string)
*_m.ProviderInputRef = value.String
}
case batchimagejob.FieldProviderOutputRef:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_output_ref", values[i])
} else if value.Valid {
_m.ProviderOutputRef = new(string)
*_m.ProviderOutputRef = value.String
}
case batchimagejob.FieldGcsInputURI:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field gcs_input_uri", values[i])
} else if value.Valid {
_m.GcsInputURI = new(string)
*_m.GcsInputURI = value.String
}
case batchimagejob.FieldGcsOutputURI:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field gcs_output_uri", values[i])
} else if value.Valid {
_m.GcsOutputURI = new(string)
*_m.GcsOutputURI = value.String
}
case batchimagejob.FieldItemCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field item_count", values[i])
} else if value.Valid {
_m.ItemCount = int(value.Int64)
}
case batchimagejob.FieldSuccessCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field success_count", values[i])
} else if value.Valid {
_m.SuccessCount = int(value.Int64)
}
case batchimagejob.FieldFailCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field fail_count", values[i])
} else if value.Valid {
_m.FailCount = int(value.Int64)
}
case batchimagejob.FieldCancelledCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field cancelled_count", values[i])
} else if value.Valid {
_m.CancelledCount = int(value.Int64)
}
case batchimagejob.FieldEstimatedCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field estimated_cost", values[i])
} else if value.Valid {
_m.EstimatedCost = value.Float64
}
case batchimagejob.FieldHoldAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field hold_amount", values[i])
} else if value.Valid {
_m.HoldAmount = new(float64)
*_m.HoldAmount = value.Float64
}
case batchimagejob.FieldActualCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field actual_cost", values[i])
} else if value.Valid {
_m.ActualCost = new(float64)
*_m.ActualCost = value.Float64
}
case batchimagejob.FieldCurrency:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field currency", values[i])
} else if value.Valid {
_m.Currency = value.String
}
case batchimagejob.FieldHoldID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field hold_id", values[i])
} else if value.Valid {
_m.HoldID = new(string)
*_m.HoldID = value.String
}
case batchimagejob.FieldIdempotencyKey:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field idempotency_key", values[i])
} else if value.Valid {
_m.IdempotencyKey = new(string)
*_m.IdempotencyKey = value.String
}
case batchimagejob.FieldRequestHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field request_hash", values[i])
} else if value.Valid {
_m.RequestHash = new(string)
*_m.RequestHash = value.String
}
case batchimagejob.FieldManifestHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field manifest_hash", values[i])
} else if value.Valid {
_m.ManifestHash = new(string)
*_m.ManifestHash = value.String
}
case batchimagejob.FieldRetryCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field retry_count", values[i])
} else if value.Valid {
_m.RetryCount = int(value.Int64)
}
case batchimagejob.FieldVersion:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field version", values[i])
} else if value.Valid {
_m.Version = int(value.Int64)
}
case batchimagejob.FieldOutputExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field output_expires_at", values[i])
} else if value.Valid {
_m.OutputExpiresAt = new(time.Time)
*_m.OutputExpiresAt = value.Time
}
case batchimagejob.FieldInputDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field input_deleted_at", values[i])
} else if value.Valid {
_m.InputDeletedAt = new(time.Time)
*_m.InputDeletedAt = value.Time
}
case batchimagejob.FieldOutputDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field output_deleted_at", values[i])
} else if value.Valid {
_m.OutputDeletedAt = new(time.Time)
*_m.OutputDeletedAt = value.Time
}
case batchimagejob.FieldDownloadedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field downloaded_at", values[i])
} else if value.Valid {
_m.DownloadedAt = new(time.Time)
*_m.DownloadedAt = value.Time
}
case batchimagejob.FieldUserDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field user_deleted_at", values[i])
} else if value.Valid {
_m.UserDeletedAt = new(time.Time)
*_m.UserDeletedAt = value.Time
}
case batchimagejob.FieldLastErrorCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field last_error_code", values[i])
} else if value.Valid {
_m.LastErrorCode = new(string)
*_m.LastErrorCode = value.String
}
case batchimagejob.FieldLastErrorMessage:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field last_error_message", values[i])
} else if value.Valid {
_m.LastErrorMessage = new(string)
*_m.LastErrorMessage = value.String
}
case batchimagejob.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case batchimagejob.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
_m.UpdatedAt = value.Time
}
case batchimagejob.FieldSubmittedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field submitted_at", values[i])
} else if value.Valid {
_m.SubmittedAt = new(time.Time)
*_m.SubmittedAt = value.Time
}
case batchimagejob.FieldStartedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field started_at", values[i])
} else if value.Valid {
_m.StartedAt = new(time.Time)
*_m.StartedAt = value.Time
}
case batchimagejob.FieldFinishedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field finished_at", values[i])
} else if value.Valid {
_m.FinishedAt = new(time.Time)
*_m.FinishedAt = value.Time
}
case batchimagejob.FieldSettledAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field settled_at", values[i])
} else if value.Valid {
_m.SettledAt = new(time.Time)
*_m.SettledAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the BatchImageJob.
// This includes values selected through modifiers, order, etc.
func (_m *BatchImageJob) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this BatchImageJob.
// Note that you need to call BatchImageJob.Unwrap() before calling this method if this BatchImageJob
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *BatchImageJob) Update() *BatchImageJobUpdateOne {
return NewBatchImageJobClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the BatchImageJob entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *BatchImageJob) Unwrap() *BatchImageJob {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: BatchImageJob is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *BatchImageJob) String() string {
var builder strings.Builder
builder.WriteString("BatchImageJob(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("batch_id=")
builder.WriteString(_m.BatchID)
builder.WriteString(", ")
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
if v := _m.APIKeyID; v != nil {
builder.WriteString("api_key_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.AccountID; v != nil {
builder.WriteString("account_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("provider=")
builder.WriteString(_m.Provider)
builder.WriteString(", ")
builder.WriteString("model=")
builder.WriteString(_m.Model)
builder.WriteString(", ")
builder.WriteString("task_name=")
builder.WriteString(_m.TaskName)
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
if v := _m.ProviderJobName; v != nil {
builder.WriteString("provider_job_name=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ProviderInputRef; v != nil {
builder.WriteString("provider_input_ref=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ProviderOutputRef; v != nil {
builder.WriteString("provider_output_ref=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.GcsInputURI; v != nil {
builder.WriteString("gcs_input_uri=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.GcsOutputURI; v != nil {
builder.WriteString("gcs_output_uri=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("item_count=")
builder.WriteString(fmt.Sprintf("%v", _m.ItemCount))
builder.WriteString(", ")
builder.WriteString("success_count=")
builder.WriteString(fmt.Sprintf("%v", _m.SuccessCount))
builder.WriteString(", ")
builder.WriteString("fail_count=")
builder.WriteString(fmt.Sprintf("%v", _m.FailCount))
builder.WriteString(", ")
builder.WriteString("cancelled_count=")
builder.WriteString(fmt.Sprintf("%v", _m.CancelledCount))
builder.WriteString(", ")
builder.WriteString("estimated_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.EstimatedCost))
builder.WriteString(", ")
if v := _m.HoldAmount; v != nil {
builder.WriteString("hold_amount=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.ActualCost; v != nil {
builder.WriteString("actual_cost=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("currency=")
builder.WriteString(_m.Currency)
builder.WriteString(", ")
if v := _m.HoldID; v != nil {
builder.WriteString("hold_id=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.IdempotencyKey; v != nil {
builder.WriteString("idempotency_key=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.RequestHash; v != nil {
builder.WriteString("request_hash=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ManifestHash; v != nil {
builder.WriteString("manifest_hash=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("retry_count=")
builder.WriteString(fmt.Sprintf("%v", _m.RetryCount))
builder.WriteString(", ")
builder.WriteString("version=")
builder.WriteString(fmt.Sprintf("%v", _m.Version))
builder.WriteString(", ")
if v := _m.OutputExpiresAt; v != nil {
builder.WriteString("output_expires_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.InputDeletedAt; v != nil {
builder.WriteString("input_deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.OutputDeletedAt; v != nil {
builder.WriteString("output_deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.DownloadedAt; v != nil {
builder.WriteString("downloaded_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.UserDeletedAt; v != nil {
builder.WriteString("user_deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.LastErrorCode; v != nil {
builder.WriteString("last_error_code=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.LastErrorMessage; v != nil {
builder.WriteString("last_error_message=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := _m.SubmittedAt; v != nil {
builder.WriteString("submitted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.StartedAt; v != nil {
builder.WriteString("started_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.FinishedAt; v != nil {
builder.WriteString("finished_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.SettledAt; v != nil {
builder.WriteString("settled_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteByte(')')
return builder.String()
}
// BatchImageJobs is a parsable slice of BatchImageJob.
type BatchImageJobs []*BatchImageJob
+420
View File
@@ -0,0 +1,420 @@
// Code generated by ent, DO NOT EDIT.
package batchimagejob
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the batchimagejob type in the database.
Label = "batch_image_job"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldBatchID holds the string denoting the batch_id field in the database.
FieldBatchID = "batch_id"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldAPIKeyID holds the string denoting the api_key_id field in the database.
FieldAPIKeyID = "api_key_id"
// FieldAccountID holds the string denoting the account_id field in the database.
FieldAccountID = "account_id"
// FieldProvider holds the string denoting the provider field in the database.
FieldProvider = "provider"
// FieldModel holds the string denoting the model field in the database.
FieldModel = "model"
// FieldTaskName holds the string denoting the task_name field in the database.
FieldTaskName = "task_name"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldProviderJobName holds the string denoting the provider_job_name field in the database.
FieldProviderJobName = "provider_job_name"
// FieldProviderInputRef holds the string denoting the provider_input_ref field in the database.
FieldProviderInputRef = "provider_input_ref"
// FieldProviderOutputRef holds the string denoting the provider_output_ref field in the database.
FieldProviderOutputRef = "provider_output_ref"
// FieldGcsInputURI holds the string denoting the gcs_input_uri field in the database.
FieldGcsInputURI = "gcs_input_uri"
// FieldGcsOutputURI holds the string denoting the gcs_output_uri field in the database.
FieldGcsOutputURI = "gcs_output_uri"
// FieldItemCount holds the string denoting the item_count field in the database.
FieldItemCount = "item_count"
// FieldSuccessCount holds the string denoting the success_count field in the database.
FieldSuccessCount = "success_count"
// FieldFailCount holds the string denoting the fail_count field in the database.
FieldFailCount = "fail_count"
// FieldCancelledCount holds the string denoting the cancelled_count field in the database.
FieldCancelledCount = "cancelled_count"
// FieldEstimatedCost holds the string denoting the estimated_cost field in the database.
FieldEstimatedCost = "estimated_cost"
// FieldHoldAmount holds the string denoting the hold_amount field in the database.
FieldHoldAmount = "hold_amount"
// FieldActualCost holds the string denoting the actual_cost field in the database.
FieldActualCost = "actual_cost"
// FieldCurrency holds the string denoting the currency field in the database.
FieldCurrency = "currency"
// FieldHoldID holds the string denoting the hold_id field in the database.
FieldHoldID = "hold_id"
// FieldIdempotencyKey holds the string denoting the idempotency_key field in the database.
FieldIdempotencyKey = "idempotency_key"
// FieldRequestHash holds the string denoting the request_hash field in the database.
FieldRequestHash = "request_hash"
// FieldManifestHash holds the string denoting the manifest_hash field in the database.
FieldManifestHash = "manifest_hash"
// FieldRetryCount holds the string denoting the retry_count field in the database.
FieldRetryCount = "retry_count"
// FieldVersion holds the string denoting the version field in the database.
FieldVersion = "version"
// FieldOutputExpiresAt holds the string denoting the output_expires_at field in the database.
FieldOutputExpiresAt = "output_expires_at"
// FieldInputDeletedAt holds the string denoting the input_deleted_at field in the database.
FieldInputDeletedAt = "input_deleted_at"
// FieldOutputDeletedAt holds the string denoting the output_deleted_at field in the database.
FieldOutputDeletedAt = "output_deleted_at"
// FieldDownloadedAt holds the string denoting the downloaded_at field in the database.
FieldDownloadedAt = "downloaded_at"
// FieldUserDeletedAt holds the string denoting the user_deleted_at field in the database.
FieldUserDeletedAt = "user_deleted_at"
// FieldLastErrorCode holds the string denoting the last_error_code field in the database.
FieldLastErrorCode = "last_error_code"
// FieldLastErrorMessage holds the string denoting the last_error_message field in the database.
FieldLastErrorMessage = "last_error_message"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldSubmittedAt holds the string denoting the submitted_at field in the database.
FieldSubmittedAt = "submitted_at"
// FieldStartedAt holds the string denoting the started_at field in the database.
FieldStartedAt = "started_at"
// FieldFinishedAt holds the string denoting the finished_at field in the database.
FieldFinishedAt = "finished_at"
// FieldSettledAt holds the string denoting the settled_at field in the database.
FieldSettledAt = "settled_at"
// Table holds the table name of the batchimagejob in the database.
Table = "batch_image_jobs"
)
// Columns holds all SQL columns for batchimagejob fields.
var Columns = []string{
FieldID,
FieldBatchID,
FieldUserID,
FieldAPIKeyID,
FieldAccountID,
FieldProvider,
FieldModel,
FieldTaskName,
FieldStatus,
FieldProviderJobName,
FieldProviderInputRef,
FieldProviderOutputRef,
FieldGcsInputURI,
FieldGcsOutputURI,
FieldItemCount,
FieldSuccessCount,
FieldFailCount,
FieldCancelledCount,
FieldEstimatedCost,
FieldHoldAmount,
FieldActualCost,
FieldCurrency,
FieldHoldID,
FieldIdempotencyKey,
FieldRequestHash,
FieldManifestHash,
FieldRetryCount,
FieldVersion,
FieldOutputExpiresAt,
FieldInputDeletedAt,
FieldOutputDeletedAt,
FieldDownloadedAt,
FieldUserDeletedAt,
FieldLastErrorCode,
FieldLastErrorMessage,
FieldCreatedAt,
FieldUpdatedAt,
FieldSubmittedAt,
FieldStartedAt,
FieldFinishedAt,
FieldSettledAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// BatchIDValidator is a validator for the "batch_id" field. It is called by the builders before save.
BatchIDValidator func(string) error
// ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
ProviderValidator func(string) error
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
ModelValidator func(string) error
// DefaultTaskName holds the default value on creation for the "task_name" field.
DefaultTaskName string
// TaskNameValidator is a validator for the "task_name" field. It is called by the builders before save.
TaskNameValidator func(string) error
// DefaultStatus holds the default value on creation for the "status" field.
DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error
// ProviderJobNameValidator is a validator for the "provider_job_name" field. It is called by the builders before save.
ProviderJobNameValidator func(string) error
// ProviderInputRefValidator is a validator for the "provider_input_ref" field. It is called by the builders before save.
ProviderInputRefValidator func(string) error
// ProviderOutputRefValidator is a validator for the "provider_output_ref" field. It is called by the builders before save.
ProviderOutputRefValidator func(string) error
// GcsInputURIValidator is a validator for the "gcs_input_uri" field. It is called by the builders before save.
GcsInputURIValidator func(string) error
// GcsOutputURIValidator is a validator for the "gcs_output_uri" field. It is called by the builders before save.
GcsOutputURIValidator func(string) error
// DefaultSuccessCount holds the default value on creation for the "success_count" field.
DefaultSuccessCount int
// DefaultFailCount holds the default value on creation for the "fail_count" field.
DefaultFailCount int
// DefaultCancelledCount holds the default value on creation for the "cancelled_count" field.
DefaultCancelledCount int
// DefaultEstimatedCost holds the default value on creation for the "estimated_cost" field.
DefaultEstimatedCost float64
// DefaultCurrency holds the default value on creation for the "currency" field.
DefaultCurrency string
// CurrencyValidator is a validator for the "currency" field. It is called by the builders before save.
CurrencyValidator func(string) error
// HoldIDValidator is a validator for the "hold_id" field. It is called by the builders before save.
HoldIDValidator func(string) error
// IdempotencyKeyValidator is a validator for the "idempotency_key" field. It is called by the builders before save.
IdempotencyKeyValidator func(string) error
// RequestHashValidator is a validator for the "request_hash" field. It is called by the builders before save.
RequestHashValidator func(string) error
// ManifestHashValidator is a validator for the "manifest_hash" field. It is called by the builders before save.
ManifestHashValidator func(string) error
// DefaultRetryCount holds the default value on creation for the "retry_count" field.
DefaultRetryCount int
// DefaultVersion holds the default value on creation for the "version" field.
DefaultVersion int
// LastErrorCodeValidator is a validator for the "last_error_code" field. It is called by the builders before save.
LastErrorCodeValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
)
// OrderOption defines the ordering options for the BatchImageJob queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByBatchID orders the results by the batch_id field.
func ByBatchID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBatchID, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByAPIKeyID orders the results by the api_key_id field.
func ByAPIKeyID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAPIKeyID, opts...).ToFunc()
}
// ByAccountID orders the results by the account_id field.
func ByAccountID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAccountID, opts...).ToFunc()
}
// ByProvider orders the results by the provider field.
func ByProvider(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProvider, opts...).ToFunc()
}
// ByModel orders the results by the model field.
func ByModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldModel, opts...).ToFunc()
}
// ByTaskName orders the results by the task_name field.
func ByTaskName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTaskName, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByProviderJobName orders the results by the provider_job_name field.
func ByProviderJobName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderJobName, opts...).ToFunc()
}
// ByProviderInputRef orders the results by the provider_input_ref field.
func ByProviderInputRef(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderInputRef, opts...).ToFunc()
}
// ByProviderOutputRef orders the results by the provider_output_ref field.
func ByProviderOutputRef(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderOutputRef, opts...).ToFunc()
}
// ByGcsInputURI orders the results by the gcs_input_uri field.
func ByGcsInputURI(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldGcsInputURI, opts...).ToFunc()
}
// ByGcsOutputURI orders the results by the gcs_output_uri field.
func ByGcsOutputURI(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldGcsOutputURI, opts...).ToFunc()
}
// ByItemCount orders the results by the item_count field.
func ByItemCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldItemCount, opts...).ToFunc()
}
// BySuccessCount orders the results by the success_count field.
func BySuccessCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSuccessCount, opts...).ToFunc()
}
// ByFailCount orders the results by the fail_count field.
func ByFailCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailCount, opts...).ToFunc()
}
// ByCancelledCount orders the results by the cancelled_count field.
func ByCancelledCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCancelledCount, opts...).ToFunc()
}
// ByEstimatedCost orders the results by the estimated_cost field.
func ByEstimatedCost(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEstimatedCost, opts...).ToFunc()
}
// ByHoldAmount orders the results by the hold_amount field.
func ByHoldAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldHoldAmount, opts...).ToFunc()
}
// ByActualCost orders the results by the actual_cost field.
func ByActualCost(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldActualCost, opts...).ToFunc()
}
// ByCurrency orders the results by the currency field.
func ByCurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCurrency, opts...).ToFunc()
}
// ByHoldID orders the results by the hold_id field.
func ByHoldID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldHoldID, opts...).ToFunc()
}
// ByIdempotencyKey orders the results by the idempotency_key field.
func ByIdempotencyKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIdempotencyKey, opts...).ToFunc()
}
// ByRequestHash orders the results by the request_hash field.
func ByRequestHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRequestHash, opts...).ToFunc()
}
// ByManifestHash orders the results by the manifest_hash field.
func ByManifestHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldManifestHash, opts...).ToFunc()
}
// ByRetryCount orders the results by the retry_count field.
func ByRetryCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRetryCount, opts...).ToFunc()
}
// ByVersion orders the results by the version field.
func ByVersion(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVersion, opts...).ToFunc()
}
// ByOutputExpiresAt orders the results by the output_expires_at field.
func ByOutputExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOutputExpiresAt, opts...).ToFunc()
}
// ByInputDeletedAt orders the results by the input_deleted_at field.
func ByInputDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldInputDeletedAt, opts...).ToFunc()
}
// ByOutputDeletedAt orders the results by the output_deleted_at field.
func ByOutputDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOutputDeletedAt, opts...).ToFunc()
}
// ByDownloadedAt orders the results by the downloaded_at field.
func ByDownloadedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDownloadedAt, opts...).ToFunc()
}
// ByUserDeletedAt orders the results by the user_deleted_at field.
func ByUserDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserDeletedAt, opts...).ToFunc()
}
// ByLastErrorCode orders the results by the last_error_code field.
func ByLastErrorCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastErrorCode, opts...).ToFunc()
}
// ByLastErrorMessage orders the results by the last_error_message field.
func ByLastErrorMessage(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastErrorMessage, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// BySubmittedAt orders the results by the submitted_at field.
func BySubmittedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubmittedAt, opts...).ToFunc()
}
// ByStartedAt orders the results by the started_at field.
func ByStartedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStartedAt, opts...).ToFunc()
}
// ByFinishedAt orders the results by the finished_at field.
func ByFinishedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFinishedAt, opts...).ToFunc()
}
// BySettledAt orders the results by the settled_at field.
func BySettledAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSettledAt, opts...).ToFunc()
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageJobDelete is the builder for deleting a BatchImageJob entity.
type BatchImageJobDelete struct {
config
hooks []Hook
mutation *BatchImageJobMutation
}
// Where appends a list predicates to the BatchImageJobDelete builder.
func (_d *BatchImageJobDelete) Where(ps ...predicate.BatchImageJob) *BatchImageJobDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *BatchImageJobDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageJobDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *BatchImageJobDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(batchimagejob.Table, sqlgraph.NewFieldSpec(batchimagejob.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// BatchImageJobDeleteOne is the builder for deleting a single BatchImageJob entity.
type BatchImageJobDeleteOne struct {
_d *BatchImageJobDelete
}
// Where appends a list predicates to the BatchImageJobDelete builder.
func (_d *BatchImageJobDeleteOne) Where(ps ...predicate.BatchImageJob) *BatchImageJobDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *BatchImageJobDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{batchimagejob.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *BatchImageJobDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}
+564
View File
@@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// BatchImageJobQuery is the builder for querying BatchImageJob entities.
type BatchImageJobQuery struct {
config
ctx *QueryContext
order []batchimagejob.OrderOption
inters []Interceptor
predicates []predicate.BatchImageJob
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the BatchImageJobQuery builder.
func (_q *BatchImageJobQuery) Where(ps ...predicate.BatchImageJob) *BatchImageJobQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *BatchImageJobQuery) Limit(limit int) *BatchImageJobQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *BatchImageJobQuery) Offset(offset int) *BatchImageJobQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *BatchImageJobQuery) Unique(unique bool) *BatchImageJobQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *BatchImageJobQuery) Order(o ...batchimagejob.OrderOption) *BatchImageJobQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first BatchImageJob entity from the query.
// Returns a *NotFoundError when no BatchImageJob was found.
func (_q *BatchImageJobQuery) First(ctx context.Context) (*BatchImageJob, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{batchimagejob.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *BatchImageJobQuery) FirstX(ctx context.Context) *BatchImageJob {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first BatchImageJob ID from the query.
// Returns a *NotFoundError when no BatchImageJob ID was found.
func (_q *BatchImageJobQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{batchimagejob.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *BatchImageJobQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single BatchImageJob entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one BatchImageJob entity is found.
// Returns a *NotFoundError when no BatchImageJob entities are found.
func (_q *BatchImageJobQuery) Only(ctx context.Context) (*BatchImageJob, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{batchimagejob.Label}
default:
return nil, &NotSingularError{batchimagejob.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *BatchImageJobQuery) OnlyX(ctx context.Context) *BatchImageJob {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only BatchImageJob ID in the query.
// Returns a *NotSingularError when more than one BatchImageJob ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *BatchImageJobQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{batchimagejob.Label}
default:
err = &NotSingularError{batchimagejob.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *BatchImageJobQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of BatchImageJobs.
func (_q *BatchImageJobQuery) All(ctx context.Context) ([]*BatchImageJob, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*BatchImageJob, *BatchImageJobQuery]()
return withInterceptors[[]*BatchImageJob](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *BatchImageJobQuery) AllX(ctx context.Context) []*BatchImageJob {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of BatchImageJob IDs.
func (_q *BatchImageJobQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(batchimagejob.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *BatchImageJobQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *BatchImageJobQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*BatchImageJobQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *BatchImageJobQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *BatchImageJobQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *BatchImageJobQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the BatchImageJobQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *BatchImageJobQuery) Clone() *BatchImageJobQuery {
if _q == nil {
return nil
}
return &BatchImageJobQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]batchimagejob.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.BatchImageJob{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// BatchID string `json:"batch_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.BatchImageJob.Query().
// GroupBy(batchimagejob.FieldBatchID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *BatchImageJobQuery) GroupBy(field string, fields ...string) *BatchImageJobGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &BatchImageJobGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = batchimagejob.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// BatchID string `json:"batch_id,omitempty"`
// }
//
// client.BatchImageJob.Query().
// Select(batchimagejob.FieldBatchID).
// Scan(ctx, &v)
func (_q *BatchImageJobQuery) Select(fields ...string) *BatchImageJobSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &BatchImageJobSelect{BatchImageJobQuery: _q}
sbuild.label = batchimagejob.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a BatchImageJobSelect configured with the given aggregations.
func (_q *BatchImageJobQuery) Aggregate(fns ...AggregateFunc) *BatchImageJobSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *BatchImageJobQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !batchimagejob.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *BatchImageJobQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*BatchImageJob, error) {
var (
nodes = []*BatchImageJob{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*BatchImageJob).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &BatchImageJob{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *BatchImageJobQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *BatchImageJobQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(batchimagejob.Table, batchimagejob.Columns, sqlgraph.NewFieldSpec(batchimagejob.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, batchimagejob.FieldID)
for i := range fields {
if fields[i] != batchimagejob.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *BatchImageJobQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(batchimagejob.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = batchimagejob.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *BatchImageJobQuery) ForUpdate(opts ...sql.LockOption) *BatchImageJobQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *BatchImageJobQuery) ForShare(opts ...sql.LockOption) *BatchImageJobQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// BatchImageJobGroupBy is the group-by builder for BatchImageJob entities.
type BatchImageJobGroupBy struct {
selector
build *BatchImageJobQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *BatchImageJobGroupBy) Aggregate(fns ...AggregateFunc) *BatchImageJobGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *BatchImageJobGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageJobQuery, *BatchImageJobGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *BatchImageJobGroupBy) sqlScan(ctx context.Context, root *BatchImageJobQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// BatchImageJobSelect is the builder for selecting fields of BatchImageJob entities.
type BatchImageJobSelect struct {
*BatchImageJobQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *BatchImageJobSelect) Aggregate(fns ...AggregateFunc) *BatchImageJobSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *BatchImageJobSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*BatchImageJobQuery, *BatchImageJobSelect](ctx, _s.BatchImageJobQuery, _s, _s.inters, v)
}
func (_s *BatchImageJobSelect) sqlScan(ctx context.Context, root *BatchImageJobQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -43,6 +43,8 @@ type ChannelMonitor struct {
Enabled bool `json:"enabled,omitempty"`
// IntervalSeconds holds the value of the "interval_seconds" field.
IntervalSeconds int `json:"interval_seconds,omitempty"`
// 每次调度在 interval 基础上 ± [0, jitter] 的均匀随机偏移(秒);0 表示固定间隔。service 层另保证 interval - jitter >= 15
JitterSeconds int `json:"jitter_seconds,omitempty"`
// LastCheckedAt holds the value of the "last_checked_at" field.
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
// CreatedBy holds the value of the "created_by" field.
@@ -112,7 +114,7 @@ func (*ChannelMonitor) scanValues(columns []string) ([]any, error) {
values[i] = new([]byte)
case channelmonitor.FieldEnabled:
values[i] = new(sql.NullBool)
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldJitterSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
values[i] = new(sql.NullInt64)
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldAPIMode, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName, channelmonitor.FieldBodyOverrideMode:
values[i] = new(sql.NullString)
@@ -213,6 +215,12 @@ func (_m *ChannelMonitor) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.IntervalSeconds = int(value.Int64)
}
case channelmonitor.FieldJitterSeconds:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field jitter_seconds", values[i])
} else if value.Valid {
_m.JitterSeconds = int(value.Int64)
}
case channelmonitor.FieldLastCheckedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field last_checked_at", values[i])
@@ -341,6 +349,9 @@ func (_m *ChannelMonitor) String() string {
builder.WriteString("interval_seconds=")
builder.WriteString(fmt.Sprintf("%v", _m.IntervalSeconds))
builder.WriteString(", ")
builder.WriteString("jitter_seconds=")
builder.WriteString(fmt.Sprintf("%v", _m.JitterSeconds))
builder.WriteString(", ")
if v := _m.LastCheckedAt; v != nil {
builder.WriteString("last_checked_at=")
builder.WriteString(v.Format(time.ANSIC))
@@ -39,6 +39,8 @@ const (
FieldEnabled = "enabled"
// FieldIntervalSeconds holds the string denoting the interval_seconds field in the database.
FieldIntervalSeconds = "interval_seconds"
// FieldJitterSeconds holds the string denoting the jitter_seconds field in the database.
FieldJitterSeconds = "jitter_seconds"
// FieldLastCheckedAt holds the string denoting the last_checked_at field in the database.
FieldLastCheckedAt = "last_checked_at"
// FieldCreatedBy holds the string denoting the created_by field in the database.
@@ -97,6 +99,7 @@ var Columns = []string{
FieldGroupName,
FieldEnabled,
FieldIntervalSeconds,
FieldJitterSeconds,
FieldLastCheckedAt,
FieldCreatedBy,
FieldTemplateID,
@@ -144,6 +147,10 @@ var (
DefaultEnabled bool
// IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
IntervalSecondsValidator func(int) error
// DefaultJitterSeconds holds the default value on creation for the "jitter_seconds" field.
DefaultJitterSeconds int
// JitterSecondsValidator is a validator for the "jitter_seconds" field. It is called by the builders before save.
JitterSecondsValidator func(int) error
// DefaultExtraHeaders holds the default value on creation for the "extra_headers" field.
DefaultExtraHeaders map[string]string
// DefaultBodyOverrideMode holds the default value on creation for the "body_override_mode" field.
@@ -239,6 +246,11 @@ func ByIntervalSeconds(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIntervalSeconds, opts...).ToFunc()
}
// ByJitterSeconds orders the results by the jitter_seconds field.
func ByJitterSeconds(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldJitterSeconds, opts...).ToFunc()
}
// ByLastCheckedAt orders the results by the last_checked_at field.
func ByLastCheckedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastCheckedAt, opts...).ToFunc()
+45
View File
@@ -105,6 +105,11 @@ func IntervalSeconds(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
}
// JitterSeconds applies equality check predicate on the "jitter_seconds" field. It's identical to JitterSecondsEQ.
func JitterSeconds(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldJitterSeconds, v))
}
// LastCheckedAt applies equality check predicate on the "last_checked_at" field. It's identical to LastCheckedAtEQ.
func LastCheckedAt(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
@@ -675,6 +680,46 @@ func IntervalSecondsLTE(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldIntervalSeconds, v))
}
// JitterSecondsEQ applies the EQ predicate on the "jitter_seconds" field.
func JitterSecondsEQ(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldJitterSeconds, v))
}
// JitterSecondsNEQ applies the NEQ predicate on the "jitter_seconds" field.
func JitterSecondsNEQ(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldJitterSeconds, v))
}
// JitterSecondsIn applies the In predicate on the "jitter_seconds" field.
func JitterSecondsIn(vs ...int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldJitterSeconds, vs...))
}
// JitterSecondsNotIn applies the NotIn predicate on the "jitter_seconds" field.
func JitterSecondsNotIn(vs ...int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldJitterSeconds, vs...))
}
// JitterSecondsGT applies the GT predicate on the "jitter_seconds" field.
func JitterSecondsGT(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldJitterSeconds, v))
}
// JitterSecondsGTE applies the GTE predicate on the "jitter_seconds" field.
func JitterSecondsGTE(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldJitterSeconds, v))
}
// JitterSecondsLT applies the LT predicate on the "jitter_seconds" field.
func JitterSecondsLT(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldJitterSeconds, v))
}
// JitterSecondsLTE applies the LTE predicate on the "jitter_seconds" field.
func JitterSecondsLTE(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldJitterSeconds, v))
}
// LastCheckedAtEQ applies the EQ predicate on the "last_checked_at" field.
func LastCheckedAtEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
+90
View File
@@ -137,6 +137,20 @@ func (_c *ChannelMonitorCreate) SetIntervalSeconds(v int) *ChannelMonitorCreate
return _c
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (_c *ChannelMonitorCreate) SetJitterSeconds(v int) *ChannelMonitorCreate {
_c.mutation.SetJitterSeconds(v)
return _c
}
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
func (_c *ChannelMonitorCreate) SetNillableJitterSeconds(v *int) *ChannelMonitorCreate {
if v != nil {
_c.SetJitterSeconds(*v)
}
return _c
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (_c *ChannelMonitorCreate) SetLastCheckedAt(v time.Time) *ChannelMonitorCreate {
_c.mutation.SetLastCheckedAt(v)
@@ -305,6 +319,10 @@ func (_c *ChannelMonitorCreate) defaults() {
v := channelmonitor.DefaultEnabled
_c.mutation.SetEnabled(v)
}
if _, ok := _c.mutation.JitterSeconds(); !ok {
v := channelmonitor.DefaultJitterSeconds
_c.mutation.SetJitterSeconds(v)
}
if _, ok := _c.mutation.ExtraHeaders(); !ok {
v := channelmonitor.DefaultExtraHeaders
_c.mutation.SetExtraHeaders(v)
@@ -390,6 +408,14 @@ func (_c *ChannelMonitorCreate) check() error {
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
}
}
if _, ok := _c.mutation.JitterSeconds(); !ok {
return &ValidationError{Name: "jitter_seconds", err: errors.New(`ent: missing required field "ChannelMonitor.jitter_seconds"`)}
}
if v, ok := _c.mutation.JitterSeconds(); ok {
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedBy(); !ok {
return &ValidationError{Name: "created_by", err: errors.New(`ent: missing required field "ChannelMonitor.created_by"`)}
}
@@ -479,6 +505,10 @@ func (_c *ChannelMonitorCreate) createSpec() (*ChannelMonitor, *sqlgraph.CreateS
_spec.SetField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
_node.IntervalSeconds = value
}
if value, ok := _c.mutation.JitterSeconds(); ok {
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
_node.JitterSeconds = value
}
if value, ok := _c.mutation.LastCheckedAt(); ok {
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
_node.LastCheckedAt = &value
@@ -744,6 +774,24 @@ func (u *ChannelMonitorUpsert) AddIntervalSeconds(v int) *ChannelMonitorUpsert {
return u
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (u *ChannelMonitorUpsert) SetJitterSeconds(v int) *ChannelMonitorUpsert {
u.Set(channelmonitor.FieldJitterSeconds, v)
return u
}
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
func (u *ChannelMonitorUpsert) UpdateJitterSeconds() *ChannelMonitorUpsert {
u.SetExcluded(channelmonitor.FieldJitterSeconds)
return u
}
// AddJitterSeconds adds v to the "jitter_seconds" field.
func (u *ChannelMonitorUpsert) AddJitterSeconds(v int) *ChannelMonitorUpsert {
u.Add(channelmonitor.FieldJitterSeconds, v)
return u
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (u *ChannelMonitorUpsert) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsert {
u.Set(channelmonitor.FieldLastCheckedAt, v)
@@ -1053,6 +1101,27 @@ func (u *ChannelMonitorUpsertOne) UpdateIntervalSeconds() *ChannelMonitorUpsertO
})
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (u *ChannelMonitorUpsertOne) SetJitterSeconds(v int) *ChannelMonitorUpsertOne {
return u.Update(func(s *ChannelMonitorUpsert) {
s.SetJitterSeconds(v)
})
}
// AddJitterSeconds adds v to the "jitter_seconds" field.
func (u *ChannelMonitorUpsertOne) AddJitterSeconds(v int) *ChannelMonitorUpsertOne {
return u.Update(func(s *ChannelMonitorUpsert) {
s.AddJitterSeconds(v)
})
}
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
func (u *ChannelMonitorUpsertOne) UpdateJitterSeconds() *ChannelMonitorUpsertOne {
return u.Update(func(s *ChannelMonitorUpsert) {
s.UpdateJitterSeconds()
})
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (u *ChannelMonitorUpsertOne) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsertOne {
return u.Update(func(s *ChannelMonitorUpsert) {
@@ -1544,6 +1613,27 @@ func (u *ChannelMonitorUpsertBulk) UpdateIntervalSeconds() *ChannelMonitorUpsert
})
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (u *ChannelMonitorUpsertBulk) SetJitterSeconds(v int) *ChannelMonitorUpsertBulk {
return u.Update(func(s *ChannelMonitorUpsert) {
s.SetJitterSeconds(v)
})
}
// AddJitterSeconds adds v to the "jitter_seconds" field.
func (u *ChannelMonitorUpsertBulk) AddJitterSeconds(v int) *ChannelMonitorUpsertBulk {
return u.Update(func(s *ChannelMonitorUpsert) {
s.AddJitterSeconds(v)
})
}
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
func (u *ChannelMonitorUpsertBulk) UpdateJitterSeconds() *ChannelMonitorUpsertBulk {
return u.Update(func(s *ChannelMonitorUpsert) {
s.UpdateJitterSeconds()
})
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (u *ChannelMonitorUpsertBulk) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsertBulk {
return u.Update(func(s *ChannelMonitorUpsert) {
+64
View File
@@ -189,6 +189,27 @@ func (_u *ChannelMonitorUpdate) AddIntervalSeconds(v int) *ChannelMonitorUpdate
return _u
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (_u *ChannelMonitorUpdate) SetJitterSeconds(v int) *ChannelMonitorUpdate {
_u.mutation.ResetJitterSeconds()
_u.mutation.SetJitterSeconds(v)
return _u
}
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
func (_u *ChannelMonitorUpdate) SetNillableJitterSeconds(v *int) *ChannelMonitorUpdate {
if v != nil {
_u.SetJitterSeconds(*v)
}
return _u
}
// AddJitterSeconds adds value to the "jitter_seconds" field.
func (_u *ChannelMonitorUpdate) AddJitterSeconds(v int) *ChannelMonitorUpdate {
_u.mutation.AddJitterSeconds(v)
return _u
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (_u *ChannelMonitorUpdate) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdate {
_u.mutation.SetLastCheckedAt(v)
@@ -462,6 +483,11 @@ func (_u *ChannelMonitorUpdate) check() error {
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
}
}
if v, ok := _u.mutation.JitterSeconds(); ok {
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
}
}
if v, ok := _u.mutation.BodyOverrideMode(); ok {
if err := channelmonitor.BodyOverrideModeValidator(v); err != nil {
return &ValidationError{Name: "body_override_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.body_override_mode": %w`, err)}
@@ -526,6 +552,12 @@ func (_u *ChannelMonitorUpdate) sqlSave(ctx context.Context) (_node int, err err
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.JitterSeconds(); ok {
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedJitterSeconds(); ok {
_spec.AddField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.LastCheckedAt(); ok {
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
}
@@ -846,6 +878,27 @@ func (_u *ChannelMonitorUpdateOne) AddIntervalSeconds(v int) *ChannelMonitorUpda
return _u
}
// SetJitterSeconds sets the "jitter_seconds" field.
func (_u *ChannelMonitorUpdateOne) SetJitterSeconds(v int) *ChannelMonitorUpdateOne {
_u.mutation.ResetJitterSeconds()
_u.mutation.SetJitterSeconds(v)
return _u
}
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
func (_u *ChannelMonitorUpdateOne) SetNillableJitterSeconds(v *int) *ChannelMonitorUpdateOne {
if v != nil {
_u.SetJitterSeconds(*v)
}
return _u
}
// AddJitterSeconds adds value to the "jitter_seconds" field.
func (_u *ChannelMonitorUpdateOne) AddJitterSeconds(v int) *ChannelMonitorUpdateOne {
_u.mutation.AddJitterSeconds(v)
return _u
}
// SetLastCheckedAt sets the "last_checked_at" field.
func (_u *ChannelMonitorUpdateOne) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdateOne {
_u.mutation.SetLastCheckedAt(v)
@@ -1132,6 +1185,11 @@ func (_u *ChannelMonitorUpdateOne) check() error {
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
}
}
if v, ok := _u.mutation.JitterSeconds(); ok {
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
}
}
if v, ok := _u.mutation.BodyOverrideMode(); ok {
if err := channelmonitor.BodyOverrideModeValidator(v); err != nil {
return &ValidationError{Name: "body_override_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.body_override_mode": %w`, err)}
@@ -1213,6 +1271,12 @@ func (_u *ChannelMonitorUpdateOne) sqlSave(ctx context.Context) (_node *ChannelM
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.JitterSeconds(); ok {
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedJitterSeconds(); ok {
_spec.AddField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.LastCheckedAt(); ok {
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
}
+489 -32
View File
@@ -22,6 +22,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
@@ -73,6 +76,12 @@ type Client struct {
AuthIdentity *AuthIdentityClient
// AuthIdentityChannel is the client for interacting with the AuthIdentityChannel builders.
AuthIdentityChannel *AuthIdentityChannelClient
// BatchImageEvent is the client for interacting with the BatchImageEvent builders.
BatchImageEvent *BatchImageEventClient
// BatchImageItem is the client for interacting with the BatchImageItem builders.
BatchImageItem *BatchImageItemClient
// BatchImageJob is the client for interacting with the BatchImageJob builders.
BatchImageJob *BatchImageJobClient
// ChannelMonitor is the client for interacting with the ChannelMonitor builders.
ChannelMonitor *ChannelMonitorClient
// ChannelMonitorDailyRollup is the client for interacting with the ChannelMonitorDailyRollup builders.
@@ -147,6 +156,9 @@ func (c *Client) init() {
c.AnnouncementRead = NewAnnouncementReadClient(c.config)
c.AuthIdentity = NewAuthIdentityClient(c.config)
c.AuthIdentityChannel = NewAuthIdentityChannelClient(c.config)
c.BatchImageEvent = NewBatchImageEventClient(c.config)
c.BatchImageItem = NewBatchImageItemClient(c.config)
c.BatchImageJob = NewBatchImageJobClient(c.config)
c.ChannelMonitor = NewChannelMonitorClient(c.config)
c.ChannelMonitorDailyRollup = NewChannelMonitorDailyRollupClient(c.config)
c.ChannelMonitorHistory = NewChannelMonitorHistoryClient(c.config)
@@ -274,6 +286,9 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
AnnouncementRead: NewAnnouncementReadClient(cfg),
AuthIdentity: NewAuthIdentityClient(cfg),
AuthIdentityChannel: NewAuthIdentityChannelClient(cfg),
BatchImageEvent: NewBatchImageEventClient(cfg),
BatchImageItem: NewBatchImageItemClient(cfg),
BatchImageJob: NewBatchImageJobClient(cfg),
ChannelMonitor: NewChannelMonitorClient(cfg),
ChannelMonitorDailyRollup: NewChannelMonitorDailyRollupClient(cfg),
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
@@ -328,6 +343,9 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
AnnouncementRead: NewAnnouncementReadClient(cfg),
AuthIdentity: NewAuthIdentityClient(cfg),
AuthIdentityChannel: NewAuthIdentityChannelClient(cfg),
BatchImageEvent: NewBatchImageEventClient(cfg),
BatchImageItem: NewBatchImageItemClient(cfg),
BatchImageJob: NewBatchImageJobClient(cfg),
ChannelMonitor: NewChannelMonitorClient(cfg),
ChannelMonitorDailyRollup: NewChannelMonitorDailyRollupClient(cfg),
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
@@ -386,14 +404,15 @@ func (c *Client) Close() error {
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
c.AuthIdentity, c.AuthIdentityChannel, c.ChannelMonitor,
c.ChannelMonitorDailyRollup, c.ChannelMonitorHistory,
c.ChannelMonitorRequestTemplate, c.ErrorPassthroughRule, c.Group,
c.IdempotencyRecord, c.IdentityAdoptionDecision, c.PaymentAuditLog,
c.PaymentOrder, c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode,
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog,
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.AuthIdentity, c.AuthIdentityChannel, c.BatchImageEvent, c.BatchImageItem,
c.BatchImageJob, c.ChannelMonitor, c.ChannelMonitorDailyRollup,
c.ChannelMonitorHistory, c.ChannelMonitorRequestTemplate,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.UserPlatformQuota, c.UserSubscription,
} {
n.Use(hooks...)
@@ -405,14 +424,15 @@ func (c *Client) Use(hooks ...Hook) {
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
c.AuthIdentity, c.AuthIdentityChannel, c.ChannelMonitor,
c.ChannelMonitorDailyRollup, c.ChannelMonitorHistory,
c.ChannelMonitorRequestTemplate, c.ErrorPassthroughRule, c.Group,
c.IdempotencyRecord, c.IdentityAdoptionDecision, c.PaymentAuditLog,
c.PaymentOrder, c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode,
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog,
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.AuthIdentity, c.AuthIdentityChannel, c.BatchImageEvent, c.BatchImageItem,
c.BatchImageJob, c.ChannelMonitor, c.ChannelMonitorDailyRollup,
c.ChannelMonitorHistory, c.ChannelMonitorRequestTemplate,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.UserPlatformQuota, c.UserSubscription,
} {
n.Intercept(interceptors...)
@@ -436,6 +456,12 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.AuthIdentity.mutate(ctx, m)
case *AuthIdentityChannelMutation:
return c.AuthIdentityChannel.mutate(ctx, m)
case *BatchImageEventMutation:
return c.BatchImageEvent.mutate(ctx, m)
case *BatchImageItemMutation:
return c.BatchImageItem.mutate(ctx, m)
case *BatchImageJobMutation:
return c.BatchImageJob.mutate(ctx, m)
case *ChannelMonitorMutation:
return c.ChannelMonitor.mutate(ctx, m)
case *ChannelMonitorDailyRollupMutation:
@@ -820,6 +846,38 @@ func (c *AccountClient) QueryProxy(_m *Account) *ProxyQuery {
return query
}
// QueryParent queries the parent edge of a Account.
func (c *AccountClient) QueryParent(_m *Account) *AccountQuery {
query := (&AccountClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(account.Table, account.FieldID, id),
sqlgraph.To(account.Table, account.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, account.ParentTable, account.ParentColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryChildren queries the children edge of a Account.
func (c *AccountClient) QueryChildren(_m *Account) *AccountQuery {
query := (&AccountClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(account.Table, account.FieldID, id),
sqlgraph.To(account.Table, account.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, account.ChildrenTable, account.ChildrenColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryUsageLogs queries the usage_logs edge of a Account.
func (c *AccountClient) QueryUsageLogs(_m *Account) *UsageLogQuery {
query := (&UsageLogClient{config: c.config}).Query()
@@ -1639,6 +1697,405 @@ func (c *AuthIdentityChannelClient) mutate(ctx context.Context, m *AuthIdentityC
}
}
// BatchImageEventClient is a client for the BatchImageEvent schema.
type BatchImageEventClient struct {
config
}
// NewBatchImageEventClient returns a client for the BatchImageEvent from the given config.
func NewBatchImageEventClient(c config) *BatchImageEventClient {
return &BatchImageEventClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `batchimageevent.Hooks(f(g(h())))`.
func (c *BatchImageEventClient) Use(hooks ...Hook) {
c.hooks.BatchImageEvent = append(c.hooks.BatchImageEvent, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `batchimageevent.Intercept(f(g(h())))`.
func (c *BatchImageEventClient) Intercept(interceptors ...Interceptor) {
c.inters.BatchImageEvent = append(c.inters.BatchImageEvent, interceptors...)
}
// Create returns a builder for creating a BatchImageEvent entity.
func (c *BatchImageEventClient) Create() *BatchImageEventCreate {
mutation := newBatchImageEventMutation(c.config, OpCreate)
return &BatchImageEventCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of BatchImageEvent entities.
func (c *BatchImageEventClient) CreateBulk(builders ...*BatchImageEventCreate) *BatchImageEventCreateBulk {
return &BatchImageEventCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *BatchImageEventClient) MapCreateBulk(slice any, setFunc func(*BatchImageEventCreate, int)) *BatchImageEventCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &BatchImageEventCreateBulk{err: fmt.Errorf("calling to BatchImageEventClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*BatchImageEventCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &BatchImageEventCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for BatchImageEvent.
func (c *BatchImageEventClient) Update() *BatchImageEventUpdate {
mutation := newBatchImageEventMutation(c.config, OpUpdate)
return &BatchImageEventUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *BatchImageEventClient) UpdateOne(_m *BatchImageEvent) *BatchImageEventUpdateOne {
mutation := newBatchImageEventMutation(c.config, OpUpdateOne, withBatchImageEvent(_m))
return &BatchImageEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *BatchImageEventClient) UpdateOneID(id int64) *BatchImageEventUpdateOne {
mutation := newBatchImageEventMutation(c.config, OpUpdateOne, withBatchImageEventID(id))
return &BatchImageEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for BatchImageEvent.
func (c *BatchImageEventClient) Delete() *BatchImageEventDelete {
mutation := newBatchImageEventMutation(c.config, OpDelete)
return &BatchImageEventDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *BatchImageEventClient) DeleteOne(_m *BatchImageEvent) *BatchImageEventDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *BatchImageEventClient) DeleteOneID(id int64) *BatchImageEventDeleteOne {
builder := c.Delete().Where(batchimageevent.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &BatchImageEventDeleteOne{builder}
}
// Query returns a query builder for BatchImageEvent.
func (c *BatchImageEventClient) Query() *BatchImageEventQuery {
return &BatchImageEventQuery{
config: c.config,
ctx: &QueryContext{Type: TypeBatchImageEvent},
inters: c.Interceptors(),
}
}
// Get returns a BatchImageEvent entity by its id.
func (c *BatchImageEventClient) Get(ctx context.Context, id int64) (*BatchImageEvent, error) {
return c.Query().Where(batchimageevent.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *BatchImageEventClient) GetX(ctx context.Context, id int64) *BatchImageEvent {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *BatchImageEventClient) Hooks() []Hook {
return c.hooks.BatchImageEvent
}
// Interceptors returns the client interceptors.
func (c *BatchImageEventClient) Interceptors() []Interceptor {
return c.inters.BatchImageEvent
}
func (c *BatchImageEventClient) mutate(ctx context.Context, m *BatchImageEventMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&BatchImageEventCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&BatchImageEventUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&BatchImageEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&BatchImageEventDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown BatchImageEvent mutation op: %q", m.Op())
}
}
// BatchImageItemClient is a client for the BatchImageItem schema.
type BatchImageItemClient struct {
config
}
// NewBatchImageItemClient returns a client for the BatchImageItem from the given config.
func NewBatchImageItemClient(c config) *BatchImageItemClient {
return &BatchImageItemClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `batchimageitem.Hooks(f(g(h())))`.
func (c *BatchImageItemClient) Use(hooks ...Hook) {
c.hooks.BatchImageItem = append(c.hooks.BatchImageItem, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `batchimageitem.Intercept(f(g(h())))`.
func (c *BatchImageItemClient) Intercept(interceptors ...Interceptor) {
c.inters.BatchImageItem = append(c.inters.BatchImageItem, interceptors...)
}
// Create returns a builder for creating a BatchImageItem entity.
func (c *BatchImageItemClient) Create() *BatchImageItemCreate {
mutation := newBatchImageItemMutation(c.config, OpCreate)
return &BatchImageItemCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of BatchImageItem entities.
func (c *BatchImageItemClient) CreateBulk(builders ...*BatchImageItemCreate) *BatchImageItemCreateBulk {
return &BatchImageItemCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *BatchImageItemClient) MapCreateBulk(slice any, setFunc func(*BatchImageItemCreate, int)) *BatchImageItemCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &BatchImageItemCreateBulk{err: fmt.Errorf("calling to BatchImageItemClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*BatchImageItemCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &BatchImageItemCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for BatchImageItem.
func (c *BatchImageItemClient) Update() *BatchImageItemUpdate {
mutation := newBatchImageItemMutation(c.config, OpUpdate)
return &BatchImageItemUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *BatchImageItemClient) UpdateOne(_m *BatchImageItem) *BatchImageItemUpdateOne {
mutation := newBatchImageItemMutation(c.config, OpUpdateOne, withBatchImageItem(_m))
return &BatchImageItemUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *BatchImageItemClient) UpdateOneID(id int64) *BatchImageItemUpdateOne {
mutation := newBatchImageItemMutation(c.config, OpUpdateOne, withBatchImageItemID(id))
return &BatchImageItemUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for BatchImageItem.
func (c *BatchImageItemClient) Delete() *BatchImageItemDelete {
mutation := newBatchImageItemMutation(c.config, OpDelete)
return &BatchImageItemDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *BatchImageItemClient) DeleteOne(_m *BatchImageItem) *BatchImageItemDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *BatchImageItemClient) DeleteOneID(id int64) *BatchImageItemDeleteOne {
builder := c.Delete().Where(batchimageitem.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &BatchImageItemDeleteOne{builder}
}
// Query returns a query builder for BatchImageItem.
func (c *BatchImageItemClient) Query() *BatchImageItemQuery {
return &BatchImageItemQuery{
config: c.config,
ctx: &QueryContext{Type: TypeBatchImageItem},
inters: c.Interceptors(),
}
}
// Get returns a BatchImageItem entity by its id.
func (c *BatchImageItemClient) Get(ctx context.Context, id int64) (*BatchImageItem, error) {
return c.Query().Where(batchimageitem.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *BatchImageItemClient) GetX(ctx context.Context, id int64) *BatchImageItem {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *BatchImageItemClient) Hooks() []Hook {
return c.hooks.BatchImageItem
}
// Interceptors returns the client interceptors.
func (c *BatchImageItemClient) Interceptors() []Interceptor {
return c.inters.BatchImageItem
}
func (c *BatchImageItemClient) mutate(ctx context.Context, m *BatchImageItemMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&BatchImageItemCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&BatchImageItemUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&BatchImageItemUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&BatchImageItemDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown BatchImageItem mutation op: %q", m.Op())
}
}
// BatchImageJobClient is a client for the BatchImageJob schema.
type BatchImageJobClient struct {
config
}
// NewBatchImageJobClient returns a client for the BatchImageJob from the given config.
func NewBatchImageJobClient(c config) *BatchImageJobClient {
return &BatchImageJobClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `batchimagejob.Hooks(f(g(h())))`.
func (c *BatchImageJobClient) Use(hooks ...Hook) {
c.hooks.BatchImageJob = append(c.hooks.BatchImageJob, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `batchimagejob.Intercept(f(g(h())))`.
func (c *BatchImageJobClient) Intercept(interceptors ...Interceptor) {
c.inters.BatchImageJob = append(c.inters.BatchImageJob, interceptors...)
}
// Create returns a builder for creating a BatchImageJob entity.
func (c *BatchImageJobClient) Create() *BatchImageJobCreate {
mutation := newBatchImageJobMutation(c.config, OpCreate)
return &BatchImageJobCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of BatchImageJob entities.
func (c *BatchImageJobClient) CreateBulk(builders ...*BatchImageJobCreate) *BatchImageJobCreateBulk {
return &BatchImageJobCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *BatchImageJobClient) MapCreateBulk(slice any, setFunc func(*BatchImageJobCreate, int)) *BatchImageJobCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &BatchImageJobCreateBulk{err: fmt.Errorf("calling to BatchImageJobClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*BatchImageJobCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &BatchImageJobCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for BatchImageJob.
func (c *BatchImageJobClient) Update() *BatchImageJobUpdate {
mutation := newBatchImageJobMutation(c.config, OpUpdate)
return &BatchImageJobUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *BatchImageJobClient) UpdateOne(_m *BatchImageJob) *BatchImageJobUpdateOne {
mutation := newBatchImageJobMutation(c.config, OpUpdateOne, withBatchImageJob(_m))
return &BatchImageJobUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *BatchImageJobClient) UpdateOneID(id int64) *BatchImageJobUpdateOne {
mutation := newBatchImageJobMutation(c.config, OpUpdateOne, withBatchImageJobID(id))
return &BatchImageJobUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for BatchImageJob.
func (c *BatchImageJobClient) Delete() *BatchImageJobDelete {
mutation := newBatchImageJobMutation(c.config, OpDelete)
return &BatchImageJobDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *BatchImageJobClient) DeleteOne(_m *BatchImageJob) *BatchImageJobDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *BatchImageJobClient) DeleteOneID(id int64) *BatchImageJobDeleteOne {
builder := c.Delete().Where(batchimagejob.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &BatchImageJobDeleteOne{builder}
}
// Query returns a query builder for BatchImageJob.
func (c *BatchImageJobClient) Query() *BatchImageJobQuery {
return &BatchImageJobQuery{
config: c.config,
ctx: &QueryContext{Type: TypeBatchImageJob},
inters: c.Interceptors(),
}
}
// Get returns a BatchImageJob entity by its id.
func (c *BatchImageJobClient) Get(ctx context.Context, id int64) (*BatchImageJob, error) {
return c.Query().Where(batchimagejob.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *BatchImageJobClient) GetX(ctx context.Context, id int64) *BatchImageJob {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *BatchImageJobClient) Hooks() []Hook {
return c.hooks.BatchImageJob
}
// Interceptors returns the client interceptors.
func (c *BatchImageJobClient) Interceptors() []Interceptor {
return c.inters.BatchImageJob
}
func (c *BatchImageJobClient) mutate(ctx context.Context, m *BatchImageJobMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&BatchImageJobCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&BatchImageJobUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&BatchImageJobUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&BatchImageJobDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown BatchImageJob mutation op: %q", m.Op())
}
}
// ChannelMonitorClient is a client for the ChannelMonitor schema.
type ChannelMonitorClient struct {
config
@@ -6210,25 +6667,25 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
type (
hooks struct {
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
AuthIdentityChannel, ChannelMonitor, ChannelMonitorDailyRollup,
ChannelMonitorHistory, ChannelMonitorRequestTemplate, ErrorPassthroughRule,
Group, IdempotencyRecord, IdentityAdoptionDecision, PaymentAuditLog,
PaymentOrder, PaymentProviderInstance, PendingAuthSession, PromoCode,
PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting, SubscriptionPlan,
TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
UserAttributeDefinition, UserAttributeValue, UserPlatformQuota,
UserSubscription []ent.Hook
AuthIdentityChannel, BatchImageEvent, BatchImageItem, BatchImageJob,
ChannelMonitor, ChannelMonitorDailyRollup, ChannelMonitorHistory,
ChannelMonitorRequestTemplate, ErrorPassthroughRule, Group, IdempotencyRecord,
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Hook
}
inters struct {
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
AuthIdentityChannel, ChannelMonitor, ChannelMonitorDailyRollup,
ChannelMonitorHistory, ChannelMonitorRequestTemplate, ErrorPassthroughRule,
Group, IdempotencyRecord, IdentityAdoptionDecision, PaymentAuditLog,
PaymentOrder, PaymentProviderInstance, PendingAuthSession, PromoCode,
PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting, SubscriptionPlan,
TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
UserAttributeDefinition, UserAttributeValue, UserPlatformQuota,
UserSubscription []ent.Interceptor
AuthIdentityChannel, BatchImageEvent, BatchImageItem, BatchImageJob,
ChannelMonitor, ChannelMonitorDailyRollup, ChannelMonitorHistory,
ChannelMonitorRequestTemplate, ErrorPassthroughRule, Group, IdempotencyRecord,
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Interceptor
}
)
+6
View File
@@ -19,6 +19,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
@@ -114,6 +117,9 @@ func checkColumn(t, c string) error {
announcementread.Table: announcementread.ValidColumn,
authidentity.Table: authidentity.ValidColumn,
authidentitychannel.Table: authidentitychannel.ValidColumn,
batchimageevent.Table: batchimageevent.ValidColumn,
batchimageitem.Table: batchimageitem.ValidColumn,
batchimagejob.Table: batchimagejob.ValidColumn,
channelmonitor.Table: channelmonitor.ValidColumn,
channelmonitordailyrollup.Table: channelmonitordailyrollup.ValidColumn,
channelmonitorhistory.Table: channelmonitorhistory.ValidColumn,
+144 -3
View File
@@ -31,6 +31,14 @@ type Group struct {
Description *string `json:"description,omitempty"`
// RateMultiplier holds the value of the "rate_multiplier" field.
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
// 是否启用高峰时段倍率
PeakRateEnabled bool `json:"peak_rate_enabled,omitempty"`
// 高峰开始时间 HH:MM(含),如 14:00;空表示未配置;不支持跨天
PeakStart string `json:"peak_start,omitempty"`
// 高峰结束时间 HH:MM(不含),必须大于 peak_start;不支持跨天,如 22:00-02:00
PeakEnd string `json:"peak_end,omitempty"`
// 高峰时段叠加倍率,仅在 peak_rate_enabled 且处于 [peak_start, peak_end) 时乘入文本倍率
PeakRateMultiplier float64 `json:"peak_rate_multiplier,omitempty"`
// IsExclusive holds the value of the "is_exclusive" field.
IsExclusive bool `json:"is_exclusive,omitempty"`
// Status holds the value of the "status" field.
@@ -49,6 +57,8 @@ type Group struct {
DefaultValidityDays int `json:"default_validity_days,omitempty"`
// 是否允许该分组使用图片生成能力
AllowImageGeneration bool `json:"allow_image_generation,omitempty"`
// 是否允许该分组使用批量图片生成能力
AllowBatchImageGeneration bool `json:"allow_batch_image_generation,omitempty"`
// 图片生成是否使用独立倍率;false 表示共享分组有效倍率
ImageRateIndependent bool `json:"image_rate_independent,omitempty"`
// 图片生成独立倍率,仅 image_rate_independent=true 时生效
@@ -59,6 +69,20 @@ type Group struct {
ImagePrice2k *float64 `json:"image_price_2k,omitempty"`
// ImagePrice4k holds the value of the "image_price_4k" field.
ImagePrice4k *float64 `json:"image_price_4k,omitempty"`
// 批量图片生成折扣倍率,最终单价会乘以该值;0 表示免费
BatchImageDiscountMultiplier float64 `json:"batch_image_discount_multiplier,omitempty"`
// 批量图片生成冻结价格比例,按普通生图原价乘以该比例冻结,结算后释放差额
BatchImageHoldMultiplier float64 `json:"batch_image_hold_multiplier,omitempty"`
// 视频生成是否使用独立倍率;false 表示共享分组有效倍率
VideoRateIndependent bool `json:"video_rate_independent,omitempty"`
// 视频生成独立倍率,仅 video_rate_independent=true 时生效
VideoRateMultiplier float64 `json:"video_rate_multiplier,omitempty"`
// VideoPrice480p holds the value of the "video_price_480p" field.
VideoPrice480p *float64 `json:"video_price_480p,omitempty"`
// VideoPrice720p holds the value of the "video_price_720p" field.
VideoPrice720p *float64 `json:"video_price_720p,omitempty"`
// VideoPrice1080p holds the value of the "video_price_1080p" field.
VideoPrice1080p *float64 `json:"video_price_1080p,omitempty"`
// 是否仅允许 Claude Code 客户端
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
// 非 Claude Code 请求降级使用的分组 ID
@@ -197,13 +221,13 @@ func (*Group) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig, group.FieldModelsListConfig:
values[i] = new([]byte)
case group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldImageRateIndependent, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
values[i] = new(sql.NullBool)
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k:
case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p:
values[i] = new(sql.NullFloat64)
case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder, group.FieldRpmLimit:
values[i] = new(sql.NullInt64)
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType, group.FieldDefaultMappedModel:
case group.FieldName, group.FieldDescription, group.FieldPeakStart, group.FieldPeakEnd, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType, group.FieldDefaultMappedModel:
values[i] = new(sql.NullString)
case group.FieldCreatedAt, group.FieldUpdatedAt, group.FieldDeletedAt:
values[i] = new(sql.NullTime)
@@ -266,6 +290,30 @@ func (_m *Group) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.RateMultiplier = value.Float64
}
case group.FieldPeakRateEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field peak_rate_enabled", values[i])
} else if value.Valid {
_m.PeakRateEnabled = value.Bool
}
case group.FieldPeakStart:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field peak_start", values[i])
} else if value.Valid {
_m.PeakStart = value.String
}
case group.FieldPeakEnd:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field peak_end", values[i])
} else if value.Valid {
_m.PeakEnd = value.String
}
case group.FieldPeakRateMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field peak_rate_multiplier", values[i])
} else if value.Valid {
_m.PeakRateMultiplier = value.Float64
}
case group.FieldIsExclusive:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field is_exclusive", values[i])
@@ -323,6 +371,12 @@ func (_m *Group) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.AllowImageGeneration = value.Bool
}
case group.FieldAllowBatchImageGeneration:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field allow_batch_image_generation", values[i])
} else if value.Valid {
_m.AllowBatchImageGeneration = value.Bool
}
case group.FieldImageRateIndependent:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field image_rate_independent", values[i])
@@ -356,6 +410,51 @@ func (_m *Group) assignValues(columns []string, values []any) error {
_m.ImagePrice4k = new(float64)
*_m.ImagePrice4k = value.Float64
}
case group.FieldBatchImageDiscountMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field batch_image_discount_multiplier", values[i])
} else if value.Valid {
_m.BatchImageDiscountMultiplier = value.Float64
}
case group.FieldBatchImageHoldMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field batch_image_hold_multiplier", values[i])
} else if value.Valid {
_m.BatchImageHoldMultiplier = value.Float64
}
case group.FieldVideoRateIndependent:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field video_rate_independent", values[i])
} else if value.Valid {
_m.VideoRateIndependent = value.Bool
}
case group.FieldVideoRateMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field video_rate_multiplier", values[i])
} else if value.Valid {
_m.VideoRateMultiplier = value.Float64
}
case group.FieldVideoPrice480p:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field video_price_480p", values[i])
} else if value.Valid {
_m.VideoPrice480p = new(float64)
*_m.VideoPrice480p = value.Float64
}
case group.FieldVideoPrice720p:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field video_price_720p", values[i])
} else if value.Valid {
_m.VideoPrice720p = new(float64)
*_m.VideoPrice720p = value.Float64
}
case group.FieldVideoPrice1080p:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field video_price_1080p", values[i])
} else if value.Valid {
_m.VideoPrice1080p = new(float64)
*_m.VideoPrice1080p = value.Float64
}
case group.FieldClaudeCodeOnly:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field claude_code_only", values[i])
@@ -554,6 +653,18 @@ func (_m *Group) String() string {
builder.WriteString("rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
builder.WriteString(", ")
builder.WriteString("peak_rate_enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.PeakRateEnabled))
builder.WriteString(", ")
builder.WriteString("peak_start=")
builder.WriteString(_m.PeakStart)
builder.WriteString(", ")
builder.WriteString("peak_end=")
builder.WriteString(_m.PeakEnd)
builder.WriteString(", ")
builder.WriteString("peak_rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.PeakRateMultiplier))
builder.WriteString(", ")
builder.WriteString("is_exclusive=")
builder.WriteString(fmt.Sprintf("%v", _m.IsExclusive))
builder.WriteString(", ")
@@ -587,6 +698,9 @@ func (_m *Group) String() string {
builder.WriteString("allow_image_generation=")
builder.WriteString(fmt.Sprintf("%v", _m.AllowImageGeneration))
builder.WriteString(", ")
builder.WriteString("allow_batch_image_generation=")
builder.WriteString(fmt.Sprintf("%v", _m.AllowBatchImageGeneration))
builder.WriteString(", ")
builder.WriteString("image_rate_independent=")
builder.WriteString(fmt.Sprintf("%v", _m.ImageRateIndependent))
builder.WriteString(", ")
@@ -608,6 +722,33 @@ func (_m *Group) String() string {
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("batch_image_discount_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.BatchImageDiscountMultiplier))
builder.WriteString(", ")
builder.WriteString("batch_image_hold_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.BatchImageHoldMultiplier))
builder.WriteString(", ")
builder.WriteString("video_rate_independent=")
builder.WriteString(fmt.Sprintf("%v", _m.VideoRateIndependent))
builder.WriteString(", ")
builder.WriteString("video_rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.VideoRateMultiplier))
builder.WriteString(", ")
if v := _m.VideoPrice480p; v != nil {
builder.WriteString("video_price_480p=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.VideoPrice720p; v != nil {
builder.WriteString("video_price_720p=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.VideoPrice1080p; v != nil {
builder.WriteString("video_price_1080p=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("claude_code_only=")
builder.WriteString(fmt.Sprintf("%v", _m.ClaudeCodeOnly))
builder.WriteString(", ")
+118
View File
@@ -28,6 +28,14 @@ const (
FieldDescription = "description"
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
FieldRateMultiplier = "rate_multiplier"
// FieldPeakRateEnabled holds the string denoting the peak_rate_enabled field in the database.
FieldPeakRateEnabled = "peak_rate_enabled"
// FieldPeakStart holds the string denoting the peak_start field in the database.
FieldPeakStart = "peak_start"
// FieldPeakEnd holds the string denoting the peak_end field in the database.
FieldPeakEnd = "peak_end"
// FieldPeakRateMultiplier holds the string denoting the peak_rate_multiplier field in the database.
FieldPeakRateMultiplier = "peak_rate_multiplier"
// FieldIsExclusive holds the string denoting the is_exclusive field in the database.
FieldIsExclusive = "is_exclusive"
// FieldStatus holds the string denoting the status field in the database.
@@ -46,6 +54,8 @@ const (
FieldDefaultValidityDays = "default_validity_days"
// FieldAllowImageGeneration holds the string denoting the allow_image_generation field in the database.
FieldAllowImageGeneration = "allow_image_generation"
// FieldAllowBatchImageGeneration holds the string denoting the allow_batch_image_generation field in the database.
FieldAllowBatchImageGeneration = "allow_batch_image_generation"
// FieldImageRateIndependent holds the string denoting the image_rate_independent field in the database.
FieldImageRateIndependent = "image_rate_independent"
// FieldImageRateMultiplier holds the string denoting the image_rate_multiplier field in the database.
@@ -56,6 +66,20 @@ const (
FieldImagePrice2k = "image_price_2k"
// FieldImagePrice4k holds the string denoting the image_price_4k field in the database.
FieldImagePrice4k = "image_price_4k"
// FieldBatchImageDiscountMultiplier holds the string denoting the batch_image_discount_multiplier field in the database.
FieldBatchImageDiscountMultiplier = "batch_image_discount_multiplier"
// FieldBatchImageHoldMultiplier holds the string denoting the batch_image_hold_multiplier field in the database.
FieldBatchImageHoldMultiplier = "batch_image_hold_multiplier"
// FieldVideoRateIndependent holds the string denoting the video_rate_independent field in the database.
FieldVideoRateIndependent = "video_rate_independent"
// FieldVideoRateMultiplier holds the string denoting the video_rate_multiplier field in the database.
FieldVideoRateMultiplier = "video_rate_multiplier"
// FieldVideoPrice480p holds the string denoting the video_price_480p field in the database.
FieldVideoPrice480p = "video_price_480p"
// FieldVideoPrice720p holds the string denoting the video_price_720p field in the database.
FieldVideoPrice720p = "video_price_720p"
// FieldVideoPrice1080p holds the string denoting the video_price_1080p field in the database.
FieldVideoPrice1080p = "video_price_1080p"
// FieldClaudeCodeOnly holds the string denoting the claude_code_only field in the database.
FieldClaudeCodeOnly = "claude_code_only"
// FieldFallbackGroupID holds the string denoting the fallback_group_id field in the database.
@@ -167,6 +191,10 @@ var Columns = []string{
FieldName,
FieldDescription,
FieldRateMultiplier,
FieldPeakRateEnabled,
FieldPeakStart,
FieldPeakEnd,
FieldPeakRateMultiplier,
FieldIsExclusive,
FieldStatus,
FieldPlatform,
@@ -176,11 +204,19 @@ var Columns = []string{
FieldMonthlyLimitUsd,
FieldDefaultValidityDays,
FieldAllowImageGeneration,
FieldAllowBatchImageGeneration,
FieldImageRateIndependent,
FieldImageRateMultiplier,
FieldImagePrice1k,
FieldImagePrice2k,
FieldImagePrice4k,
FieldBatchImageDiscountMultiplier,
FieldBatchImageHoldMultiplier,
FieldVideoRateIndependent,
FieldVideoRateMultiplier,
FieldVideoPrice480p,
FieldVideoPrice720p,
FieldVideoPrice1080p,
FieldClaudeCodeOnly,
FieldFallbackGroupID,
FieldFallbackGroupIDOnInvalidRequest,
@@ -235,6 +271,18 @@ var (
NameValidator func(string) error
// DefaultRateMultiplier holds the default value on creation for the "rate_multiplier" field.
DefaultRateMultiplier float64
// DefaultPeakRateEnabled holds the default value on creation for the "peak_rate_enabled" field.
DefaultPeakRateEnabled bool
// DefaultPeakStart holds the default value on creation for the "peak_start" field.
DefaultPeakStart string
// PeakStartValidator is a validator for the "peak_start" field. It is called by the builders before save.
PeakStartValidator func(string) error
// DefaultPeakEnd holds the default value on creation for the "peak_end" field.
DefaultPeakEnd string
// PeakEndValidator is a validator for the "peak_end" field. It is called by the builders before save.
PeakEndValidator func(string) error
// DefaultPeakRateMultiplier holds the default value on creation for the "peak_rate_multiplier" field.
DefaultPeakRateMultiplier float64
// DefaultIsExclusive holds the default value on creation for the "is_exclusive" field.
DefaultIsExclusive bool
// DefaultStatus holds the default value on creation for the "status" field.
@@ -253,10 +301,20 @@ var (
DefaultDefaultValidityDays int
// DefaultAllowImageGeneration holds the default value on creation for the "allow_image_generation" field.
DefaultAllowImageGeneration bool
// DefaultAllowBatchImageGeneration holds the default value on creation for the "allow_batch_image_generation" field.
DefaultAllowBatchImageGeneration bool
// DefaultImageRateIndependent holds the default value on creation for the "image_rate_independent" field.
DefaultImageRateIndependent bool
// DefaultImageRateMultiplier holds the default value on creation for the "image_rate_multiplier" field.
DefaultImageRateMultiplier float64
// DefaultBatchImageDiscountMultiplier holds the default value on creation for the "batch_image_discount_multiplier" field.
DefaultBatchImageDiscountMultiplier float64
// DefaultBatchImageHoldMultiplier holds the default value on creation for the "batch_image_hold_multiplier" field.
DefaultBatchImageHoldMultiplier float64
// DefaultVideoRateIndependent holds the default value on creation for the "video_rate_independent" field.
DefaultVideoRateIndependent bool
// DefaultVideoRateMultiplier holds the default value on creation for the "video_rate_multiplier" field.
DefaultVideoRateMultiplier float64
// DefaultClaudeCodeOnly holds the default value on creation for the "claude_code_only" field.
DefaultClaudeCodeOnly bool
// DefaultModelRoutingEnabled holds the default value on creation for the "model_routing_enabled" field.
@@ -323,6 +381,26 @@ func ByRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
}
// ByPeakRateEnabled orders the results by the peak_rate_enabled field.
func ByPeakRateEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPeakRateEnabled, opts...).ToFunc()
}
// ByPeakStart orders the results by the peak_start field.
func ByPeakStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPeakStart, opts...).ToFunc()
}
// ByPeakEnd orders the results by the peak_end field.
func ByPeakEnd(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPeakEnd, opts...).ToFunc()
}
// ByPeakRateMultiplier orders the results by the peak_rate_multiplier field.
func ByPeakRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPeakRateMultiplier, opts...).ToFunc()
}
// ByIsExclusive orders the results by the is_exclusive field.
func ByIsExclusive(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIsExclusive, opts...).ToFunc()
@@ -368,6 +446,11 @@ func ByAllowImageGeneration(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAllowImageGeneration, opts...).ToFunc()
}
// ByAllowBatchImageGeneration orders the results by the allow_batch_image_generation field.
func ByAllowBatchImageGeneration(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAllowBatchImageGeneration, opts...).ToFunc()
}
// ByImageRateIndependent orders the results by the image_rate_independent field.
func ByImageRateIndependent(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldImageRateIndependent, opts...).ToFunc()
@@ -393,6 +476,41 @@ func ByImagePrice4k(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldImagePrice4k, opts...).ToFunc()
}
// ByBatchImageDiscountMultiplier orders the results by the batch_image_discount_multiplier field.
func ByBatchImageDiscountMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBatchImageDiscountMultiplier, opts...).ToFunc()
}
// ByBatchImageHoldMultiplier orders the results by the batch_image_hold_multiplier field.
func ByBatchImageHoldMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBatchImageHoldMultiplier, opts...).ToFunc()
}
// ByVideoRateIndependent orders the results by the video_rate_independent field.
func ByVideoRateIndependent(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoRateIndependent, opts...).ToFunc()
}
// ByVideoRateMultiplier orders the results by the video_rate_multiplier field.
func ByVideoRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoRateMultiplier, opts...).ToFunc()
}
// ByVideoPrice480p orders the results by the video_price_480p field.
func ByVideoPrice480p(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoPrice480p, opts...).ToFunc()
}
// ByVideoPrice720p orders the results by the video_price_720p field.
func ByVideoPrice720p(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoPrice720p, opts...).ToFunc()
}
// ByVideoPrice1080p orders the results by the video_price_1080p field.
func ByVideoPrice1080p(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoPrice1080p, opts...).ToFunc()
}
// ByClaudeCodeOnly orders the results by the claude_code_only field.
func ByClaudeCodeOnly(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldClaudeCodeOnly, opts...).ToFunc()
+530
View File
@@ -85,6 +85,26 @@ func RateMultiplier(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldRateMultiplier, v))
}
// PeakRateEnabled applies equality check predicate on the "peak_rate_enabled" field. It's identical to PeakRateEnabledEQ.
func PeakRateEnabled(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakRateEnabled, v))
}
// PeakStart applies equality check predicate on the "peak_start" field. It's identical to PeakStartEQ.
func PeakStart(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakStart, v))
}
// PeakEnd applies equality check predicate on the "peak_end" field. It's identical to PeakEndEQ.
func PeakEnd(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakEnd, v))
}
// PeakRateMultiplier applies equality check predicate on the "peak_rate_multiplier" field. It's identical to PeakRateMultiplierEQ.
func PeakRateMultiplier(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakRateMultiplier, v))
}
// IsExclusive applies equality check predicate on the "is_exclusive" field. It's identical to IsExclusiveEQ.
func IsExclusive(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldIsExclusive, v))
@@ -130,6 +150,11 @@ func AllowImageGeneration(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldAllowImageGeneration, v))
}
// AllowBatchImageGeneration applies equality check predicate on the "allow_batch_image_generation" field. It's identical to AllowBatchImageGenerationEQ.
func AllowBatchImageGeneration(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldAllowBatchImageGeneration, v))
}
// ImageRateIndependent applies equality check predicate on the "image_rate_independent" field. It's identical to ImageRateIndependentEQ.
func ImageRateIndependent(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldImageRateIndependent, v))
@@ -155,6 +180,41 @@ func ImagePrice4k(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldImagePrice4k, v))
}
// BatchImageDiscountMultiplier applies equality check predicate on the "batch_image_discount_multiplier" field. It's identical to BatchImageDiscountMultiplierEQ.
func BatchImageDiscountMultiplier(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageHoldMultiplier applies equality check predicate on the "batch_image_hold_multiplier" field. It's identical to BatchImageHoldMultiplierEQ.
func BatchImageHoldMultiplier(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldBatchImageHoldMultiplier, v))
}
// VideoRateIndependent applies equality check predicate on the "video_rate_independent" field. It's identical to VideoRateIndependentEQ.
func VideoRateIndependent(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoRateIndependent, v))
}
// VideoRateMultiplier applies equality check predicate on the "video_rate_multiplier" field. It's identical to VideoRateMultiplierEQ.
func VideoRateMultiplier(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoRateMultiplier, v))
}
// VideoPrice480p applies equality check predicate on the "video_price_480p" field. It's identical to VideoPrice480pEQ.
func VideoPrice480p(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice480p, v))
}
// VideoPrice720p applies equality check predicate on the "video_price_720p" field. It's identical to VideoPrice720pEQ.
func VideoPrice720p(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice720p, v))
}
// VideoPrice1080p applies equality check predicate on the "video_price_1080p" field. It's identical to VideoPrice1080pEQ.
func VideoPrice1080p(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice1080p, v))
}
// ClaudeCodeOnly applies equality check predicate on the "claude_code_only" field. It's identical to ClaudeCodeOnlyEQ.
func ClaudeCodeOnly(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
@@ -520,6 +580,186 @@ func RateMultiplierLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldRateMultiplier, v))
}
// PeakRateEnabledEQ applies the EQ predicate on the "peak_rate_enabled" field.
func PeakRateEnabledEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakRateEnabled, v))
}
// PeakRateEnabledNEQ applies the NEQ predicate on the "peak_rate_enabled" field.
func PeakRateEnabledNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldPeakRateEnabled, v))
}
// PeakStartEQ applies the EQ predicate on the "peak_start" field.
func PeakStartEQ(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakStart, v))
}
// PeakStartNEQ applies the NEQ predicate on the "peak_start" field.
func PeakStartNEQ(v string) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldPeakStart, v))
}
// PeakStartIn applies the In predicate on the "peak_start" field.
func PeakStartIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldIn(FieldPeakStart, vs...))
}
// PeakStartNotIn applies the NotIn predicate on the "peak_start" field.
func PeakStartNotIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldPeakStart, vs...))
}
// PeakStartGT applies the GT predicate on the "peak_start" field.
func PeakStartGT(v string) predicate.Group {
return predicate.Group(sql.FieldGT(FieldPeakStart, v))
}
// PeakStartGTE applies the GTE predicate on the "peak_start" field.
func PeakStartGTE(v string) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldPeakStart, v))
}
// PeakStartLT applies the LT predicate on the "peak_start" field.
func PeakStartLT(v string) predicate.Group {
return predicate.Group(sql.FieldLT(FieldPeakStart, v))
}
// PeakStartLTE applies the LTE predicate on the "peak_start" field.
func PeakStartLTE(v string) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldPeakStart, v))
}
// PeakStartContains applies the Contains predicate on the "peak_start" field.
func PeakStartContains(v string) predicate.Group {
return predicate.Group(sql.FieldContains(FieldPeakStart, v))
}
// PeakStartHasPrefix applies the HasPrefix predicate on the "peak_start" field.
func PeakStartHasPrefix(v string) predicate.Group {
return predicate.Group(sql.FieldHasPrefix(FieldPeakStart, v))
}
// PeakStartHasSuffix applies the HasSuffix predicate on the "peak_start" field.
func PeakStartHasSuffix(v string) predicate.Group {
return predicate.Group(sql.FieldHasSuffix(FieldPeakStart, v))
}
// PeakStartEqualFold applies the EqualFold predicate on the "peak_start" field.
func PeakStartEqualFold(v string) predicate.Group {
return predicate.Group(sql.FieldEqualFold(FieldPeakStart, v))
}
// PeakStartContainsFold applies the ContainsFold predicate on the "peak_start" field.
func PeakStartContainsFold(v string) predicate.Group {
return predicate.Group(sql.FieldContainsFold(FieldPeakStart, v))
}
// PeakEndEQ applies the EQ predicate on the "peak_end" field.
func PeakEndEQ(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakEnd, v))
}
// PeakEndNEQ applies the NEQ predicate on the "peak_end" field.
func PeakEndNEQ(v string) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldPeakEnd, v))
}
// PeakEndIn applies the In predicate on the "peak_end" field.
func PeakEndIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldIn(FieldPeakEnd, vs...))
}
// PeakEndNotIn applies the NotIn predicate on the "peak_end" field.
func PeakEndNotIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldPeakEnd, vs...))
}
// PeakEndGT applies the GT predicate on the "peak_end" field.
func PeakEndGT(v string) predicate.Group {
return predicate.Group(sql.FieldGT(FieldPeakEnd, v))
}
// PeakEndGTE applies the GTE predicate on the "peak_end" field.
func PeakEndGTE(v string) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldPeakEnd, v))
}
// PeakEndLT applies the LT predicate on the "peak_end" field.
func PeakEndLT(v string) predicate.Group {
return predicate.Group(sql.FieldLT(FieldPeakEnd, v))
}
// PeakEndLTE applies the LTE predicate on the "peak_end" field.
func PeakEndLTE(v string) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldPeakEnd, v))
}
// PeakEndContains applies the Contains predicate on the "peak_end" field.
func PeakEndContains(v string) predicate.Group {
return predicate.Group(sql.FieldContains(FieldPeakEnd, v))
}
// PeakEndHasPrefix applies the HasPrefix predicate on the "peak_end" field.
func PeakEndHasPrefix(v string) predicate.Group {
return predicate.Group(sql.FieldHasPrefix(FieldPeakEnd, v))
}
// PeakEndHasSuffix applies the HasSuffix predicate on the "peak_end" field.
func PeakEndHasSuffix(v string) predicate.Group {
return predicate.Group(sql.FieldHasSuffix(FieldPeakEnd, v))
}
// PeakEndEqualFold applies the EqualFold predicate on the "peak_end" field.
func PeakEndEqualFold(v string) predicate.Group {
return predicate.Group(sql.FieldEqualFold(FieldPeakEnd, v))
}
// PeakEndContainsFold applies the ContainsFold predicate on the "peak_end" field.
func PeakEndContainsFold(v string) predicate.Group {
return predicate.Group(sql.FieldContainsFold(FieldPeakEnd, v))
}
// PeakRateMultiplierEQ applies the EQ predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldPeakRateMultiplier, v))
}
// PeakRateMultiplierNEQ applies the NEQ predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldPeakRateMultiplier, v))
}
// PeakRateMultiplierIn applies the In predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldPeakRateMultiplier, vs...))
}
// PeakRateMultiplierNotIn applies the NotIn predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldPeakRateMultiplier, vs...))
}
// PeakRateMultiplierGT applies the GT predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldPeakRateMultiplier, v))
}
// PeakRateMultiplierGTE applies the GTE predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldPeakRateMultiplier, v))
}
// PeakRateMultiplierLT applies the LT predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldPeakRateMultiplier, v))
}
// PeakRateMultiplierLTE applies the LTE predicate on the "peak_rate_multiplier" field.
func PeakRateMultiplierLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldPeakRateMultiplier, v))
}
// IsExclusiveEQ applies the EQ predicate on the "is_exclusive" field.
func IsExclusiveEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldIsExclusive, v))
@@ -925,6 +1165,16 @@ func AllowImageGenerationNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldAllowImageGeneration, v))
}
// AllowBatchImageGenerationEQ applies the EQ predicate on the "allow_batch_image_generation" field.
func AllowBatchImageGenerationEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldAllowBatchImageGeneration, v))
}
// AllowBatchImageGenerationNEQ applies the NEQ predicate on the "allow_batch_image_generation" field.
func AllowBatchImageGenerationNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldAllowBatchImageGeneration, v))
}
// ImageRateIndependentEQ applies the EQ predicate on the "image_rate_independent" field.
func ImageRateIndependentEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldImageRateIndependent, v))
@@ -1125,6 +1375,286 @@ func ImagePrice4kNotNil() predicate.Group {
return predicate.Group(sql.FieldNotNull(FieldImagePrice4k))
}
// BatchImageDiscountMultiplierEQ applies the EQ predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageDiscountMultiplierNEQ applies the NEQ predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageDiscountMultiplierIn applies the In predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldBatchImageDiscountMultiplier, vs...))
}
// BatchImageDiscountMultiplierNotIn applies the NotIn predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldBatchImageDiscountMultiplier, vs...))
}
// BatchImageDiscountMultiplierGT applies the GT predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageDiscountMultiplierGTE applies the GTE predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageDiscountMultiplierLT applies the LT predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageDiscountMultiplierLTE applies the LTE predicate on the "batch_image_discount_multiplier" field.
func BatchImageDiscountMultiplierLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldBatchImageDiscountMultiplier, v))
}
// BatchImageHoldMultiplierEQ applies the EQ predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldBatchImageHoldMultiplier, v))
}
// BatchImageHoldMultiplierNEQ applies the NEQ predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldBatchImageHoldMultiplier, v))
}
// BatchImageHoldMultiplierIn applies the In predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldBatchImageHoldMultiplier, vs...))
}
// BatchImageHoldMultiplierNotIn applies the NotIn predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldBatchImageHoldMultiplier, vs...))
}
// BatchImageHoldMultiplierGT applies the GT predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldBatchImageHoldMultiplier, v))
}
// BatchImageHoldMultiplierGTE applies the GTE predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldBatchImageHoldMultiplier, v))
}
// BatchImageHoldMultiplierLT applies the LT predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldBatchImageHoldMultiplier, v))
}
// BatchImageHoldMultiplierLTE applies the LTE predicate on the "batch_image_hold_multiplier" field.
func BatchImageHoldMultiplierLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldBatchImageHoldMultiplier, v))
}
// VideoRateIndependentEQ applies the EQ predicate on the "video_rate_independent" field.
func VideoRateIndependentEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoRateIndependent, v))
}
// VideoRateIndependentNEQ applies the NEQ predicate on the "video_rate_independent" field.
func VideoRateIndependentNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldVideoRateIndependent, v))
}
// VideoRateMultiplierEQ applies the EQ predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoRateMultiplier, v))
}
// VideoRateMultiplierNEQ applies the NEQ predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldVideoRateMultiplier, v))
}
// VideoRateMultiplierIn applies the In predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldVideoRateMultiplier, vs...))
}
// VideoRateMultiplierNotIn applies the NotIn predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldVideoRateMultiplier, vs...))
}
// VideoRateMultiplierGT applies the GT predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldVideoRateMultiplier, v))
}
// VideoRateMultiplierGTE applies the GTE predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldVideoRateMultiplier, v))
}
// VideoRateMultiplierLT applies the LT predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldVideoRateMultiplier, v))
}
// VideoRateMultiplierLTE applies the LTE predicate on the "video_rate_multiplier" field.
func VideoRateMultiplierLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldVideoRateMultiplier, v))
}
// VideoPrice480pEQ applies the EQ predicate on the "video_price_480p" field.
func VideoPrice480pEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice480p, v))
}
// VideoPrice480pNEQ applies the NEQ predicate on the "video_price_480p" field.
func VideoPrice480pNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldVideoPrice480p, v))
}
// VideoPrice480pIn applies the In predicate on the "video_price_480p" field.
func VideoPrice480pIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldVideoPrice480p, vs...))
}
// VideoPrice480pNotIn applies the NotIn predicate on the "video_price_480p" field.
func VideoPrice480pNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldVideoPrice480p, vs...))
}
// VideoPrice480pGT applies the GT predicate on the "video_price_480p" field.
func VideoPrice480pGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldVideoPrice480p, v))
}
// VideoPrice480pGTE applies the GTE predicate on the "video_price_480p" field.
func VideoPrice480pGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldVideoPrice480p, v))
}
// VideoPrice480pLT applies the LT predicate on the "video_price_480p" field.
func VideoPrice480pLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldVideoPrice480p, v))
}
// VideoPrice480pLTE applies the LTE predicate on the "video_price_480p" field.
func VideoPrice480pLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldVideoPrice480p, v))
}
// VideoPrice480pIsNil applies the IsNil predicate on the "video_price_480p" field.
func VideoPrice480pIsNil() predicate.Group {
return predicate.Group(sql.FieldIsNull(FieldVideoPrice480p))
}
// VideoPrice480pNotNil applies the NotNil predicate on the "video_price_480p" field.
func VideoPrice480pNotNil() predicate.Group {
return predicate.Group(sql.FieldNotNull(FieldVideoPrice480p))
}
// VideoPrice720pEQ applies the EQ predicate on the "video_price_720p" field.
func VideoPrice720pEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice720p, v))
}
// VideoPrice720pNEQ applies the NEQ predicate on the "video_price_720p" field.
func VideoPrice720pNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldVideoPrice720p, v))
}
// VideoPrice720pIn applies the In predicate on the "video_price_720p" field.
func VideoPrice720pIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldVideoPrice720p, vs...))
}
// VideoPrice720pNotIn applies the NotIn predicate on the "video_price_720p" field.
func VideoPrice720pNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldVideoPrice720p, vs...))
}
// VideoPrice720pGT applies the GT predicate on the "video_price_720p" field.
func VideoPrice720pGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldVideoPrice720p, v))
}
// VideoPrice720pGTE applies the GTE predicate on the "video_price_720p" field.
func VideoPrice720pGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldVideoPrice720p, v))
}
// VideoPrice720pLT applies the LT predicate on the "video_price_720p" field.
func VideoPrice720pLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldVideoPrice720p, v))
}
// VideoPrice720pLTE applies the LTE predicate on the "video_price_720p" field.
func VideoPrice720pLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldVideoPrice720p, v))
}
// VideoPrice720pIsNil applies the IsNil predicate on the "video_price_720p" field.
func VideoPrice720pIsNil() predicate.Group {
return predicate.Group(sql.FieldIsNull(FieldVideoPrice720p))
}
// VideoPrice720pNotNil applies the NotNil predicate on the "video_price_720p" field.
func VideoPrice720pNotNil() predicate.Group {
return predicate.Group(sql.FieldNotNull(FieldVideoPrice720p))
}
// VideoPrice1080pEQ applies the EQ predicate on the "video_price_1080p" field.
func VideoPrice1080pEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldVideoPrice1080p, v))
}
// VideoPrice1080pNEQ applies the NEQ predicate on the "video_price_1080p" field.
func VideoPrice1080pNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldVideoPrice1080p, v))
}
// VideoPrice1080pIn applies the In predicate on the "video_price_1080p" field.
func VideoPrice1080pIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldVideoPrice1080p, vs...))
}
// VideoPrice1080pNotIn applies the NotIn predicate on the "video_price_1080p" field.
func VideoPrice1080pNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldVideoPrice1080p, vs...))
}
// VideoPrice1080pGT applies the GT predicate on the "video_price_1080p" field.
func VideoPrice1080pGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldVideoPrice1080p, v))
}
// VideoPrice1080pGTE applies the GTE predicate on the "video_price_1080p" field.
func VideoPrice1080pGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldVideoPrice1080p, v))
}
// VideoPrice1080pLT applies the LT predicate on the "video_price_1080p" field.
func VideoPrice1080pLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldVideoPrice1080p, v))
}
// VideoPrice1080pLTE applies the LTE predicate on the "video_price_1080p" field.
func VideoPrice1080pLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldVideoPrice1080p, v))
}
// VideoPrice1080pIsNil applies the IsNil predicate on the "video_price_1080p" field.
func VideoPrice1080pIsNil() predicate.Group {
return predicate.Group(sql.FieldIsNull(FieldVideoPrice1080p))
}
// VideoPrice1080pNotNil applies the NotNil predicate on the "video_price_1080p" field.
func VideoPrice1080pNotNil() predicate.Group {
return predicate.Group(sql.FieldNotNull(FieldVideoPrice1080p))
}
// ClaudeCodeOnlyEQ applies the EQ predicate on the "claude_code_only" field.
func ClaudeCodeOnlyEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
File diff suppressed because it is too large Load Diff
+622
View File
@@ -117,6 +117,69 @@ func (_u *GroupUpdate) AddRateMultiplier(v float64) *GroupUpdate {
return _u
}
// SetPeakRateEnabled sets the "peak_rate_enabled" field.
func (_u *GroupUpdate) SetPeakRateEnabled(v bool) *GroupUpdate {
_u.mutation.SetPeakRateEnabled(v)
return _u
}
// SetNillablePeakRateEnabled sets the "peak_rate_enabled" field if the given value is not nil.
func (_u *GroupUpdate) SetNillablePeakRateEnabled(v *bool) *GroupUpdate {
if v != nil {
_u.SetPeakRateEnabled(*v)
}
return _u
}
// SetPeakStart sets the "peak_start" field.
func (_u *GroupUpdate) SetPeakStart(v string) *GroupUpdate {
_u.mutation.SetPeakStart(v)
return _u
}
// SetNillablePeakStart sets the "peak_start" field if the given value is not nil.
func (_u *GroupUpdate) SetNillablePeakStart(v *string) *GroupUpdate {
if v != nil {
_u.SetPeakStart(*v)
}
return _u
}
// SetPeakEnd sets the "peak_end" field.
func (_u *GroupUpdate) SetPeakEnd(v string) *GroupUpdate {
_u.mutation.SetPeakEnd(v)
return _u
}
// SetNillablePeakEnd sets the "peak_end" field if the given value is not nil.
func (_u *GroupUpdate) SetNillablePeakEnd(v *string) *GroupUpdate {
if v != nil {
_u.SetPeakEnd(*v)
}
return _u
}
// SetPeakRateMultiplier sets the "peak_rate_multiplier" field.
func (_u *GroupUpdate) SetPeakRateMultiplier(v float64) *GroupUpdate {
_u.mutation.ResetPeakRateMultiplier()
_u.mutation.SetPeakRateMultiplier(v)
return _u
}
// SetNillablePeakRateMultiplier sets the "peak_rate_multiplier" field if the given value is not nil.
func (_u *GroupUpdate) SetNillablePeakRateMultiplier(v *float64) *GroupUpdate {
if v != nil {
_u.SetPeakRateMultiplier(*v)
}
return _u
}
// AddPeakRateMultiplier adds value to the "peak_rate_multiplier" field.
func (_u *GroupUpdate) AddPeakRateMultiplier(v float64) *GroupUpdate {
_u.mutation.AddPeakRateMultiplier(v)
return _u
}
// SetIsExclusive sets the "is_exclusive" field.
func (_u *GroupUpdate) SetIsExclusive(v bool) *GroupUpdate {
_u.mutation.SetIsExclusive(v)
@@ -289,6 +352,20 @@ func (_u *GroupUpdate) SetNillableAllowImageGeneration(v *bool) *GroupUpdate {
return _u
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (_u *GroupUpdate) SetAllowBatchImageGeneration(v bool) *GroupUpdate {
_u.mutation.SetAllowBatchImageGeneration(v)
return _u
}
// SetNillableAllowBatchImageGeneration sets the "allow_batch_image_generation" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableAllowBatchImageGeneration(v *bool) *GroupUpdate {
if v != nil {
_u.SetAllowBatchImageGeneration(*v)
}
return _u
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (_u *GroupUpdate) SetImageRateIndependent(v bool) *GroupUpdate {
_u.mutation.SetImageRateIndependent(v)
@@ -405,6 +482,164 @@ func (_u *GroupUpdate) ClearImagePrice4k() *GroupUpdate {
return _u
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (_u *GroupUpdate) SetBatchImageDiscountMultiplier(v float64) *GroupUpdate {
_u.mutation.ResetBatchImageDiscountMultiplier()
_u.mutation.SetBatchImageDiscountMultiplier(v)
return _u
}
// SetNillableBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableBatchImageDiscountMultiplier(v *float64) *GroupUpdate {
if v != nil {
_u.SetBatchImageDiscountMultiplier(*v)
}
return _u
}
// AddBatchImageDiscountMultiplier adds value to the "batch_image_discount_multiplier" field.
func (_u *GroupUpdate) AddBatchImageDiscountMultiplier(v float64) *GroupUpdate {
_u.mutation.AddBatchImageDiscountMultiplier(v)
return _u
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (_u *GroupUpdate) SetBatchImageHoldMultiplier(v float64) *GroupUpdate {
_u.mutation.ResetBatchImageHoldMultiplier()
_u.mutation.SetBatchImageHoldMultiplier(v)
return _u
}
// SetNillableBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableBatchImageHoldMultiplier(v *float64) *GroupUpdate {
if v != nil {
_u.SetBatchImageHoldMultiplier(*v)
}
return _u
}
// AddBatchImageHoldMultiplier adds value to the "batch_image_hold_multiplier" field.
func (_u *GroupUpdate) AddBatchImageHoldMultiplier(v float64) *GroupUpdate {
_u.mutation.AddBatchImageHoldMultiplier(v)
return _u
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (_u *GroupUpdate) SetVideoRateIndependent(v bool) *GroupUpdate {
_u.mutation.SetVideoRateIndependent(v)
return _u
}
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableVideoRateIndependent(v *bool) *GroupUpdate {
if v != nil {
_u.SetVideoRateIndependent(*v)
}
return _u
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (_u *GroupUpdate) SetVideoRateMultiplier(v float64) *GroupUpdate {
_u.mutation.ResetVideoRateMultiplier()
_u.mutation.SetVideoRateMultiplier(v)
return _u
}
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableVideoRateMultiplier(v *float64) *GroupUpdate {
if v != nil {
_u.SetVideoRateMultiplier(*v)
}
return _u
}
// AddVideoRateMultiplier adds value to the "video_rate_multiplier" field.
func (_u *GroupUpdate) AddVideoRateMultiplier(v float64) *GroupUpdate {
_u.mutation.AddVideoRateMultiplier(v)
return _u
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (_u *GroupUpdate) SetVideoPrice480p(v float64) *GroupUpdate {
_u.mutation.ResetVideoPrice480p()
_u.mutation.SetVideoPrice480p(v)
return _u
}
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableVideoPrice480p(v *float64) *GroupUpdate {
if v != nil {
_u.SetVideoPrice480p(*v)
}
return _u
}
// AddVideoPrice480p adds value to the "video_price_480p" field.
func (_u *GroupUpdate) AddVideoPrice480p(v float64) *GroupUpdate {
_u.mutation.AddVideoPrice480p(v)
return _u
}
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
func (_u *GroupUpdate) ClearVideoPrice480p() *GroupUpdate {
_u.mutation.ClearVideoPrice480p()
return _u
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (_u *GroupUpdate) SetVideoPrice720p(v float64) *GroupUpdate {
_u.mutation.ResetVideoPrice720p()
_u.mutation.SetVideoPrice720p(v)
return _u
}
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableVideoPrice720p(v *float64) *GroupUpdate {
if v != nil {
_u.SetVideoPrice720p(*v)
}
return _u
}
// AddVideoPrice720p adds value to the "video_price_720p" field.
func (_u *GroupUpdate) AddVideoPrice720p(v float64) *GroupUpdate {
_u.mutation.AddVideoPrice720p(v)
return _u
}
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
func (_u *GroupUpdate) ClearVideoPrice720p() *GroupUpdate {
_u.mutation.ClearVideoPrice720p()
return _u
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (_u *GroupUpdate) SetVideoPrice1080p(v float64) *GroupUpdate {
_u.mutation.ResetVideoPrice1080p()
_u.mutation.SetVideoPrice1080p(v)
return _u
}
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableVideoPrice1080p(v *float64) *GroupUpdate {
if v != nil {
_u.SetVideoPrice1080p(*v)
}
return _u
}
// AddVideoPrice1080p adds value to the "video_price_1080p" field.
func (_u *GroupUpdate) AddVideoPrice1080p(v float64) *GroupUpdate {
_u.mutation.AddVideoPrice1080p(v)
return _u
}
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
func (_u *GroupUpdate) ClearVideoPrice1080p() *GroupUpdate {
_u.mutation.ClearVideoPrice1080p()
return _u
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdate) SetClaudeCodeOnly(v bool) *GroupUpdate {
_u.mutation.SetClaudeCodeOnly(v)
@@ -921,6 +1156,16 @@ func (_u *GroupUpdate) check() error {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)}
}
}
if v, ok := _u.mutation.PeakStart(); ok {
if err := group.PeakStartValidator(v); err != nil {
return &ValidationError{Name: "peak_start", err: fmt.Errorf(`ent: validator failed for field "Group.peak_start": %w`, err)}
}
}
if v, ok := _u.mutation.PeakEnd(); ok {
if err := group.PeakEndValidator(v); err != nil {
return &ValidationError{Name: "peak_end", err: fmt.Errorf(`ent: validator failed for field "Group.peak_end": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := group.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Group.status": %w`, err)}
@@ -980,6 +1225,21 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(group.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.PeakRateEnabled(); ok {
_spec.SetField(group.FieldPeakRateEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PeakStart(); ok {
_spec.SetField(group.FieldPeakStart, field.TypeString, value)
}
if value, ok := _u.mutation.PeakEnd(); ok {
_spec.SetField(group.FieldPeakEnd, field.TypeString, value)
}
if value, ok := _u.mutation.PeakRateMultiplier(); ok {
_spec.SetField(group.FieldPeakRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedPeakRateMultiplier(); ok {
_spec.AddField(group.FieldPeakRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.IsExclusive(); ok {
_spec.SetField(group.FieldIsExclusive, field.TypeBool, value)
}
@@ -1028,6 +1288,9 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AllowImageGeneration(); ok {
_spec.SetField(group.FieldAllowImageGeneration, field.TypeBool, value)
}
if value, ok := _u.mutation.AllowBatchImageGeneration(); ok {
_spec.SetField(group.FieldAllowBatchImageGeneration, field.TypeBool, value)
}
if value, ok := _u.mutation.ImageRateIndependent(); ok {
_spec.SetField(group.FieldImageRateIndependent, field.TypeBool, value)
}
@@ -1064,6 +1327,54 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.ImagePrice4kCleared() {
_spec.ClearField(group.FieldImagePrice4k, field.TypeFloat64)
}
if value, ok := _u.mutation.BatchImageDiscountMultiplier(); ok {
_spec.SetField(group.FieldBatchImageDiscountMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedBatchImageDiscountMultiplier(); ok {
_spec.AddField(group.FieldBatchImageDiscountMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.BatchImageHoldMultiplier(); ok {
_spec.SetField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedBatchImageHoldMultiplier(); ok {
_spec.AddField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.VideoRateIndependent(); ok {
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
}
if value, ok := _u.mutation.VideoRateMultiplier(); ok {
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoRateMultiplier(); ok {
_spec.AddField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.VideoPrice480p(); ok {
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice480p(); ok {
_spec.AddField(group.FieldVideoPrice480p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice480pCleared() {
_spec.ClearField(group.FieldVideoPrice480p, field.TypeFloat64)
}
if value, ok := _u.mutation.VideoPrice720p(); ok {
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice720p(); ok {
_spec.AddField(group.FieldVideoPrice720p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice720pCleared() {
_spec.ClearField(group.FieldVideoPrice720p, field.TypeFloat64)
}
if value, ok := _u.mutation.VideoPrice1080p(); ok {
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice1080p(); ok {
_spec.AddField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice1080pCleared() {
_spec.ClearField(group.FieldVideoPrice1080p, field.TypeFloat64)
}
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
}
@@ -1530,6 +1841,69 @@ func (_u *GroupUpdateOne) AddRateMultiplier(v float64) *GroupUpdateOne {
return _u
}
// SetPeakRateEnabled sets the "peak_rate_enabled" field.
func (_u *GroupUpdateOne) SetPeakRateEnabled(v bool) *GroupUpdateOne {
_u.mutation.SetPeakRateEnabled(v)
return _u
}
// SetNillablePeakRateEnabled sets the "peak_rate_enabled" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillablePeakRateEnabled(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetPeakRateEnabled(*v)
}
return _u
}
// SetPeakStart sets the "peak_start" field.
func (_u *GroupUpdateOne) SetPeakStart(v string) *GroupUpdateOne {
_u.mutation.SetPeakStart(v)
return _u
}
// SetNillablePeakStart sets the "peak_start" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillablePeakStart(v *string) *GroupUpdateOne {
if v != nil {
_u.SetPeakStart(*v)
}
return _u
}
// SetPeakEnd sets the "peak_end" field.
func (_u *GroupUpdateOne) SetPeakEnd(v string) *GroupUpdateOne {
_u.mutation.SetPeakEnd(v)
return _u
}
// SetNillablePeakEnd sets the "peak_end" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillablePeakEnd(v *string) *GroupUpdateOne {
if v != nil {
_u.SetPeakEnd(*v)
}
return _u
}
// SetPeakRateMultiplier sets the "peak_rate_multiplier" field.
func (_u *GroupUpdateOne) SetPeakRateMultiplier(v float64) *GroupUpdateOne {
_u.mutation.ResetPeakRateMultiplier()
_u.mutation.SetPeakRateMultiplier(v)
return _u
}
// SetNillablePeakRateMultiplier sets the "peak_rate_multiplier" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillablePeakRateMultiplier(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetPeakRateMultiplier(*v)
}
return _u
}
// AddPeakRateMultiplier adds value to the "peak_rate_multiplier" field.
func (_u *GroupUpdateOne) AddPeakRateMultiplier(v float64) *GroupUpdateOne {
_u.mutation.AddPeakRateMultiplier(v)
return _u
}
// SetIsExclusive sets the "is_exclusive" field.
func (_u *GroupUpdateOne) SetIsExclusive(v bool) *GroupUpdateOne {
_u.mutation.SetIsExclusive(v)
@@ -1702,6 +2076,20 @@ func (_u *GroupUpdateOne) SetNillableAllowImageGeneration(v *bool) *GroupUpdateO
return _u
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (_u *GroupUpdateOne) SetAllowBatchImageGeneration(v bool) *GroupUpdateOne {
_u.mutation.SetAllowBatchImageGeneration(v)
return _u
}
// SetNillableAllowBatchImageGeneration sets the "allow_batch_image_generation" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableAllowBatchImageGeneration(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetAllowBatchImageGeneration(*v)
}
return _u
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (_u *GroupUpdateOne) SetImageRateIndependent(v bool) *GroupUpdateOne {
_u.mutation.SetImageRateIndependent(v)
@@ -1818,6 +2206,164 @@ func (_u *GroupUpdateOne) ClearImagePrice4k() *GroupUpdateOne {
return _u
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (_u *GroupUpdateOne) SetBatchImageDiscountMultiplier(v float64) *GroupUpdateOne {
_u.mutation.ResetBatchImageDiscountMultiplier()
_u.mutation.SetBatchImageDiscountMultiplier(v)
return _u
}
// SetNillableBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableBatchImageDiscountMultiplier(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetBatchImageDiscountMultiplier(*v)
}
return _u
}
// AddBatchImageDiscountMultiplier adds value to the "batch_image_discount_multiplier" field.
func (_u *GroupUpdateOne) AddBatchImageDiscountMultiplier(v float64) *GroupUpdateOne {
_u.mutation.AddBatchImageDiscountMultiplier(v)
return _u
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (_u *GroupUpdateOne) SetBatchImageHoldMultiplier(v float64) *GroupUpdateOne {
_u.mutation.ResetBatchImageHoldMultiplier()
_u.mutation.SetBatchImageHoldMultiplier(v)
return _u
}
// SetNillableBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableBatchImageHoldMultiplier(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetBatchImageHoldMultiplier(*v)
}
return _u
}
// AddBatchImageHoldMultiplier adds value to the "batch_image_hold_multiplier" field.
func (_u *GroupUpdateOne) AddBatchImageHoldMultiplier(v float64) *GroupUpdateOne {
_u.mutation.AddBatchImageHoldMultiplier(v)
return _u
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (_u *GroupUpdateOne) SetVideoRateIndependent(v bool) *GroupUpdateOne {
_u.mutation.SetVideoRateIndependent(v)
return _u
}
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableVideoRateIndependent(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetVideoRateIndependent(*v)
}
return _u
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (_u *GroupUpdateOne) SetVideoRateMultiplier(v float64) *GroupUpdateOne {
_u.mutation.ResetVideoRateMultiplier()
_u.mutation.SetVideoRateMultiplier(v)
return _u
}
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableVideoRateMultiplier(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetVideoRateMultiplier(*v)
}
return _u
}
// AddVideoRateMultiplier adds value to the "video_rate_multiplier" field.
func (_u *GroupUpdateOne) AddVideoRateMultiplier(v float64) *GroupUpdateOne {
_u.mutation.AddVideoRateMultiplier(v)
return _u
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (_u *GroupUpdateOne) SetVideoPrice480p(v float64) *GroupUpdateOne {
_u.mutation.ResetVideoPrice480p()
_u.mutation.SetVideoPrice480p(v)
return _u
}
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableVideoPrice480p(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetVideoPrice480p(*v)
}
return _u
}
// AddVideoPrice480p adds value to the "video_price_480p" field.
func (_u *GroupUpdateOne) AddVideoPrice480p(v float64) *GroupUpdateOne {
_u.mutation.AddVideoPrice480p(v)
return _u
}
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
func (_u *GroupUpdateOne) ClearVideoPrice480p() *GroupUpdateOne {
_u.mutation.ClearVideoPrice480p()
return _u
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (_u *GroupUpdateOne) SetVideoPrice720p(v float64) *GroupUpdateOne {
_u.mutation.ResetVideoPrice720p()
_u.mutation.SetVideoPrice720p(v)
return _u
}
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableVideoPrice720p(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetVideoPrice720p(*v)
}
return _u
}
// AddVideoPrice720p adds value to the "video_price_720p" field.
func (_u *GroupUpdateOne) AddVideoPrice720p(v float64) *GroupUpdateOne {
_u.mutation.AddVideoPrice720p(v)
return _u
}
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
func (_u *GroupUpdateOne) ClearVideoPrice720p() *GroupUpdateOne {
_u.mutation.ClearVideoPrice720p()
return _u
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (_u *GroupUpdateOne) SetVideoPrice1080p(v float64) *GroupUpdateOne {
_u.mutation.ResetVideoPrice1080p()
_u.mutation.SetVideoPrice1080p(v)
return _u
}
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableVideoPrice1080p(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetVideoPrice1080p(*v)
}
return _u
}
// AddVideoPrice1080p adds value to the "video_price_1080p" field.
func (_u *GroupUpdateOne) AddVideoPrice1080p(v float64) *GroupUpdateOne {
_u.mutation.AddVideoPrice1080p(v)
return _u
}
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
func (_u *GroupUpdateOne) ClearVideoPrice1080p() *GroupUpdateOne {
_u.mutation.ClearVideoPrice1080p()
return _u
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdateOne) SetClaudeCodeOnly(v bool) *GroupUpdateOne {
_u.mutation.SetClaudeCodeOnly(v)
@@ -2347,6 +2893,16 @@ func (_u *GroupUpdateOne) check() error {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Group.name": %w`, err)}
}
}
if v, ok := _u.mutation.PeakStart(); ok {
if err := group.PeakStartValidator(v); err != nil {
return &ValidationError{Name: "peak_start", err: fmt.Errorf(`ent: validator failed for field "Group.peak_start": %w`, err)}
}
}
if v, ok := _u.mutation.PeakEnd(); ok {
if err := group.PeakEndValidator(v); err != nil {
return &ValidationError{Name: "peak_end", err: fmt.Errorf(`ent: validator failed for field "Group.peak_end": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := group.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Group.status": %w`, err)}
@@ -2423,6 +2979,21 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(group.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.PeakRateEnabled(); ok {
_spec.SetField(group.FieldPeakRateEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PeakStart(); ok {
_spec.SetField(group.FieldPeakStart, field.TypeString, value)
}
if value, ok := _u.mutation.PeakEnd(); ok {
_spec.SetField(group.FieldPeakEnd, field.TypeString, value)
}
if value, ok := _u.mutation.PeakRateMultiplier(); ok {
_spec.SetField(group.FieldPeakRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedPeakRateMultiplier(); ok {
_spec.AddField(group.FieldPeakRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.IsExclusive(); ok {
_spec.SetField(group.FieldIsExclusive, field.TypeBool, value)
}
@@ -2471,6 +3042,9 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if value, ok := _u.mutation.AllowImageGeneration(); ok {
_spec.SetField(group.FieldAllowImageGeneration, field.TypeBool, value)
}
if value, ok := _u.mutation.AllowBatchImageGeneration(); ok {
_spec.SetField(group.FieldAllowBatchImageGeneration, field.TypeBool, value)
}
if value, ok := _u.mutation.ImageRateIndependent(); ok {
_spec.SetField(group.FieldImageRateIndependent, field.TypeBool, value)
}
@@ -2507,6 +3081,54 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if _u.mutation.ImagePrice4kCleared() {
_spec.ClearField(group.FieldImagePrice4k, field.TypeFloat64)
}
if value, ok := _u.mutation.BatchImageDiscountMultiplier(); ok {
_spec.SetField(group.FieldBatchImageDiscountMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedBatchImageDiscountMultiplier(); ok {
_spec.AddField(group.FieldBatchImageDiscountMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.BatchImageHoldMultiplier(); ok {
_spec.SetField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedBatchImageHoldMultiplier(); ok {
_spec.AddField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.VideoRateIndependent(); ok {
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
}
if value, ok := _u.mutation.VideoRateMultiplier(); ok {
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoRateMultiplier(); ok {
_spec.AddField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.VideoPrice480p(); ok {
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice480p(); ok {
_spec.AddField(group.FieldVideoPrice480p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice480pCleared() {
_spec.ClearField(group.FieldVideoPrice480p, field.TypeFloat64)
}
if value, ok := _u.mutation.VideoPrice720p(); ok {
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice720p(); ok {
_spec.AddField(group.FieldVideoPrice720p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice720pCleared() {
_spec.ClearField(group.FieldVideoPrice720p, field.TypeFloat64)
}
if value, ok := _u.mutation.VideoPrice1080p(); ok {
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedVideoPrice1080p(); ok {
_spec.AddField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
}
if _u.mutation.VideoPrice1080pCleared() {
_spec.ClearField(group.FieldVideoPrice1080p, field.TypeFloat64)
}
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
}
+36
View File
@@ -93,6 +93,42 @@ func (f AuthIdentityChannelFunc) Mutate(ctx context.Context, m ent.Mutation) (en
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AuthIdentityChannelMutation", m)
}
// The BatchImageEventFunc type is an adapter to allow the use of ordinary
// function as BatchImageEvent mutator.
type BatchImageEventFunc func(context.Context, *ent.BatchImageEventMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f BatchImageEventFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.BatchImageEventMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.BatchImageEventMutation", m)
}
// The BatchImageItemFunc type is an adapter to allow the use of ordinary
// function as BatchImageItem mutator.
type BatchImageItemFunc func(context.Context, *ent.BatchImageItemMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f BatchImageItemFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.BatchImageItemMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.BatchImageItemMutation", m)
}
// The BatchImageJobFunc type is an adapter to allow the use of ordinary
// function as BatchImageJob mutator.
type BatchImageJobFunc func(context.Context, *ent.BatchImageJobMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f BatchImageJobFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.BatchImageJobMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.BatchImageJobMutation", m)
}
// The ChannelMonitorFunc type is an adapter to allow the use of ordinary
// function as ChannelMonitor mutator.
type ChannelMonitorFunc func(context.Context, *ent.ChannelMonitorMutation) (ent.Value, error)
+90
View File
@@ -15,6 +15,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
@@ -291,6 +294,87 @@ func (f TraverseAuthIdentityChannel) Traverse(ctx context.Context, q ent.Query)
return fmt.Errorf("unexpected query type %T. expect *ent.AuthIdentityChannelQuery", q)
}
// The BatchImageEventFunc type is an adapter to allow the use of ordinary function as a Querier.
type BatchImageEventFunc func(context.Context, *ent.BatchImageEventQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f BatchImageEventFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.BatchImageEventQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.BatchImageEventQuery", q)
}
// The TraverseBatchImageEvent type is an adapter to allow the use of ordinary function as Traverser.
type TraverseBatchImageEvent func(context.Context, *ent.BatchImageEventQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseBatchImageEvent) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseBatchImageEvent) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.BatchImageEventQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.BatchImageEventQuery", q)
}
// The BatchImageItemFunc type is an adapter to allow the use of ordinary function as a Querier.
type BatchImageItemFunc func(context.Context, *ent.BatchImageItemQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f BatchImageItemFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.BatchImageItemQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.BatchImageItemQuery", q)
}
// The TraverseBatchImageItem type is an adapter to allow the use of ordinary function as Traverser.
type TraverseBatchImageItem func(context.Context, *ent.BatchImageItemQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseBatchImageItem) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseBatchImageItem) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.BatchImageItemQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.BatchImageItemQuery", q)
}
// The BatchImageJobFunc type is an adapter to allow the use of ordinary function as a Querier.
type BatchImageJobFunc func(context.Context, *ent.BatchImageJobQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f BatchImageJobFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.BatchImageJobQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.BatchImageJobQuery", q)
}
// The TraverseBatchImageJob type is an adapter to allow the use of ordinary function as Traverser.
type TraverseBatchImageJob func(context.Context, *ent.BatchImageJobQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseBatchImageJob) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseBatchImageJob) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.BatchImageJobQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.BatchImageJobQuery", q)
}
// The ChannelMonitorFunc type is an adapter to allow the use of ordinary function as a Querier.
type ChannelMonitorFunc func(context.Context, *ent.ChannelMonitorQuery) (ent.Value, error)
@@ -1064,6 +1148,12 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.AuthIdentityQuery, predicate.AuthIdentity, authidentity.OrderOption]{typ: ent.TypeAuthIdentity, tq: q}, nil
case *ent.AuthIdentityChannelQuery:
return &query[*ent.AuthIdentityChannelQuery, predicate.AuthIdentityChannel, authidentitychannel.OrderOption]{typ: ent.TypeAuthIdentityChannel, tq: q}, nil
case *ent.BatchImageEventQuery:
return &query[*ent.BatchImageEventQuery, predicate.BatchImageEvent, batchimageevent.OrderOption]{typ: ent.TypeBatchImageEvent, tq: q}, nil
case *ent.BatchImageItemQuery:
return &query[*ent.BatchImageItemQuery, predicate.BatchImageItem, batchimageitem.OrderOption]{typ: ent.TypeBatchImageItem, tq: q}, nil
case *ent.BatchImageJobQuery:
return &query[*ent.BatchImageJobQuery, predicate.BatchImageJob, batchimagejob.OrderOption]{typ: ent.TypeBatchImageJob, tq: q}, nil
case *ent.ChannelMonitorQuery:
return &query[*ent.ChannelMonitorQuery, predicate.ChannelMonitor, channelmonitor.OrderOption]{typ: ent.TypeChannelMonitor, tq: q}, nil
case *ent.ChannelMonitorDailyRollupQuery:
@@ -46,6 +46,16 @@ func TestPaymentOrdersOutTradeNoPartialUniqueIndex(t *testing.T) {
require.Equal(t, (&entsql.IndexAnnotation{Where: "out_trade_no <> ''"}).Where, idx.Annotation.Where)
}
func TestAccountsParentAccountForeignKey(t *testing.T) {
fk := findForeignKeyByColumn(t, AccountsTable, "parent_account_id")
require.Len(t, fk.Columns, 1)
require.Equal(t, "parent_account_id", fk.Columns[0].Name)
require.False(t, fk.Columns[0].Unique, "active-shadow uniqueness is enforced by the partial uq_accounts_spark_shadow_per_parent index")
require.Len(t, fk.RefColumns, 1)
require.Equal(t, "id", fk.RefColumns[0].Name)
require.Equal(t, entschema.Restrict, fk.OnDelete)
}
func findForeignKeyBySymbol(t *testing.T, table *entschema.Table, symbol string) *entschema.ForeignKey {
t.Helper()
@@ -71,3 +81,18 @@ func findIndexByName(t *testing.T, table *entschema.Table, name string) *entsche
require.Failf(t, "missing index", "table %s should include index %s", table.Name, name)
return nil
}
func findForeignKeyByColumn(t *testing.T, table *entschema.Table, column string) *entschema.ForeignKey {
t.Helper()
for _, fk := range table.ForeignKeys {
for _, col := range fk.Columns {
if col.Name == column {
return fk
}
}
}
require.Failf(t, "missing foreign key", "table %s should include foreign key for column %s", table.Name, column)
return nil
}
+250 -25
View File
@@ -124,7 +124,9 @@ var (
{Name: "session_window_start", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_end", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_status", Type: field.TypeString, Nullable: true, Size: 20},
{Name: "quota_dimension", Type: field.TypeEnum, Enums: []string{"global", "spark"}, Default: "global"},
{Name: "proxy_id", Type: field.TypeInt64, Nullable: true},
{Name: "parent_account_id", Type: field.TypeInt64, Nullable: true},
}
// AccountsTable holds the schema information for the "accounts" table.
AccountsTable = &schema.Table{
@@ -134,10 +136,16 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "accounts_proxies_proxy",
Columns: []*schema.Column{AccountsColumns[29]},
Columns: []*schema.Column{AccountsColumns[30]},
RefColumns: []*schema.Column{ProxiesColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "accounts_accounts_children",
Columns: []*schema.Column{AccountsColumns[31]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.Restrict,
},
},
Indexes: []*schema.Index{
{
@@ -158,7 +166,7 @@ var (
{
Name: "account_proxy_id",
Unique: false,
Columns: []*schema.Column{AccountsColumns[29]},
Columns: []*schema.Column{AccountsColumns[30]},
},
{
Name: "account_priority",
@@ -205,6 +213,11 @@ var (
Unique: false,
Columns: []*schema.Column{AccountsColumns[3]},
},
{
Name: "account_parent_account_id",
Unique: false,
Columns: []*schema.Column{AccountsColumns[31]},
},
},
}
// AccountGroupsColumns holds the columns for the "account_groups" table.
@@ -422,6 +435,188 @@ var (
},
},
}
// BatchImageEventsColumns holds the columns for the "batch_image_events" table.
BatchImageEventsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "job_id", Type: field.TypeString, Size: 64},
{Name: "event_type", Type: field.TypeString, Size: 64},
{Name: "payload", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "event_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageEventsTable holds the schema information for the "batch_image_events" table.
BatchImageEventsTable = &schema.Table{
Name: "batch_image_events",
Columns: BatchImageEventsColumns,
PrimaryKey: []*schema.Column{BatchImageEventsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimageevent_job_id_created_at",
Unique: false,
Columns: []*schema.Column{BatchImageEventsColumns[1], BatchImageEventsColumns[5]},
},
{
Name: "batchimageevent_event_type",
Unique: false,
Columns: []*schema.Column{BatchImageEventsColumns[2]},
},
{
Name: "batchimageevent_job_id_event_hash",
Unique: true,
Columns: []*schema.Column{BatchImageEventsColumns[1], BatchImageEventsColumns[4]},
Annotation: &entsql.IndexAnnotation{
Where: "event_hash IS NOT NULL AND event_hash <> ''",
},
},
},
}
// BatchImageItemsColumns holds the columns for the "batch_image_items" table.
BatchImageItemsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "job_id", Type: field.TypeString, Size: 64},
{Name: "custom_id", Type: field.TypeString, Size: 255},
{Name: "status", Type: field.TypeString, Size: 32},
{Name: "request_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "prompt_preview", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "provider_source_object", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "source_line_number", Type: field.TypeInt, Nullable: true},
{Name: "source_byte_offset", Type: field.TypeInt64, Nullable: true},
{Name: "source_byte_length", Type: field.TypeInt64, Nullable: true},
{Name: "mime_type", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "file_extension", Type: field.TypeString, Nullable: true, Size: 32},
{Name: "image_count", Type: field.TypeInt, Default: 0},
{Name: "error_code", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "billed_amount", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "indexed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageItemsTable holds the schema information for the "batch_image_items" table.
BatchImageItemsTable = &schema.Table{
Name: "batch_image_items",
Columns: BatchImageItemsColumns,
PrimaryKey: []*schema.Column{BatchImageItemsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimageitem_job_id_custom_id",
Unique: true,
Columns: []*schema.Column{BatchImageItemsColumns[1], BatchImageItemsColumns[2]},
},
{
Name: "batchimageitem_job_id_status",
Unique: false,
Columns: []*schema.Column{BatchImageItemsColumns[1], BatchImageItemsColumns[3]},
},
{
Name: "batchimageitem_provider_source_object",
Unique: false,
Columns: []*schema.Column{BatchImageItemsColumns[6]},
},
},
}
// BatchImageJobsColumns holds the columns for the "batch_image_jobs" table.
BatchImageJobsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "batch_id", Type: field.TypeString, Size: 64},
{Name: "user_id", Type: field.TypeInt64},
{Name: "api_key_id", Type: field.TypeInt64, Nullable: true},
{Name: "account_id", Type: field.TypeInt64, Nullable: true},
{Name: "provider", Type: field.TypeString, Size: 32},
{Name: "model", Type: field.TypeString, Size: 128},
{Name: "task_name", Type: field.TypeString, Size: 255, Default: ""},
{Name: "status", Type: field.TypeString, Size: 32, Default: "created"},
{Name: "provider_job_name", Type: field.TypeString, Nullable: true, Size: 512},
{Name: "provider_input_ref", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "provider_output_ref", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "gcs_input_uri", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "gcs_output_uri", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "item_count", Type: field.TypeInt},
{Name: "success_count", Type: field.TypeInt, Default: 0},
{Name: "fail_count", Type: field.TypeInt, Default: 0},
{Name: "cancelled_count", Type: field.TypeInt, Default: 0},
{Name: "estimated_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "hold_amount", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "actual_cost", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "currency", Type: field.TypeString, Size: 16, Default: "USD"},
{Name: "hold_id", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "idempotency_key", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "request_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "manifest_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "retry_count", Type: field.TypeInt, Default: 0},
{Name: "version", Type: field.TypeInt, Default: 0},
{Name: "output_expires_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "input_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "output_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "downloaded_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "user_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "last_error_code", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "last_error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "submitted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "started_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "finished_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "settled_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageJobsTable holds the schema information for the "batch_image_jobs" table.
BatchImageJobsTable = &schema.Table{
Name: "batch_image_jobs",
Columns: BatchImageJobsColumns,
PrimaryKey: []*schema.Column{BatchImageJobsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimagejob_batch_id",
Unique: true,
Columns: []*schema.Column{BatchImageJobsColumns[1]},
},
{
Name: "batchimagejob_user_id_created_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[2], BatchImageJobsColumns[35]},
},
{
Name: "batchimagejob_status",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[8]},
},
{
Name: "batchimagejob_provider_status",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[5], BatchImageJobsColumns[8]},
},
{
Name: "batchimagejob_idempotency_key",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[23]},
Annotation: &entsql.IndexAnnotation{
Where: "idempotency_key IS NOT NULL AND idempotency_key <> ''",
},
},
{
Name: "batchimagejob_manifest_hash",
Unique: true,
Columns: []*schema.Column{BatchImageJobsColumns[25]},
Annotation: &entsql.IndexAnnotation{
Where: "manifest_hash IS NOT NULL AND manifest_hash <> ''",
},
},
{
Name: "batchimagejob_output_expires_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[28]},
},
{
Name: "batchimagejob_downloaded_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[31]},
},
{
Name: "batchimagejob_user_deleted_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[32]},
},
},
}
// ChannelMonitorsColumns holds the columns for the "channel_monitors" table.
ChannelMonitorsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -437,6 +632,7 @@ var (
{Name: "group_name", Type: field.TypeString, Nullable: true, Size: 100, Default: ""},
{Name: "enabled", Type: field.TypeBool, Default: true},
{Name: "interval_seconds", Type: field.TypeInt},
{Name: "jitter_seconds", Type: field.TypeInt, Default: 0},
{Name: "last_checked_at", Type: field.TypeTime, Nullable: true},
{Name: "created_by", Type: field.TypeInt64},
{Name: "extra_headers", Type: field.TypeJSON},
@@ -452,7 +648,7 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "channel_monitors_channel_monitor_request_templates_request_template",
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
Columns: []*schema.Column{ChannelMonitorsColumns[19]},
RefColumns: []*schema.Column{ChannelMonitorRequestTemplatesColumns[0]},
OnDelete: schema.SetNull,
},
@@ -461,7 +657,7 @@ var (
{
Name: "channelmonitor_enabled_last_checked_at",
Unique: false,
Columns: []*schema.Column{ChannelMonitorsColumns[11], ChannelMonitorsColumns[13]},
Columns: []*schema.Column{ChannelMonitorsColumns[11], ChannelMonitorsColumns[14]},
},
{
Name: "channelmonitor_provider",
@@ -481,7 +677,7 @@ var (
{
Name: "channelmonitor_template_id",
Unique: false,
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
Columns: []*schema.Column{ChannelMonitorsColumns[19]},
},
},
}
@@ -643,6 +839,10 @@ var (
{Name: "name", Type: field.TypeString, Size: 100},
{Name: "description", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "peak_rate_enabled", Type: field.TypeBool, Default: false},
{Name: "peak_start", Type: field.TypeString, Size: 5, Default: ""},
{Name: "peak_end", Type: field.TypeString, Size: 5, Default: ""},
{Name: "peak_rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "is_exclusive", Type: field.TypeBool, Default: false},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
{Name: "platform", Type: field.TypeString, Size: 50, Default: "anthropic"},
@@ -652,11 +852,19 @@ var (
{Name: "monthly_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "default_validity_days", Type: field.TypeInt, Default: 30},
{Name: "allow_image_generation", Type: field.TypeBool, Default: false},
{Name: "allow_batch_image_generation", Type: field.TypeBool, Default: false},
{Name: "image_rate_independent", Type: field.TypeBool, Default: false},
{Name: "image_rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "image_price_1k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "image_price_2k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "image_price_4k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "batch_image_discount_multiplier", Type: field.TypeFloat64, Default: 0.5, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "batch_image_hold_multiplier", Type: field.TypeFloat64, Default: 0.6, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "video_rate_independent", Type: field.TypeBool, Default: false},
{Name: "video_rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "video_price_480p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "video_price_720p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "video_price_1080p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
{Name: "fallback_group_id_on_invalid_request", Type: field.TypeInt64, Nullable: true},
@@ -682,22 +890,22 @@ var (
{
Name: "group_status",
Unique: false,
Columns: []*schema.Column{GroupsColumns[8]},
Columns: []*schema.Column{GroupsColumns[12]},
},
{
Name: "group_platform",
Unique: false,
Columns: []*schema.Column{GroupsColumns[9]},
Columns: []*schema.Column{GroupsColumns[13]},
},
{
Name: "group_subscription_type",
Unique: false,
Columns: []*schema.Column{GroupsColumns[10]},
Columns: []*schema.Column{GroupsColumns[14]},
},
{
Name: "group_is_exclusive",
Unique: false,
Columns: []*schema.Column{GroupsColumns[7]},
Columns: []*schema.Column{GroupsColumns[11]},
},
{
Name: "group_deleted_at",
@@ -707,7 +915,7 @@ var (
{
Name: "group_sort_order",
Unique: false,
Columns: []*schema.Column{GroupsColumns[28]},
Columns: []*schema.Column{GroupsColumns[40]},
},
},
}
@@ -1364,6 +1572,9 @@ var (
{Name: "image_output_size", Type: field.TypeString, Nullable: true, Size: 32},
{Name: "image_size_source", Type: field.TypeString, Nullable: true, Size: 16},
{Name: "image_size_breakdown", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "video_count", Type: field.TypeInt, Default: 0},
{Name: "video_resolution", Type: field.TypeString, Nullable: true, Size: 10},
{Name: "video_duration_seconds", Type: field.TypeInt, Nullable: true},
{Name: "cache_ttl_overridden", Type: field.TypeBool, Default: false},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "api_key_id", Type: field.TypeInt64},
@@ -1380,31 +1591,31 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "usage_logs_api_keys_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[40]},
RefColumns: []*schema.Column{APIKeysColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_accounts_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[41]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_groups_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[42]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "usage_logs_users_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[43]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_user_subscriptions_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[44]},
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
OnDelete: schema.SetNull,
},
@@ -1413,32 +1624,32 @@ var (
{
Name: "usagelog_user_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[43]},
},
{
Name: "usagelog_api_key_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[40]},
},
{
Name: "usagelog_account_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[41]},
},
{
Name: "usagelog_group_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[42]},
},
{
Name: "usagelog_subscription_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[44]},
},
{
Name: "usagelog_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[39]},
},
{
Name: "usagelog_model",
@@ -1458,17 +1669,17 @@ var (
{
Name: "usagelog_user_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[43], UsageLogsColumns[39]},
},
{
Name: "usagelog_api_key_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[40], UsageLogsColumns[39]},
},
{
Name: "usagelog_group_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[42], UsageLogsColumns[39]},
},
},
}
@@ -1482,6 +1693,7 @@ var (
{Name: "password_hash", Type: field.TypeString, Size: 255},
{Name: "role", Type: field.TypeString, Size: 20, Default: "user"},
{Name: "balance", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "frozen_balance", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "concurrency", Type: field.TypeInt, Default: 5},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
{Name: "username", Type: field.TypeString, Size: 100, Default: ""},
@@ -1508,7 +1720,7 @@ var (
{
Name: "user_status",
Unique: false,
Columns: []*schema.Column{UsersColumns[9]},
Columns: []*schema.Column{UsersColumns[10]},
},
{
Name: "user_deleted_at",
@@ -1781,6 +1993,9 @@ var (
AnnouncementReadsTable,
AuthIdentitiesTable,
AuthIdentityChannelsTable,
BatchImageEventsTable,
BatchImageItemsTable,
BatchImageJobsTable,
ChannelMonitorsTable,
ChannelMonitorDailyRollupsTable,
ChannelMonitorHistoriesTable,
@@ -1819,6 +2034,7 @@ func init() {
Table: "api_keys",
}
AccountsTable.ForeignKeys[0].RefTable = ProxiesTable
AccountsTable.ForeignKeys[1].RefTable = AccountsTable
AccountsTable.Annotation = &entsql.Annotation{
Table: "accounts",
}
@@ -1843,6 +2059,15 @@ func init() {
AuthIdentityChannelsTable.Annotation = &entsql.Annotation{
Table: "auth_identity_channels",
}
BatchImageEventsTable.Annotation = &entsql.Annotation{
Table: "batch_image_events",
}
BatchImageItemsTable.Annotation = &entsql.Annotation{
Table: "batch_image_items",
}
BatchImageJobsTable.Annotation = &entsql.Annotation{
Table: "batch_image_jobs",
}
ChannelMonitorsTable.ForeignKeys[0].RefTable = ChannelMonitorRequestTemplatesTable
ChannelMonitorsTable.Annotation = &entsql.Annotation{
Table: "channel_monitors",
+7133 -8
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -27,6 +27,15 @@ type AuthIdentity func(*sql.Selector)
// AuthIdentityChannel is the predicate function for authidentitychannel builders.
type AuthIdentityChannel func(*sql.Selector)
// BatchImageEvent is the predicate function for batchimageevent builders.
type BatchImageEvent func(*sql.Selector)
// BatchImageItem is the predicate function for batchimageitem builders.
type BatchImageItem func(*sql.Selector)
// BatchImageJob is the predicate function for batchimagejob builders.
type BatchImageJob func(*sql.Selector)
// ChannelMonitor is the predicate function for channelmonitor builders.
type ChannelMonitor func(*sql.Selector)
+262 -35
View File
@@ -12,6 +12,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/batchimageevent"
"github.com/Wei-Shaw/sub2api/ent/batchimageitem"
"github.com/Wei-Shaw/sub2api/ent/batchimagejob"
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
@@ -432,6 +435,172 @@ func init() {
authidentitychannelDescMetadata := authidentitychannelFields[6].Descriptor()
// authidentitychannel.DefaultMetadata holds the default value on creation for the metadata field.
authidentitychannel.DefaultMetadata = authidentitychannelDescMetadata.Default.(func() map[string]interface{})
batchimageeventFields := schema.BatchImageEvent{}.Fields()
_ = batchimageeventFields
// batchimageeventDescJobID is the schema descriptor for job_id field.
batchimageeventDescJobID := batchimageeventFields[0].Descriptor()
// batchimageevent.JobIDValidator is a validator for the "job_id" field. It is called by the builders before save.
batchimageevent.JobIDValidator = batchimageeventDescJobID.Validators[0].(func(string) error)
// batchimageeventDescEventType is the schema descriptor for event_type field.
batchimageeventDescEventType := batchimageeventFields[1].Descriptor()
// batchimageevent.EventTypeValidator is a validator for the "event_type" field. It is called by the builders before save.
batchimageevent.EventTypeValidator = batchimageeventDescEventType.Validators[0].(func(string) error)
// batchimageeventDescEventHash is the schema descriptor for event_hash field.
batchimageeventDescEventHash := batchimageeventFields[3].Descriptor()
// batchimageevent.EventHashValidator is a validator for the "event_hash" field. It is called by the builders before save.
batchimageevent.EventHashValidator = batchimageeventDescEventHash.Validators[0].(func(string) error)
// batchimageeventDescCreatedAt is the schema descriptor for created_at field.
batchimageeventDescCreatedAt := batchimageeventFields[4].Descriptor()
// batchimageevent.DefaultCreatedAt holds the default value on creation for the created_at field.
batchimageevent.DefaultCreatedAt = batchimageeventDescCreatedAt.Default.(func() time.Time)
batchimageitemFields := schema.BatchImageItem{}.Fields()
_ = batchimageitemFields
// batchimageitemDescJobID is the schema descriptor for job_id field.
batchimageitemDescJobID := batchimageitemFields[0].Descriptor()
// batchimageitem.JobIDValidator is a validator for the "job_id" field. It is called by the builders before save.
batchimageitem.JobIDValidator = batchimageitemDescJobID.Validators[0].(func(string) error)
// batchimageitemDescCustomID is the schema descriptor for custom_id field.
batchimageitemDescCustomID := batchimageitemFields[1].Descriptor()
// batchimageitem.CustomIDValidator is a validator for the "custom_id" field. It is called by the builders before save.
batchimageitem.CustomIDValidator = batchimageitemDescCustomID.Validators[0].(func(string) error)
// batchimageitemDescStatus is the schema descriptor for status field.
batchimageitemDescStatus := batchimageitemFields[2].Descriptor()
// batchimageitem.StatusValidator is a validator for the "status" field. It is called by the builders before save.
batchimageitem.StatusValidator = batchimageitemDescStatus.Validators[0].(func(string) error)
// batchimageitemDescRequestHash is the schema descriptor for request_hash field.
batchimageitemDescRequestHash := batchimageitemFields[3].Descriptor()
// batchimageitem.RequestHashValidator is a validator for the "request_hash" field. It is called by the builders before save.
batchimageitem.RequestHashValidator = batchimageitemDescRequestHash.Validators[0].(func(string) error)
// batchimageitemDescProviderSourceObject is the schema descriptor for provider_source_object field.
batchimageitemDescProviderSourceObject := batchimageitemFields[5].Descriptor()
// batchimageitem.ProviderSourceObjectValidator is a validator for the "provider_source_object" field. It is called by the builders before save.
batchimageitem.ProviderSourceObjectValidator = batchimageitemDescProviderSourceObject.Validators[0].(func(string) error)
// batchimageitemDescMimeType is the schema descriptor for mime_type field.
batchimageitemDescMimeType := batchimageitemFields[9].Descriptor()
// batchimageitem.MimeTypeValidator is a validator for the "mime_type" field. It is called by the builders before save.
batchimageitem.MimeTypeValidator = batchimageitemDescMimeType.Validators[0].(func(string) error)
// batchimageitemDescFileExtension is the schema descriptor for file_extension field.
batchimageitemDescFileExtension := batchimageitemFields[10].Descriptor()
// batchimageitem.FileExtensionValidator is a validator for the "file_extension" field. It is called by the builders before save.
batchimageitem.FileExtensionValidator = batchimageitemDescFileExtension.Validators[0].(func(string) error)
// batchimageitemDescImageCount is the schema descriptor for image_count field.
batchimageitemDescImageCount := batchimageitemFields[11].Descriptor()
// batchimageitem.DefaultImageCount holds the default value on creation for the image_count field.
batchimageitem.DefaultImageCount = batchimageitemDescImageCount.Default.(int)
// batchimageitemDescErrorCode is the schema descriptor for error_code field.
batchimageitemDescErrorCode := batchimageitemFields[12].Descriptor()
// batchimageitem.ErrorCodeValidator is a validator for the "error_code" field. It is called by the builders before save.
batchimageitem.ErrorCodeValidator = batchimageitemDescErrorCode.Validators[0].(func(string) error)
// batchimageitemDescCreatedAt is the schema descriptor for created_at field.
batchimageitemDescCreatedAt := batchimageitemFields[15].Descriptor()
// batchimageitem.DefaultCreatedAt holds the default value on creation for the created_at field.
batchimageitem.DefaultCreatedAt = batchimageitemDescCreatedAt.Default.(func() time.Time)
batchimagejobFields := schema.BatchImageJob{}.Fields()
_ = batchimagejobFields
// batchimagejobDescBatchID is the schema descriptor for batch_id field.
batchimagejobDescBatchID := batchimagejobFields[0].Descriptor()
// batchimagejob.BatchIDValidator is a validator for the "batch_id" field. It is called by the builders before save.
batchimagejob.BatchIDValidator = batchimagejobDescBatchID.Validators[0].(func(string) error)
// batchimagejobDescProvider is the schema descriptor for provider field.
batchimagejobDescProvider := batchimagejobFields[4].Descriptor()
// batchimagejob.ProviderValidator is a validator for the "provider" field. It is called by the builders before save.
batchimagejob.ProviderValidator = batchimagejobDescProvider.Validators[0].(func(string) error)
// batchimagejobDescModel is the schema descriptor for model field.
batchimagejobDescModel := batchimagejobFields[5].Descriptor()
// batchimagejob.ModelValidator is a validator for the "model" field. It is called by the builders before save.
batchimagejob.ModelValidator = batchimagejobDescModel.Validators[0].(func(string) error)
// batchimagejobDescTaskName is the schema descriptor for task_name field.
batchimagejobDescTaskName := batchimagejobFields[6].Descriptor()
// batchimagejob.DefaultTaskName holds the default value on creation for the task_name field.
batchimagejob.DefaultTaskName = batchimagejobDescTaskName.Default.(string)
// batchimagejob.TaskNameValidator is a validator for the "task_name" field. It is called by the builders before save.
batchimagejob.TaskNameValidator = batchimagejobDescTaskName.Validators[0].(func(string) error)
// batchimagejobDescStatus is the schema descriptor for status field.
batchimagejobDescStatus := batchimagejobFields[7].Descriptor()
// batchimagejob.DefaultStatus holds the default value on creation for the status field.
batchimagejob.DefaultStatus = batchimagejobDescStatus.Default.(string)
// batchimagejob.StatusValidator is a validator for the "status" field. It is called by the builders before save.
batchimagejob.StatusValidator = batchimagejobDescStatus.Validators[0].(func(string) error)
// batchimagejobDescProviderJobName is the schema descriptor for provider_job_name field.
batchimagejobDescProviderJobName := batchimagejobFields[8].Descriptor()
// batchimagejob.ProviderJobNameValidator is a validator for the "provider_job_name" field. It is called by the builders before save.
batchimagejob.ProviderJobNameValidator = batchimagejobDescProviderJobName.Validators[0].(func(string) error)
// batchimagejobDescProviderInputRef is the schema descriptor for provider_input_ref field.
batchimagejobDescProviderInputRef := batchimagejobFields[9].Descriptor()
// batchimagejob.ProviderInputRefValidator is a validator for the "provider_input_ref" field. It is called by the builders before save.
batchimagejob.ProviderInputRefValidator = batchimagejobDescProviderInputRef.Validators[0].(func(string) error)
// batchimagejobDescProviderOutputRef is the schema descriptor for provider_output_ref field.
batchimagejobDescProviderOutputRef := batchimagejobFields[10].Descriptor()
// batchimagejob.ProviderOutputRefValidator is a validator for the "provider_output_ref" field. It is called by the builders before save.
batchimagejob.ProviderOutputRefValidator = batchimagejobDescProviderOutputRef.Validators[0].(func(string) error)
// batchimagejobDescGcsInputURI is the schema descriptor for gcs_input_uri field.
batchimagejobDescGcsInputURI := batchimagejobFields[11].Descriptor()
// batchimagejob.GcsInputURIValidator is a validator for the "gcs_input_uri" field. It is called by the builders before save.
batchimagejob.GcsInputURIValidator = batchimagejobDescGcsInputURI.Validators[0].(func(string) error)
// batchimagejobDescGcsOutputURI is the schema descriptor for gcs_output_uri field.
batchimagejobDescGcsOutputURI := batchimagejobFields[12].Descriptor()
// batchimagejob.GcsOutputURIValidator is a validator for the "gcs_output_uri" field. It is called by the builders before save.
batchimagejob.GcsOutputURIValidator = batchimagejobDescGcsOutputURI.Validators[0].(func(string) error)
// batchimagejobDescSuccessCount is the schema descriptor for success_count field.
batchimagejobDescSuccessCount := batchimagejobFields[14].Descriptor()
// batchimagejob.DefaultSuccessCount holds the default value on creation for the success_count field.
batchimagejob.DefaultSuccessCount = batchimagejobDescSuccessCount.Default.(int)
// batchimagejobDescFailCount is the schema descriptor for fail_count field.
batchimagejobDescFailCount := batchimagejobFields[15].Descriptor()
// batchimagejob.DefaultFailCount holds the default value on creation for the fail_count field.
batchimagejob.DefaultFailCount = batchimagejobDescFailCount.Default.(int)
// batchimagejobDescCancelledCount is the schema descriptor for cancelled_count field.
batchimagejobDescCancelledCount := batchimagejobFields[16].Descriptor()
// batchimagejob.DefaultCancelledCount holds the default value on creation for the cancelled_count field.
batchimagejob.DefaultCancelledCount = batchimagejobDescCancelledCount.Default.(int)
// batchimagejobDescEstimatedCost is the schema descriptor for estimated_cost field.
batchimagejobDescEstimatedCost := batchimagejobFields[17].Descriptor()
// batchimagejob.DefaultEstimatedCost holds the default value on creation for the estimated_cost field.
batchimagejob.DefaultEstimatedCost = batchimagejobDescEstimatedCost.Default.(float64)
// batchimagejobDescCurrency is the schema descriptor for currency field.
batchimagejobDescCurrency := batchimagejobFields[20].Descriptor()
// batchimagejob.DefaultCurrency holds the default value on creation for the currency field.
batchimagejob.DefaultCurrency = batchimagejobDescCurrency.Default.(string)
// batchimagejob.CurrencyValidator is a validator for the "currency" field. It is called by the builders before save.
batchimagejob.CurrencyValidator = batchimagejobDescCurrency.Validators[0].(func(string) error)
// batchimagejobDescHoldID is the schema descriptor for hold_id field.
batchimagejobDescHoldID := batchimagejobFields[21].Descriptor()
// batchimagejob.HoldIDValidator is a validator for the "hold_id" field. It is called by the builders before save.
batchimagejob.HoldIDValidator = batchimagejobDescHoldID.Validators[0].(func(string) error)
// batchimagejobDescIdempotencyKey is the schema descriptor for idempotency_key field.
batchimagejobDescIdempotencyKey := batchimagejobFields[22].Descriptor()
// batchimagejob.IdempotencyKeyValidator is a validator for the "idempotency_key" field. It is called by the builders before save.
batchimagejob.IdempotencyKeyValidator = batchimagejobDescIdempotencyKey.Validators[0].(func(string) error)
// batchimagejobDescRequestHash is the schema descriptor for request_hash field.
batchimagejobDescRequestHash := batchimagejobFields[23].Descriptor()
// batchimagejob.RequestHashValidator is a validator for the "request_hash" field. It is called by the builders before save.
batchimagejob.RequestHashValidator = batchimagejobDescRequestHash.Validators[0].(func(string) error)
// batchimagejobDescManifestHash is the schema descriptor for manifest_hash field.
batchimagejobDescManifestHash := batchimagejobFields[24].Descriptor()
// batchimagejob.ManifestHashValidator is a validator for the "manifest_hash" field. It is called by the builders before save.
batchimagejob.ManifestHashValidator = batchimagejobDescManifestHash.Validators[0].(func(string) error)
// batchimagejobDescRetryCount is the schema descriptor for retry_count field.
batchimagejobDescRetryCount := batchimagejobFields[25].Descriptor()
// batchimagejob.DefaultRetryCount holds the default value on creation for the retry_count field.
batchimagejob.DefaultRetryCount = batchimagejobDescRetryCount.Default.(int)
// batchimagejobDescVersion is the schema descriptor for version field.
batchimagejobDescVersion := batchimagejobFields[26].Descriptor()
// batchimagejob.DefaultVersion holds the default value on creation for the version field.
batchimagejob.DefaultVersion = batchimagejobDescVersion.Default.(int)
// batchimagejobDescLastErrorCode is the schema descriptor for last_error_code field.
batchimagejobDescLastErrorCode := batchimagejobFields[32].Descriptor()
// batchimagejob.LastErrorCodeValidator is a validator for the "last_error_code" field. It is called by the builders before save.
batchimagejob.LastErrorCodeValidator = batchimagejobDescLastErrorCode.Validators[0].(func(string) error)
// batchimagejobDescCreatedAt is the schema descriptor for created_at field.
batchimagejobDescCreatedAt := batchimagejobFields[34].Descriptor()
// batchimagejob.DefaultCreatedAt holds the default value on creation for the created_at field.
batchimagejob.DefaultCreatedAt = batchimagejobDescCreatedAt.Default.(func() time.Time)
// batchimagejobDescUpdatedAt is the schema descriptor for updated_at field.
batchimagejobDescUpdatedAt := batchimagejobFields[35].Descriptor()
// batchimagejob.DefaultUpdatedAt holds the default value on creation for the updated_at field.
batchimagejob.DefaultUpdatedAt = batchimagejobDescUpdatedAt.Default.(func() time.Time)
// batchimagejob.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
batchimagejob.UpdateDefaultUpdatedAt = batchimagejobDescUpdatedAt.UpdateDefault.(func() time.Time)
channelmonitorMixin := schema.ChannelMonitor{}.Mixin()
channelmonitorMixinFields0 := channelmonitorMixin[0].Fields()
_ = channelmonitorMixinFields0
@@ -529,12 +698,18 @@ func init() {
channelmonitorDescIntervalSeconds := channelmonitorFields[9].Descriptor()
// channelmonitor.IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
channelmonitor.IntervalSecondsValidator = channelmonitorDescIntervalSeconds.Validators[0].(func(int) error)
// channelmonitorDescJitterSeconds is the schema descriptor for jitter_seconds field.
channelmonitorDescJitterSeconds := channelmonitorFields[10].Descriptor()
// channelmonitor.DefaultJitterSeconds holds the default value on creation for the jitter_seconds field.
channelmonitor.DefaultJitterSeconds = channelmonitorDescJitterSeconds.Default.(int)
// channelmonitor.JitterSecondsValidator is a validator for the "jitter_seconds" field. It is called by the builders before save.
channelmonitor.JitterSecondsValidator = channelmonitorDescJitterSeconds.Validators[0].(func(int) error)
// channelmonitorDescExtraHeaders is the schema descriptor for extra_headers field.
channelmonitorDescExtraHeaders := channelmonitorFields[13].Descriptor()
channelmonitorDescExtraHeaders := channelmonitorFields[14].Descriptor()
// channelmonitor.DefaultExtraHeaders holds the default value on creation for the extra_headers field.
channelmonitor.DefaultExtraHeaders = channelmonitorDescExtraHeaders.Default.(map[string]string)
// channelmonitorDescBodyOverrideMode is the schema descriptor for body_override_mode field.
channelmonitorDescBodyOverrideMode := channelmonitorFields[14].Descriptor()
channelmonitorDescBodyOverrideMode := channelmonitorFields[15].Descriptor()
// channelmonitor.DefaultBodyOverrideMode holds the default value on creation for the body_override_mode field.
channelmonitor.DefaultBodyOverrideMode = channelmonitorDescBodyOverrideMode.Default.(string)
// channelmonitor.BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
@@ -790,92 +965,132 @@ func init() {
groupDescRateMultiplier := groupFields[2].Descriptor()
// group.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
group.DefaultRateMultiplier = groupDescRateMultiplier.Default.(float64)
// groupDescPeakRateEnabled is the schema descriptor for peak_rate_enabled field.
groupDescPeakRateEnabled := groupFields[3].Descriptor()
// group.DefaultPeakRateEnabled holds the default value on creation for the peak_rate_enabled field.
group.DefaultPeakRateEnabled = groupDescPeakRateEnabled.Default.(bool)
// groupDescPeakStart is the schema descriptor for peak_start field.
groupDescPeakStart := groupFields[4].Descriptor()
// group.DefaultPeakStart holds the default value on creation for the peak_start field.
group.DefaultPeakStart = groupDescPeakStart.Default.(string)
// group.PeakStartValidator is a validator for the "peak_start" field. It is called by the builders before save.
group.PeakStartValidator = groupDescPeakStart.Validators[0].(func(string) error)
// groupDescPeakEnd is the schema descriptor for peak_end field.
groupDescPeakEnd := groupFields[5].Descriptor()
// group.DefaultPeakEnd holds the default value on creation for the peak_end field.
group.DefaultPeakEnd = groupDescPeakEnd.Default.(string)
// group.PeakEndValidator is a validator for the "peak_end" field. It is called by the builders before save.
group.PeakEndValidator = groupDescPeakEnd.Validators[0].(func(string) error)
// groupDescPeakRateMultiplier is the schema descriptor for peak_rate_multiplier field.
groupDescPeakRateMultiplier := groupFields[6].Descriptor()
// group.DefaultPeakRateMultiplier holds the default value on creation for the peak_rate_multiplier field.
group.DefaultPeakRateMultiplier = groupDescPeakRateMultiplier.Default.(float64)
// groupDescIsExclusive is the schema descriptor for is_exclusive field.
groupDescIsExclusive := groupFields[3].Descriptor()
groupDescIsExclusive := groupFields[7].Descriptor()
// group.DefaultIsExclusive holds the default value on creation for the is_exclusive field.
group.DefaultIsExclusive = groupDescIsExclusive.Default.(bool)
// groupDescStatus is the schema descriptor for status field.
groupDescStatus := groupFields[4].Descriptor()
groupDescStatus := groupFields[8].Descriptor()
// group.DefaultStatus holds the default value on creation for the status field.
group.DefaultStatus = groupDescStatus.Default.(string)
// group.StatusValidator is a validator for the "status" field. It is called by the builders before save.
group.StatusValidator = groupDescStatus.Validators[0].(func(string) error)
// groupDescPlatform is the schema descriptor for platform field.
groupDescPlatform := groupFields[5].Descriptor()
groupDescPlatform := groupFields[9].Descriptor()
// group.DefaultPlatform holds the default value on creation for the platform field.
group.DefaultPlatform = groupDescPlatform.Default.(string)
// group.PlatformValidator is a validator for the "platform" field. It is called by the builders before save.
group.PlatformValidator = groupDescPlatform.Validators[0].(func(string) error)
// groupDescSubscriptionType is the schema descriptor for subscription_type field.
groupDescSubscriptionType := groupFields[6].Descriptor()
groupDescSubscriptionType := groupFields[10].Descriptor()
// group.DefaultSubscriptionType holds the default value on creation for the subscription_type field.
group.DefaultSubscriptionType = groupDescSubscriptionType.Default.(string)
// group.SubscriptionTypeValidator is a validator for the "subscription_type" field. It is called by the builders before save.
group.SubscriptionTypeValidator = groupDescSubscriptionType.Validators[0].(func(string) error)
// groupDescDefaultValidityDays is the schema descriptor for default_validity_days field.
groupDescDefaultValidityDays := groupFields[10].Descriptor()
groupDescDefaultValidityDays := groupFields[14].Descriptor()
// group.DefaultDefaultValidityDays holds the default value on creation for the default_validity_days field.
group.DefaultDefaultValidityDays = groupDescDefaultValidityDays.Default.(int)
// groupDescAllowImageGeneration is the schema descriptor for allow_image_generation field.
groupDescAllowImageGeneration := groupFields[11].Descriptor()
groupDescAllowImageGeneration := groupFields[15].Descriptor()
// group.DefaultAllowImageGeneration holds the default value on creation for the allow_image_generation field.
group.DefaultAllowImageGeneration = groupDescAllowImageGeneration.Default.(bool)
// groupDescAllowBatchImageGeneration is the schema descriptor for allow_batch_image_generation field.
groupDescAllowBatchImageGeneration := groupFields[16].Descriptor()
// group.DefaultAllowBatchImageGeneration holds the default value on creation for the allow_batch_image_generation field.
group.DefaultAllowBatchImageGeneration = groupDescAllowBatchImageGeneration.Default.(bool)
// groupDescImageRateIndependent is the schema descriptor for image_rate_independent field.
groupDescImageRateIndependent := groupFields[12].Descriptor()
groupDescImageRateIndependent := groupFields[17].Descriptor()
// group.DefaultImageRateIndependent holds the default value on creation for the image_rate_independent field.
group.DefaultImageRateIndependent = groupDescImageRateIndependent.Default.(bool)
// groupDescImageRateMultiplier is the schema descriptor for image_rate_multiplier field.
groupDescImageRateMultiplier := groupFields[13].Descriptor()
groupDescImageRateMultiplier := groupFields[18].Descriptor()
// group.DefaultImageRateMultiplier holds the default value on creation for the image_rate_multiplier field.
group.DefaultImageRateMultiplier = groupDescImageRateMultiplier.Default.(float64)
// groupDescBatchImageDiscountMultiplier is the schema descriptor for batch_image_discount_multiplier field.
groupDescBatchImageDiscountMultiplier := groupFields[22].Descriptor()
// group.DefaultBatchImageDiscountMultiplier holds the default value on creation for the batch_image_discount_multiplier field.
group.DefaultBatchImageDiscountMultiplier = groupDescBatchImageDiscountMultiplier.Default.(float64)
// groupDescBatchImageHoldMultiplier is the schema descriptor for batch_image_hold_multiplier field.
groupDescBatchImageHoldMultiplier := groupFields[23].Descriptor()
// group.DefaultBatchImageHoldMultiplier holds the default value on creation for the batch_image_hold_multiplier field.
group.DefaultBatchImageHoldMultiplier = groupDescBatchImageHoldMultiplier.Default.(float64)
// groupDescVideoRateIndependent is the schema descriptor for video_rate_independent field.
groupDescVideoRateIndependent := groupFields[24].Descriptor()
// group.DefaultVideoRateIndependent holds the default value on creation for the video_rate_independent field.
group.DefaultVideoRateIndependent = groupDescVideoRateIndependent.Default.(bool)
// groupDescVideoRateMultiplier is the schema descriptor for video_rate_multiplier field.
groupDescVideoRateMultiplier := groupFields[25].Descriptor()
// group.DefaultVideoRateMultiplier holds the default value on creation for the video_rate_multiplier field.
group.DefaultVideoRateMultiplier = groupDescVideoRateMultiplier.Default.(float64)
// groupDescClaudeCodeOnly is the schema descriptor for claude_code_only field.
groupDescClaudeCodeOnly := groupFields[17].Descriptor()
groupDescClaudeCodeOnly := groupFields[29].Descriptor()
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
// groupDescModelRoutingEnabled is the schema descriptor for model_routing_enabled field.
groupDescModelRoutingEnabled := groupFields[21].Descriptor()
groupDescModelRoutingEnabled := groupFields[33].Descriptor()
// group.DefaultModelRoutingEnabled holds the default value on creation for the model_routing_enabled field.
group.DefaultModelRoutingEnabled = groupDescModelRoutingEnabled.Default.(bool)
// groupDescMcpXMLInject is the schema descriptor for mcp_xml_inject field.
groupDescMcpXMLInject := groupFields[22].Descriptor()
groupDescMcpXMLInject := groupFields[34].Descriptor()
// group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field.
group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool)
// groupDescSupportedModelScopes is the schema descriptor for supported_model_scopes field.
groupDescSupportedModelScopes := groupFields[23].Descriptor()
groupDescSupportedModelScopes := groupFields[35].Descriptor()
// group.DefaultSupportedModelScopes holds the default value on creation for the supported_model_scopes field.
group.DefaultSupportedModelScopes = groupDescSupportedModelScopes.Default.([]string)
// groupDescSortOrder is the schema descriptor for sort_order field.
groupDescSortOrder := groupFields[24].Descriptor()
groupDescSortOrder := groupFields[36].Descriptor()
// group.DefaultSortOrder holds the default value on creation for the sort_order field.
group.DefaultSortOrder = groupDescSortOrder.Default.(int)
// groupDescAllowMessagesDispatch is the schema descriptor for allow_messages_dispatch field.
groupDescAllowMessagesDispatch := groupFields[25].Descriptor()
groupDescAllowMessagesDispatch := groupFields[37].Descriptor()
// group.DefaultAllowMessagesDispatch holds the default value on creation for the allow_messages_dispatch field.
group.DefaultAllowMessagesDispatch = groupDescAllowMessagesDispatch.Default.(bool)
// groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field.
groupDescRequireOauthOnly := groupFields[26].Descriptor()
groupDescRequireOauthOnly := groupFields[38].Descriptor()
// group.DefaultRequireOauthOnly holds the default value on creation for the require_oauth_only field.
group.DefaultRequireOauthOnly = groupDescRequireOauthOnly.Default.(bool)
// groupDescRequirePrivacySet is the schema descriptor for require_privacy_set field.
groupDescRequirePrivacySet := groupFields[27].Descriptor()
groupDescRequirePrivacySet := groupFields[39].Descriptor()
// group.DefaultRequirePrivacySet holds the default value on creation for the require_privacy_set field.
group.DefaultRequirePrivacySet = groupDescRequirePrivacySet.Default.(bool)
// groupDescDefaultMappedModel is the schema descriptor for default_mapped_model field.
groupDescDefaultMappedModel := groupFields[28].Descriptor()
groupDescDefaultMappedModel := groupFields[40].Descriptor()
// group.DefaultDefaultMappedModel holds the default value on creation for the default_mapped_model field.
group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string)
// group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error)
// groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field.
groupDescMessagesDispatchModelConfig := groupFields[29].Descriptor()
groupDescMessagesDispatchModelConfig := groupFields[41].Descriptor()
// group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field.
group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig)
// groupDescModelsListConfig is the schema descriptor for models_list_config field.
groupDescModelsListConfig := groupFields[30].Descriptor()
groupDescModelsListConfig := groupFields[42].Descriptor()
// group.DefaultModelsListConfig holds the default value on creation for the models_list_config field.
group.DefaultModelsListConfig = groupDescModelsListConfig.Default.(domain.GroupModelsListConfig)
// groupDescRpmLimit is the schema descriptor for rpm_limit field.
groupDescRpmLimit := groupFields[31].Descriptor()
groupDescRpmLimit := groupFields[43].Descriptor()
// group.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
group.DefaultRpmLimit = groupDescRpmLimit.Default.(int)
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
@@ -1761,12 +1976,20 @@ func init() {
usagelogDescImageSizeSource := usagelogFields[37].Descriptor()
// usagelog.ImageSizeSourceValidator is a validator for the "image_size_source" field. It is called by the builders before save.
usagelog.ImageSizeSourceValidator = usagelogDescImageSizeSource.Validators[0].(func(string) error)
// usagelogDescVideoCount is the schema descriptor for video_count field.
usagelogDescVideoCount := usagelogFields[39].Descriptor()
// usagelog.DefaultVideoCount holds the default value on creation for the video_count field.
usagelog.DefaultVideoCount = usagelogDescVideoCount.Default.(int)
// usagelogDescVideoResolution is the schema descriptor for video_resolution field.
usagelogDescVideoResolution := usagelogFields[40].Descriptor()
// usagelog.VideoResolutionValidator is a validator for the "video_resolution" field. It is called by the builders before save.
usagelog.VideoResolutionValidator = usagelogDescVideoResolution.Validators[0].(func(string) error)
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
usagelogDescCacheTTLOverridden := usagelogFields[39].Descriptor()
usagelogDescCacheTTLOverridden := usagelogFields[42].Descriptor()
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
usagelog.DefaultCacheTTLOverridden = usagelogDescCacheTTLOverridden.Default.(bool)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt := usagelogFields[40].Descriptor()
usagelogDescCreatedAt := usagelogFields[43].Descriptor()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
userMixin := schema.User{}.Mixin()
@@ -1834,54 +2057,58 @@ func init() {
userDescBalance := userFields[3].Descriptor()
// user.DefaultBalance holds the default value on creation for the balance field.
user.DefaultBalance = userDescBalance.Default.(float64)
// userDescFrozenBalance is the schema descriptor for frozen_balance field.
userDescFrozenBalance := userFields[4].Descriptor()
// user.DefaultFrozenBalance holds the default value on creation for the frozen_balance field.
user.DefaultFrozenBalance = userDescFrozenBalance.Default.(float64)
// userDescConcurrency is the schema descriptor for concurrency field.
userDescConcurrency := userFields[4].Descriptor()
userDescConcurrency := userFields[5].Descriptor()
// user.DefaultConcurrency holds the default value on creation for the concurrency field.
user.DefaultConcurrency = userDescConcurrency.Default.(int)
// userDescStatus is the schema descriptor for status field.
userDescStatus := userFields[5].Descriptor()
userDescStatus := userFields[6].Descriptor()
// user.DefaultStatus holds the default value on creation for the status field.
user.DefaultStatus = userDescStatus.Default.(string)
// user.StatusValidator is a validator for the "status" field. It is called by the builders before save.
user.StatusValidator = userDescStatus.Validators[0].(func(string) error)
// userDescUsername is the schema descriptor for username field.
userDescUsername := userFields[6].Descriptor()
userDescUsername := userFields[7].Descriptor()
// user.DefaultUsername holds the default value on creation for the username field.
user.DefaultUsername = userDescUsername.Default.(string)
// user.UsernameValidator is a validator for the "username" field. It is called by the builders before save.
user.UsernameValidator = userDescUsername.Validators[0].(func(string) error)
// userDescNotes is the schema descriptor for notes field.
userDescNotes := userFields[7].Descriptor()
userDescNotes := userFields[8].Descriptor()
// user.DefaultNotes holds the default value on creation for the notes field.
user.DefaultNotes = userDescNotes.Default.(string)
// userDescTotpEnabled is the schema descriptor for totp_enabled field.
userDescTotpEnabled := userFields[9].Descriptor()
userDescTotpEnabled := userFields[10].Descriptor()
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
user.DefaultTotpEnabled = userDescTotpEnabled.Default.(bool)
// userDescSignupSource is the schema descriptor for signup_source field.
userDescSignupSource := userFields[11].Descriptor()
userDescSignupSource := userFields[12].Descriptor()
// user.DefaultSignupSource holds the default value on creation for the signup_source field.
user.DefaultSignupSource = userDescSignupSource.Default.(string)
// user.SignupSourceValidator is a validator for the "signup_source" field. It is called by the builders before save.
user.SignupSourceValidator = userDescSignupSource.Validators[0].(func(string) error)
// userDescBalanceNotifyEnabled is the schema descriptor for balance_notify_enabled field.
userDescBalanceNotifyEnabled := userFields[14].Descriptor()
userDescBalanceNotifyEnabled := userFields[15].Descriptor()
// user.DefaultBalanceNotifyEnabled holds the default value on creation for the balance_notify_enabled field.
user.DefaultBalanceNotifyEnabled = userDescBalanceNotifyEnabled.Default.(bool)
// userDescBalanceNotifyThresholdType is the schema descriptor for balance_notify_threshold_type field.
userDescBalanceNotifyThresholdType := userFields[15].Descriptor()
userDescBalanceNotifyThresholdType := userFields[16].Descriptor()
// user.DefaultBalanceNotifyThresholdType holds the default value on creation for the balance_notify_threshold_type field.
user.DefaultBalanceNotifyThresholdType = userDescBalanceNotifyThresholdType.Default.(string)
// userDescBalanceNotifyExtraEmails is the schema descriptor for balance_notify_extra_emails field.
userDescBalanceNotifyExtraEmails := userFields[17].Descriptor()
userDescBalanceNotifyExtraEmails := userFields[18].Descriptor()
// user.DefaultBalanceNotifyExtraEmails holds the default value on creation for the balance_notify_extra_emails field.
user.DefaultBalanceNotifyExtraEmails = userDescBalanceNotifyExtraEmails.Default.(string)
// userDescTotalRecharged is the schema descriptor for total_recharged field.
userDescTotalRecharged := userFields[18].Descriptor()
userDescTotalRecharged := userFields[19].Descriptor()
// user.DefaultTotalRecharged holds the default value on creation for the total_recharged field.
user.DefaultTotalRecharged = userDescTotalRecharged.Default.(float64)
// userDescRpmLimit is the schema descriptor for rpm_limit field.
userDescRpmLimit := userFields[19].Descriptor()
userDescRpmLimit := userFields[20].Descriptor()
// user.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
user.DefaultRpmLimit = userDescRpmLimit.Default.(int)
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
+14
View File
@@ -196,6 +196,11 @@ func (Account) Fields() []ent.Field {
Optional().
Nillable().
MaxLen(20),
field.Int64("parent_account_id").Optional().Nillable().
Comment("Parent account id for a linked spark shadow (NULL = normal)."),
field.Enum("quota_dimension").Values("global", "spark").Default("global").
Comment("'global' (default) or 'spark' (shadow reads codex_bengalfox)."),
}
}
@@ -212,6 +217,14 @@ func (Account) Edges() []ent.Edge {
edge.To("proxy", Proxy.Type).
Field("proxy_id").
Unique(),
// children/parent: linked spark shadow relationship.
// parent_account_id is nullable, and the active one-shadow-per-parent rule
// is enforced by the partial unique index in migration 154a.
edge.To("children", Account.Type).
Annotations(entsql.OnDelete(entsql.Restrict)).
From("parent").
Field("parent_account_id").
Unique(),
// usage_logs: 该账户的使用日志
edge.To("usage_logs", UsageLog.Type),
}
@@ -235,5 +248,6 @@ func (Account) Indexes() []ent.Index {
index.Fields("platform", "priority"),
index.Fields("priority", "status"),
index.Fields("deleted_at"), // 软删除查询优化
index.Fields("parent_account_id"),
}
}
+43
View File
@@ -0,0 +1,43 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// BatchImageEvent records append-only operational events for batch image jobs.
type BatchImageEvent struct {
ent.Schema
}
func (BatchImageEvent) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "batch_image_events"},
}
}
func (BatchImageEvent) Fields() []ent.Field {
return []ent.Field{
field.String("job_id").MaxLen(64),
field.String("event_type").MaxLen(64),
field.JSON("payload", map[string]any{}).
Optional().
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
field.String("event_hash").Optional().Nillable().MaxLen(128),
field.Time("created_at").Immutable().Default(time.Now).SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (BatchImageEvent) Indexes() []ent.Index {
return []ent.Index{
index.Fields("job_id", "created_at"),
index.Fields("event_type"),
index.Fields("job_id", "event_hash").Unique().Annotations(entsql.IndexWhere("event_hash IS NOT NULL AND event_hash <> ''")),
}
}
+53
View File
@@ -0,0 +1,53 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// BatchImageItem holds indexed output rows for a batch image job.
type BatchImageItem struct {
ent.Schema
}
func (BatchImageItem) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "batch_image_items"},
}
}
func (BatchImageItem) Fields() []ent.Field {
return []ent.Field{
field.String("job_id").MaxLen(64),
field.String("custom_id").MaxLen(255),
field.String("status").MaxLen(32),
field.String("request_hash").Optional().Nillable().MaxLen(128),
field.String("prompt_preview").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("provider_source_object").Optional().Nillable().MaxLen(1024),
field.Int("source_line_number").Optional().Nillable(),
field.Int64("source_byte_offset").Optional().Nillable(),
field.Int64("source_byte_length").Optional().Nillable(),
field.String("mime_type").Optional().Nillable().MaxLen(128),
field.String("file_extension").Optional().Nillable().MaxLen(32),
field.Int("image_count").Default(0),
field.String("error_code").Optional().Nillable().MaxLen(128),
field.String("error_message").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "text"}),
field.Float("billed_amount").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Time("created_at").Immutable().Default(time.Now).SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("indexed_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (BatchImageItem) Indexes() []ent.Index {
return []ent.Index{
index.Fields("job_id", "custom_id").Unique(),
index.Fields("job_id", "status"),
index.Fields("provider_source_object"),
}
}
+86
View File
@@ -0,0 +1,86 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// BatchImageJob holds the schema definition for asynchronous image batch jobs.
//
// 删除策略:账务源保留
// 这张表是批量生图任务的账务和状态源;用户侧删除仅通过 user_deleted_at
// 从列表隐藏,输出清理通过 output_deleted 状态和删除时间字段表达。
type BatchImageJob struct {
ent.Schema
}
func (BatchImageJob) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "batch_image_jobs"},
}
}
func (BatchImageJob) Fields() []ent.Field {
return []ent.Field{
field.String("batch_id").MaxLen(64).Immutable(),
field.Int64("user_id"),
field.Int64("api_key_id").Optional().Nillable(),
field.Int64("account_id").Optional().Nillable(),
field.String("provider").MaxLen(32),
field.String("model").MaxLen(128),
field.String("task_name").MaxLen(255).Default(""),
field.String("status").MaxLen(32).Default("created"),
field.String("provider_job_name").Optional().Nillable().MaxLen(512),
field.String("provider_input_ref").Optional().Nillable().MaxLen(1024),
field.String("provider_output_ref").Optional().Nillable().MaxLen(1024),
field.String("gcs_input_uri").Optional().Nillable().MaxLen(1024),
field.String("gcs_output_uri").Optional().Nillable().MaxLen(1024),
field.Int("item_count"),
field.Int("success_count").Default(0),
field.Int("fail_count").Default(0),
field.Int("cancelled_count").Default(0),
field.Float("estimated_cost").SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}).Default(0),
field.Float("hold_amount").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("actual_cost").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.String("currency").MaxLen(16).Default("USD"),
field.String("hold_id").Optional().Nillable().MaxLen(128),
field.String("idempotency_key").Optional().Nillable().MaxLen(255),
field.String("request_hash").Optional().Nillable().MaxLen(128),
field.String("manifest_hash").Optional().Nillable().MaxLen(128),
field.Int("retry_count").Default(0),
field.Int("version").Default(0),
field.Time("output_expires_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("input_deleted_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("output_deleted_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("downloaded_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("user_deleted_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.String("last_error_code").Optional().Nillable().MaxLen(128),
field.String("last_error_message").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "text"}),
field.Time("created_at").Immutable().Default(time.Now).SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now).SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("submitted_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("started_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("finished_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("settled_at").Optional().Nillable().SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (BatchImageJob) Indexes() []ent.Index {
return []ent.Index{
index.Fields("batch_id").Unique(),
index.Fields("user_id", "created_at"),
index.Fields("status"),
index.Fields("provider", "status"),
index.Fields("idempotency_key").Annotations(entsql.IndexWhere("idempotency_key IS NOT NULL AND idempotency_key <> ''")),
index.Fields("manifest_hash").Unique().Annotations(entsql.IndexWhere("manifest_hash IS NOT NULL AND manifest_hash <> ''")),
index.Fields("output_expires_at"),
index.Fields("downloaded_at"),
index.Fields("user_deleted_at"),
}
}
+4
View File
@@ -62,6 +62,10 @@ func (ChannelMonitor) Fields() []ent.Field {
Default(true),
field.Int("interval_seconds").
Range(15, 3600),
field.Int("jitter_seconds").
Default(0).
Range(0, 3600).
Comment("每次调度在 interval 基础上 ± [0, jitter] 的均匀随机偏移(秒);0 表示固定间隔。service 层另保证 interval - jitter >= 15"),
field.Time("last_checked_at").
Optional().
Nillable(),
+46
View File
@@ -45,6 +45,22 @@ func (Group) Fields() []ent.Field {
field.Float("rate_multiplier").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(1.0),
// 高峰时段倍率(added by migration 158)
field.Bool("peak_rate_enabled").
Default(false).
Comment("是否启用高峰时段倍率"),
field.String("peak_start").
MaxLen(5).
Default("").
Comment("高峰开始时间 HH:MM(含),如 14:00;空表示未配置;不支持跨天"),
field.String("peak_end").
MaxLen(5).
Default("").
Comment("高峰结束时间 HH:MM(不含),必须大于 peak_start;不支持跨天,如 22:00-02:00"),
field.Float("peak_rate_multiplier").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(1.0).
Comment("高峰时段叠加倍率,仅在 peak_rate_enabled 且处于 [peak_start, peak_end) 时乘入文本倍率"),
field.Bool("is_exclusive").
Default(false),
field.String("status").
@@ -77,6 +93,9 @@ func (Group) Fields() []ent.Field {
field.Bool("allow_image_generation").
Default(false).
Comment("是否允许该分组使用图片生成能力"),
field.Bool("allow_batch_image_generation").
Default(false).
Comment("是否允许该分组使用批量图片生成能力"),
field.Bool("image_rate_independent").
Default(false).
Comment("图片生成是否使用独立倍率;false 表示共享分组有效倍率"),
@@ -96,6 +115,33 @@ func (Group) Fields() []ent.Field {
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("batch_image_discount_multiplier").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(0.5).
Comment("批量图片生成折扣倍率,最终单价会乘以该值;0 表示免费"),
field.Float("batch_image_hold_multiplier").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(0.6).
Comment("批量图片生成冻结价格比例,按普通生图原价乘以该比例冻结,结算后释放差额"),
field.Bool("video_rate_independent").
Default(false).
Comment("视频生成是否使用独立倍率;false 表示共享分组有效倍率"),
field.Float("video_rate_multiplier").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(1.0).
Comment("视频生成独立倍率,仅 video_rate_independent=true 时生效"),
field.Float("video_price_480p").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("video_price_720p").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("video_price_1080p").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
// Claude Code 客户端限制 (added by migration 029)
field.Bool("claude_code_only").
+14
View File
@@ -149,6 +149,20 @@ func (UsageLog) Fields() []ent.Field {
field.JSON("image_size_breakdown", map[string]int{}).
Optional().
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
// 视频生成字段(Grok 视频按秒计费;billing_mode 走 token/其他模式时这些列仍标记视频用量)
field.Int("video_count").
Default(0).
Comment("视频生成数量;>0 表示本行是视频生成用量"),
field.String("video_resolution").
MaxLen(10).
Optional().
Nillable().
Comment("计费用视频分辨率 480p/720p/1080p"),
field.Int("video_duration_seconds").
Optional().
Nillable().
Comment("提交时请求的视频时长(秒),按秒计费的乘数"),
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
field.Bool("cache_ttl_overridden").
Default(false),
+3
View File
@@ -49,6 +49,9 @@ func (User) Fields() []ent.Field {
field.Float("balance").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0),
field.Float("frozen_balance").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0),
field.Int("concurrency").
Default(5),
field.String("status").
+1 -1
View File
@@ -41,7 +41,7 @@ func (UserPlatformQuota) Fields() []ent.Field {
// 注意:平台列表的单一权威源为 service.AllowedQuotaPlatforms;
// 此处为 ent 构建期约束,需与 service.AllowedQuotaPlatforms 保持同步。
switch s {
case "anthropic", "openai", "gemini", "antigravity":
case "anthropic", "openai", "gemini", "antigravity", "grok":
return nil
default:
return fmt.Errorf("platform %q is not allowed", s)
+9
View File
@@ -28,6 +28,12 @@ type Tx struct {
AuthIdentity *AuthIdentityClient
// AuthIdentityChannel is the client for interacting with the AuthIdentityChannel builders.
AuthIdentityChannel *AuthIdentityChannelClient
// BatchImageEvent is the client for interacting with the BatchImageEvent builders.
BatchImageEvent *BatchImageEventClient
// BatchImageItem is the client for interacting with the BatchImageItem builders.
BatchImageItem *BatchImageItemClient
// BatchImageJob is the client for interacting with the BatchImageJob builders.
BatchImageJob *BatchImageJobClient
// ChannelMonitor is the client for interacting with the ChannelMonitor builders.
ChannelMonitor *ChannelMonitorClient
// ChannelMonitorDailyRollup is the client for interacting with the ChannelMonitorDailyRollup builders.
@@ -222,6 +228,9 @@ func (tx *Tx) init() {
tx.AnnouncementRead = NewAnnouncementReadClient(tx.config)
tx.AuthIdentity = NewAuthIdentityClient(tx.config)
tx.AuthIdentityChannel = NewAuthIdentityChannelClient(tx.config)
tx.BatchImageEvent = NewBatchImageEventClient(tx.config)
tx.BatchImageItem = NewBatchImageItemClient(tx.config)
tx.BatchImageJob = NewBatchImageJobClient(tx.config)
tx.ChannelMonitor = NewChannelMonitorClient(tx.config)
tx.ChannelMonitorDailyRollup = NewChannelMonitorDailyRollupClient(tx.config)
tx.ChannelMonitorHistory = NewChannelMonitorHistoryClient(tx.config)
+17
View File
@@ -0,0 +1,17 @@
package ent
import "context"
// WithoutTx 返回一个剥离了所附 *Tx 的 ctx 副本,使调用方可以在基础 client
// (autocommit)上执行 best-effort、非关键的副作用,而不加入——也就不会毒化——
// 外层事务。
//
// Postgres 语义:事务内任一语句失败即把整个事务标记为 aborted,后续语句全部被拒,
// 直到 ROLLBACK。因此对 fail-open 的副作用(如注册时的默认平台配额快照)必须做事务
// 隔离,否则一条无关紧要的写入失败会连累调用方的关键事务。
func WithoutTx(ctx context.Context) context.Context {
if TxFromContext(ctx) == nil {
return ctx
}
return context.WithValue(ctx, txCtxKey{}, (*Tx)(nil))
}
+41 -2
View File
@@ -101,6 +101,12 @@ type UsageLog struct {
ImageSizeSource *string `json:"image_size_source,omitempty"`
// ImageSizeBreakdown holds the value of the "image_size_breakdown" field.
ImageSizeBreakdown map[string]int `json:"image_size_breakdown,omitempty"`
// 视频生成数量;>0 表示本行是视频生成用量
VideoCount int `json:"video_count,omitempty"`
// 计费用视频分辨率 480p/720p/1080p
VideoResolution *string `json:"video_resolution,omitempty"`
// 提交时请求的视频时长(秒),按秒计费的乘数
VideoDurationSeconds *int `json:"video_duration_seconds,omitempty"`
// CacheTTLOverridden holds the value of the "cache_ttl_overridden" field.
CacheTTLOverridden bool `json:"cache_ttl_overridden,omitempty"`
// CreatedAt holds the value of the "created_at" field.
@@ -194,9 +200,9 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullBool)
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier, usagelog.FieldAccountRateMultiplier:
values[i] = new(sql.NullFloat64)
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldChannelID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount:
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldChannelID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount, usagelog.FieldVideoCount, usagelog.FieldVideoDurationSeconds:
values[i] = new(sql.NullInt64)
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldRequestedModel, usagelog.FieldUpstreamModel, usagelog.FieldModelMappingChain, usagelog.FieldBillingTier, usagelog.FieldBillingMode, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldImageInputSize, usagelog.FieldImageOutputSize, usagelog.FieldImageSizeSource:
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldRequestedModel, usagelog.FieldUpstreamModel, usagelog.FieldModelMappingChain, usagelog.FieldBillingTier, usagelog.FieldBillingMode, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldImageInputSize, usagelog.FieldImageOutputSize, usagelog.FieldImageSizeSource, usagelog.FieldVideoResolution:
values[i] = new(sql.NullString)
case usagelog.FieldCreatedAt:
values[i] = new(sql.NullTime)
@@ -474,6 +480,26 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
return fmt.Errorf("unmarshal field image_size_breakdown: %w", err)
}
}
case usagelog.FieldVideoCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field video_count", values[i])
} else if value.Valid {
_m.VideoCount = int(value.Int64)
}
case usagelog.FieldVideoResolution:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field video_resolution", values[i])
} else if value.Valid {
_m.VideoResolution = new(string)
*_m.VideoResolution = value.String
}
case usagelog.FieldVideoDurationSeconds:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field video_duration_seconds", values[i])
} else if value.Valid {
_m.VideoDurationSeconds = new(int)
*_m.VideoDurationSeconds = int(value.Int64)
}
case usagelog.FieldCacheTTLOverridden:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field cache_ttl_overridden", values[i])
@@ -698,6 +724,19 @@ func (_m *UsageLog) String() string {
builder.WriteString("image_size_breakdown=")
builder.WriteString(fmt.Sprintf("%v", _m.ImageSizeBreakdown))
builder.WriteString(", ")
builder.WriteString("video_count=")
builder.WriteString(fmt.Sprintf("%v", _m.VideoCount))
builder.WriteString(", ")
if v := _m.VideoResolution; v != nil {
builder.WriteString("video_resolution=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.VideoDurationSeconds; v != nil {
builder.WriteString("video_duration_seconds=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("cache_ttl_overridden=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheTTLOverridden))
builder.WriteString(", ")
+28
View File
@@ -92,6 +92,12 @@ const (
FieldImageSizeSource = "image_size_source"
// FieldImageSizeBreakdown holds the string denoting the image_size_breakdown field in the database.
FieldImageSizeBreakdown = "image_size_breakdown"
// FieldVideoCount holds the string denoting the video_count field in the database.
FieldVideoCount = "video_count"
// FieldVideoResolution holds the string denoting the video_resolution field in the database.
FieldVideoResolution = "video_resolution"
// FieldVideoDurationSeconds holds the string denoting the video_duration_seconds field in the database.
FieldVideoDurationSeconds = "video_duration_seconds"
// FieldCacheTTLOverridden holds the string denoting the cache_ttl_overridden field in the database.
FieldCacheTTLOverridden = "cache_ttl_overridden"
// FieldCreatedAt holds the string denoting the created_at field in the database.
@@ -187,6 +193,9 @@ var Columns = []string{
FieldImageOutputSize,
FieldImageSizeSource,
FieldImageSizeBreakdown,
FieldVideoCount,
FieldVideoResolution,
FieldVideoDurationSeconds,
FieldCacheTTLOverridden,
FieldCreatedAt,
}
@@ -260,6 +269,10 @@ var (
ImageOutputSizeValidator func(string) error
// ImageSizeSourceValidator is a validator for the "image_size_source" field. It is called by the builders before save.
ImageSizeSourceValidator func(string) error
// DefaultVideoCount holds the default value on creation for the "video_count" field.
DefaultVideoCount int
// VideoResolutionValidator is a validator for the "video_resolution" field. It is called by the builders before save.
VideoResolutionValidator func(string) error
// DefaultCacheTTLOverridden holds the default value on creation for the "cache_ttl_overridden" field.
DefaultCacheTTLOverridden bool
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
@@ -464,6 +477,21 @@ func ByImageSizeSource(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldImageSizeSource, opts...).ToFunc()
}
// ByVideoCount orders the results by the video_count field.
func ByVideoCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoCount, opts...).ToFunc()
}
// ByVideoResolution orders the results by the video_resolution field.
func ByVideoResolution(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoResolution, opts...).ToFunc()
}
// ByVideoDurationSeconds orders the results by the video_duration_seconds field.
func ByVideoDurationSeconds(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoDurationSeconds, opts...).ToFunc()
}
// ByCacheTTLOverridden orders the results by the cache_ttl_overridden field.
func ByCacheTTLOverridden(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCacheTTLOverridden, opts...).ToFunc()
+180
View File
@@ -245,6 +245,21 @@ func ImageSizeSource(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldImageSizeSource, v))
}
// VideoCount applies equality check predicate on the "video_count" field. It's identical to VideoCountEQ.
func VideoCount(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoCount, v))
}
// VideoResolution applies equality check predicate on the "video_resolution" field. It's identical to VideoResolutionEQ.
func VideoResolution(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoResolution, v))
}
// VideoDurationSeconds applies equality check predicate on the "video_duration_seconds" field. It's identical to VideoDurationSecondsEQ.
func VideoDurationSeconds(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoDurationSeconds, v))
}
// CacheTTLOverridden applies equality check predicate on the "cache_ttl_overridden" field. It's identical to CacheTTLOverriddenEQ.
func CacheTTLOverridden(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
@@ -2150,6 +2165,171 @@ func ImageSizeBreakdownNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldImageSizeBreakdown))
}
// VideoCountEQ applies the EQ predicate on the "video_count" field.
func VideoCountEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoCount, v))
}
// VideoCountNEQ applies the NEQ predicate on the "video_count" field.
func VideoCountNEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoCount, v))
}
// VideoCountIn applies the In predicate on the "video_count" field.
func VideoCountIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoCount, vs...))
}
// VideoCountNotIn applies the NotIn predicate on the "video_count" field.
func VideoCountNotIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoCount, vs...))
}
// VideoCountGT applies the GT predicate on the "video_count" field.
func VideoCountGT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoCount, v))
}
// VideoCountGTE applies the GTE predicate on the "video_count" field.
func VideoCountGTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoCount, v))
}
// VideoCountLT applies the LT predicate on the "video_count" field.
func VideoCountLT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoCount, v))
}
// VideoCountLTE applies the LTE predicate on the "video_count" field.
func VideoCountLTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoCount, v))
}
// VideoResolutionEQ applies the EQ predicate on the "video_resolution" field.
func VideoResolutionEQ(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoResolution, v))
}
// VideoResolutionNEQ applies the NEQ predicate on the "video_resolution" field.
func VideoResolutionNEQ(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoResolution, v))
}
// VideoResolutionIn applies the In predicate on the "video_resolution" field.
func VideoResolutionIn(vs ...string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoResolution, vs...))
}
// VideoResolutionNotIn applies the NotIn predicate on the "video_resolution" field.
func VideoResolutionNotIn(vs ...string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoResolution, vs...))
}
// VideoResolutionGT applies the GT predicate on the "video_resolution" field.
func VideoResolutionGT(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoResolution, v))
}
// VideoResolutionGTE applies the GTE predicate on the "video_resolution" field.
func VideoResolutionGTE(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoResolution, v))
}
// VideoResolutionLT applies the LT predicate on the "video_resolution" field.
func VideoResolutionLT(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoResolution, v))
}
// VideoResolutionLTE applies the LTE predicate on the "video_resolution" field.
func VideoResolutionLTE(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoResolution, v))
}
// VideoResolutionContains applies the Contains predicate on the "video_resolution" field.
func VideoResolutionContains(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldContains(FieldVideoResolution, v))
}
// VideoResolutionHasPrefix applies the HasPrefix predicate on the "video_resolution" field.
func VideoResolutionHasPrefix(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldHasPrefix(FieldVideoResolution, v))
}
// VideoResolutionHasSuffix applies the HasSuffix predicate on the "video_resolution" field.
func VideoResolutionHasSuffix(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldHasSuffix(FieldVideoResolution, v))
}
// VideoResolutionIsNil applies the IsNil predicate on the "video_resolution" field.
func VideoResolutionIsNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldIsNull(FieldVideoResolution))
}
// VideoResolutionNotNil applies the NotNil predicate on the "video_resolution" field.
func VideoResolutionNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldVideoResolution))
}
// VideoResolutionEqualFold applies the EqualFold predicate on the "video_resolution" field.
func VideoResolutionEqualFold(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEqualFold(FieldVideoResolution, v))
}
// VideoResolutionContainsFold applies the ContainsFold predicate on the "video_resolution" field.
func VideoResolutionContainsFold(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldContainsFold(FieldVideoResolution, v))
}
// VideoDurationSecondsEQ applies the EQ predicate on the "video_duration_seconds" field.
func VideoDurationSecondsEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsNEQ applies the NEQ predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsIn applies the In predicate on the "video_duration_seconds" field.
func VideoDurationSecondsIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoDurationSeconds, vs...))
}
// VideoDurationSecondsNotIn applies the NotIn predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNotIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoDurationSeconds, vs...))
}
// VideoDurationSecondsGT applies the GT predicate on the "video_duration_seconds" field.
func VideoDurationSecondsGT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsGTE applies the GTE predicate on the "video_duration_seconds" field.
func VideoDurationSecondsGTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsLT applies the LT predicate on the "video_duration_seconds" field.
func VideoDurationSecondsLT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsLTE applies the LTE predicate on the "video_duration_seconds" field.
func VideoDurationSecondsLTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsIsNil applies the IsNil predicate on the "video_duration_seconds" field.
func VideoDurationSecondsIsNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldIsNull(FieldVideoDurationSeconds))
}
// VideoDurationSecondsNotNil applies the NotNil predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldVideoDurationSeconds))
}
// CacheTTLOverriddenEQ applies the EQ predicate on the "cache_ttl_overridden" field.
func CacheTTLOverriddenEQ(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
+266
View File
@@ -525,6 +525,48 @@ func (_c *UsageLogCreate) SetImageSizeBreakdown(v map[string]int) *UsageLogCreat
return _c
}
// SetVideoCount sets the "video_count" field.
func (_c *UsageLogCreate) SetVideoCount(v int) *UsageLogCreate {
_c.mutation.SetVideoCount(v)
return _c
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoCount(v *int) *UsageLogCreate {
if v != nil {
_c.SetVideoCount(*v)
}
return _c
}
// SetVideoResolution sets the "video_resolution" field.
func (_c *UsageLogCreate) SetVideoResolution(v string) *UsageLogCreate {
_c.mutation.SetVideoResolution(v)
return _c
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoResolution(v *string) *UsageLogCreate {
if v != nil {
_c.SetVideoResolution(*v)
}
return _c
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_c *UsageLogCreate) SetVideoDurationSeconds(v int) *UsageLogCreate {
_c.mutation.SetVideoDurationSeconds(v)
return _c
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoDurationSeconds(v *int) *UsageLogCreate {
if v != nil {
_c.SetVideoDurationSeconds(*v)
}
return _c
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_c *UsageLogCreate) SetCacheTTLOverridden(v bool) *UsageLogCreate {
_c.mutation.SetCacheTTLOverridden(v)
@@ -677,6 +719,10 @@ func (_c *UsageLogCreate) defaults() {
v := usagelog.DefaultImageCount
_c.mutation.SetImageCount(v)
}
if _, ok := _c.mutation.VideoCount(); !ok {
v := usagelog.DefaultVideoCount
_c.mutation.SetVideoCount(v)
}
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
v := usagelog.DefaultCacheTTLOverridden
_c.mutation.SetCacheTTLOverridden(v)
@@ -817,6 +863,14 @@ func (_c *UsageLogCreate) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if _, ok := _c.mutation.VideoCount(); !ok {
return &ValidationError{Name: "video_count", err: errors.New(`ent: missing required field "UsageLog.video_count"`)}
}
if v, ok := _c.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
return &ValidationError{Name: "cache_ttl_overridden", err: errors.New(`ent: missing required field "UsageLog.cache_ttl_overridden"`)}
}
@@ -995,6 +1049,18 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
_spec.SetField(usagelog.FieldImageSizeBreakdown, field.TypeJSON, value)
_node.ImageSizeBreakdown = value
}
if value, ok := _c.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
_node.VideoCount = value
}
if value, ok := _c.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
_node.VideoResolution = &value
}
if value, ok := _c.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
_node.VideoDurationSeconds = &value
}
if value, ok := _c.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
_node.CacheTTLOverridden = value
@@ -1830,6 +1896,66 @@ func (u *UsageLogUpsert) ClearImageSizeBreakdown() *UsageLogUpsert {
return u
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsert) SetVideoCount(v int) *UsageLogUpsert {
u.Set(usagelog.FieldVideoCount, v)
return u
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoCount() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoCount)
return u
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsert) AddVideoCount(v int) *UsageLogUpsert {
u.Add(usagelog.FieldVideoCount, v)
return u
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsert) SetVideoResolution(v string) *UsageLogUpsert {
u.Set(usagelog.FieldVideoResolution, v)
return u
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoResolution() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoResolution)
return u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsert) ClearVideoResolution() *UsageLogUpsert {
u.SetNull(usagelog.FieldVideoResolution)
return u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsert) SetVideoDurationSeconds(v int) *UsageLogUpsert {
u.Set(usagelog.FieldVideoDurationSeconds, v)
return u
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoDurationSeconds() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoDurationSeconds)
return u
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsert) AddVideoDurationSeconds(v int) *UsageLogUpsert {
u.Add(usagelog.FieldVideoDurationSeconds, v)
return u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsert) ClearVideoDurationSeconds() *UsageLogUpsert {
u.SetNull(usagelog.FieldVideoDurationSeconds)
return u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsert) SetCacheTTLOverridden(v bool) *UsageLogUpsert {
u.Set(usagelog.FieldCacheTTLOverridden, v)
@@ -2692,6 +2818,76 @@ func (u *UsageLogUpsertOne) ClearImageSizeBreakdown() *UsageLogUpsertOne {
})
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsertOne) SetVideoCount(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoCount(v)
})
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsertOne) AddVideoCount(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoCount(v)
})
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoCount() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoCount()
})
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsertOne) SetVideoResolution(v string) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoResolution(v)
})
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoResolution() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoResolution()
})
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsertOne) ClearVideoResolution() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoResolution()
})
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) SetVideoDurationSeconds(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoDurationSeconds(v)
})
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) AddVideoDurationSeconds(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoDurationSeconds(v)
})
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoDurationSeconds() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoDurationSeconds()
})
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) ClearVideoDurationSeconds() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoDurationSeconds()
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsertOne) SetCacheTTLOverridden(v bool) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
@@ -3722,6 +3918,76 @@ func (u *UsageLogUpsertBulk) ClearImageSizeBreakdown() *UsageLogUpsertBulk {
})
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsertBulk) SetVideoCount(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoCount(v)
})
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsertBulk) AddVideoCount(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoCount(v)
})
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoCount() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoCount()
})
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsertBulk) SetVideoResolution(v string) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoResolution(v)
})
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoResolution() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoResolution()
})
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsertBulk) ClearVideoResolution() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoResolution()
})
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) SetVideoDurationSeconds(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoDurationSeconds(v)
})
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) AddVideoDurationSeconds(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoDurationSeconds(v)
})
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoDurationSeconds() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoDurationSeconds()
})
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) ClearVideoDurationSeconds() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoDurationSeconds()
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsertBulk) SetCacheTTLOverridden(v bool) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
+188
View File
@@ -811,6 +811,74 @@ func (_u *UsageLogUpdate) ClearImageSizeBreakdown() *UsageLogUpdate {
return _u
}
// SetVideoCount sets the "video_count" field.
func (_u *UsageLogUpdate) SetVideoCount(v int) *UsageLogUpdate {
_u.mutation.ResetVideoCount()
_u.mutation.SetVideoCount(v)
return _u
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoCount(v *int) *UsageLogUpdate {
if v != nil {
_u.SetVideoCount(*v)
}
return _u
}
// AddVideoCount adds value to the "video_count" field.
func (_u *UsageLogUpdate) AddVideoCount(v int) *UsageLogUpdate {
_u.mutation.AddVideoCount(v)
return _u
}
// SetVideoResolution sets the "video_resolution" field.
func (_u *UsageLogUpdate) SetVideoResolution(v string) *UsageLogUpdate {
_u.mutation.SetVideoResolution(v)
return _u
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoResolution(v *string) *UsageLogUpdate {
if v != nil {
_u.SetVideoResolution(*v)
}
return _u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (_u *UsageLogUpdate) ClearVideoResolution() *UsageLogUpdate {
_u.mutation.ClearVideoResolution()
return _u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_u *UsageLogUpdate) SetVideoDurationSeconds(v int) *UsageLogUpdate {
_u.mutation.ResetVideoDurationSeconds()
_u.mutation.SetVideoDurationSeconds(v)
return _u
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoDurationSeconds(v *int) *UsageLogUpdate {
if v != nil {
_u.SetVideoDurationSeconds(*v)
}
return _u
}
// AddVideoDurationSeconds adds value to the "video_duration_seconds" field.
func (_u *UsageLogUpdate) AddVideoDurationSeconds(v int) *UsageLogUpdate {
_u.mutation.AddVideoDurationSeconds(v)
return _u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (_u *UsageLogUpdate) ClearVideoDurationSeconds() *UsageLogUpdate {
_u.mutation.ClearVideoDurationSeconds()
return _u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_u *UsageLogUpdate) SetCacheTTLOverridden(v bool) *UsageLogUpdate {
_u.mutation.SetCacheTTLOverridden(v)
@@ -979,6 +1047,11 @@ func (_u *UsageLogUpdate) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if v, ok := _u.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "UsageLog.user"`)
}
@@ -1210,6 +1283,27 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.ImageSizeBreakdownCleared() {
_spec.ClearField(usagelog.FieldImageSizeBreakdown, field.TypeJSON)
}
if value, ok := _u.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoCount(); ok {
_spec.AddField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
}
if _u.mutation.VideoResolutionCleared() {
_spec.ClearField(usagelog.FieldVideoResolution, field.TypeString)
}
if value, ok := _u.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoDurationSeconds(); ok {
_spec.AddField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if _u.mutation.VideoDurationSecondsCleared() {
_spec.ClearField(usagelog.FieldVideoDurationSeconds, field.TypeInt)
}
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
}
@@ -2157,6 +2251,74 @@ func (_u *UsageLogUpdateOne) ClearImageSizeBreakdown() *UsageLogUpdateOne {
return _u
}
// SetVideoCount sets the "video_count" field.
func (_u *UsageLogUpdateOne) SetVideoCount(v int) *UsageLogUpdateOne {
_u.mutation.ResetVideoCount()
_u.mutation.SetVideoCount(v)
return _u
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoCount(v *int) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoCount(*v)
}
return _u
}
// AddVideoCount adds value to the "video_count" field.
func (_u *UsageLogUpdateOne) AddVideoCount(v int) *UsageLogUpdateOne {
_u.mutation.AddVideoCount(v)
return _u
}
// SetVideoResolution sets the "video_resolution" field.
func (_u *UsageLogUpdateOne) SetVideoResolution(v string) *UsageLogUpdateOne {
_u.mutation.SetVideoResolution(v)
return _u
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoResolution(v *string) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoResolution(*v)
}
return _u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (_u *UsageLogUpdateOne) ClearVideoResolution() *UsageLogUpdateOne {
_u.mutation.ClearVideoResolution()
return _u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) SetVideoDurationSeconds(v int) *UsageLogUpdateOne {
_u.mutation.ResetVideoDurationSeconds()
_u.mutation.SetVideoDurationSeconds(v)
return _u
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoDurationSeconds(v *int) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoDurationSeconds(*v)
}
return _u
}
// AddVideoDurationSeconds adds value to the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) AddVideoDurationSeconds(v int) *UsageLogUpdateOne {
_u.mutation.AddVideoDurationSeconds(v)
return _u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) ClearVideoDurationSeconds() *UsageLogUpdateOne {
_u.mutation.ClearVideoDurationSeconds()
return _u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_u *UsageLogUpdateOne) SetCacheTTLOverridden(v bool) *UsageLogUpdateOne {
_u.mutation.SetCacheTTLOverridden(v)
@@ -2338,6 +2500,11 @@ func (_u *UsageLogUpdateOne) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if v, ok := _u.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "UsageLog.user"`)
}
@@ -2586,6 +2753,27 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
if _u.mutation.ImageSizeBreakdownCleared() {
_spec.ClearField(usagelog.FieldImageSizeBreakdown, field.TypeJSON)
}
if value, ok := _u.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoCount(); ok {
_spec.AddField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
}
if _u.mutation.VideoResolutionCleared() {
_spec.ClearField(usagelog.FieldVideoResolution, field.TypeString)
}
if value, ok := _u.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoDurationSeconds(); ok {
_spec.AddField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if _u.mutation.VideoDurationSecondsCleared() {
_spec.ClearField(usagelog.FieldVideoDurationSeconds, field.TypeInt)
}
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
}
+12 -1
View File
@@ -31,6 +31,8 @@ type User struct {
Role string `json:"role,omitempty"`
// Balance holds the value of the "balance" field.
Balance float64 `json:"balance,omitempty"`
// FrozenBalance holds the value of the "frozen_balance" field.
FrozenBalance float64 `json:"frozen_balance,omitempty"`
// Concurrency holds the value of the "concurrency" field.
Concurrency int `json:"concurrency,omitempty"`
// Status holds the value of the "status" field.
@@ -237,7 +239,7 @@ func (*User) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case user.FieldTotpEnabled, user.FieldBalanceNotifyEnabled:
values[i] = new(sql.NullBool)
case user.FieldBalance, user.FieldBalanceNotifyThreshold, user.FieldTotalRecharged:
case user.FieldBalance, user.FieldFrozenBalance, user.FieldBalanceNotifyThreshold, user.FieldTotalRecharged:
values[i] = new(sql.NullFloat64)
case user.FieldID, user.FieldConcurrency, user.FieldRpmLimit:
values[i] = new(sql.NullInt64)
@@ -309,6 +311,12 @@ func (_m *User) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Balance = value.Float64
}
case user.FieldFrozenBalance:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field frozen_balance", values[i])
} else if value.Valid {
_m.FrozenBalance = value.Float64
}
case user.FieldConcurrency:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field concurrency", values[i])
@@ -539,6 +547,9 @@ func (_m *User) String() string {
builder.WriteString("balance=")
builder.WriteString(fmt.Sprintf("%v", _m.Balance))
builder.WriteString(", ")
builder.WriteString("frozen_balance=")
builder.WriteString(fmt.Sprintf("%v", _m.FrozenBalance))
builder.WriteString(", ")
builder.WriteString("concurrency=")
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
builder.WriteString(", ")
+10
View File
@@ -29,6 +29,8 @@ const (
FieldRole = "role"
// FieldBalance holds the string denoting the balance field in the database.
FieldBalance = "balance"
// FieldFrozenBalance holds the string denoting the frozen_balance field in the database.
FieldFrozenBalance = "frozen_balance"
// FieldConcurrency holds the string denoting the concurrency field in the database.
FieldConcurrency = "concurrency"
// FieldStatus holds the string denoting the status field in the database.
@@ -199,6 +201,7 @@ var Columns = []string{
FieldPasswordHash,
FieldRole,
FieldBalance,
FieldFrozenBalance,
FieldConcurrency,
FieldStatus,
FieldUsername,
@@ -257,6 +260,8 @@ var (
RoleValidator func(string) error
// DefaultBalance holds the default value on creation for the "balance" field.
DefaultBalance float64
// DefaultFrozenBalance holds the default value on creation for the "frozen_balance" field.
DefaultFrozenBalance float64
// DefaultConcurrency holds the default value on creation for the "concurrency" field.
DefaultConcurrency int
// DefaultStatus holds the default value on creation for the "status" field.
@@ -330,6 +335,11 @@ func ByBalance(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBalance, opts...).ToFunc()
}
// ByFrozenBalance orders the results by the frozen_balance field.
func ByFrozenBalance(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFrozenBalance, opts...).ToFunc()
}
// ByConcurrency orders the results by the concurrency field.
func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
+45
View File
@@ -90,6 +90,11 @@ func Balance(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldBalance, v))
}
// FrozenBalance applies equality check predicate on the "frozen_balance" field. It's identical to FrozenBalanceEQ.
func FrozenBalance(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldFrozenBalance, v))
}
// Concurrency applies equality check predicate on the "concurrency" field. It's identical to ConcurrencyEQ.
func Concurrency(v int) predicate.User {
return predicate.User(sql.FieldEQ(FieldConcurrency, v))
@@ -535,6 +540,46 @@ func BalanceLTE(v float64) predicate.User {
return predicate.User(sql.FieldLTE(FieldBalance, v))
}
// FrozenBalanceEQ applies the EQ predicate on the "frozen_balance" field.
func FrozenBalanceEQ(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldFrozenBalance, v))
}
// FrozenBalanceNEQ applies the NEQ predicate on the "frozen_balance" field.
func FrozenBalanceNEQ(v float64) predicate.User {
return predicate.User(sql.FieldNEQ(FieldFrozenBalance, v))
}
// FrozenBalanceIn applies the In predicate on the "frozen_balance" field.
func FrozenBalanceIn(vs ...float64) predicate.User {
return predicate.User(sql.FieldIn(FieldFrozenBalance, vs...))
}
// FrozenBalanceNotIn applies the NotIn predicate on the "frozen_balance" field.
func FrozenBalanceNotIn(vs ...float64) predicate.User {
return predicate.User(sql.FieldNotIn(FieldFrozenBalance, vs...))
}
// FrozenBalanceGT applies the GT predicate on the "frozen_balance" field.
func FrozenBalanceGT(v float64) predicate.User {
return predicate.User(sql.FieldGT(FieldFrozenBalance, v))
}
// FrozenBalanceGTE applies the GTE predicate on the "frozen_balance" field.
func FrozenBalanceGTE(v float64) predicate.User {
return predicate.User(sql.FieldGTE(FieldFrozenBalance, v))
}
// FrozenBalanceLT applies the LT predicate on the "frozen_balance" field.
func FrozenBalanceLT(v float64) predicate.User {
return predicate.User(sql.FieldLT(FieldFrozenBalance, v))
}
// FrozenBalanceLTE applies the LTE predicate on the "frozen_balance" field.
func FrozenBalanceLTE(v float64) predicate.User {
return predicate.User(sql.FieldLTE(FieldFrozenBalance, v))
}
// ConcurrencyEQ applies the EQ predicate on the "concurrency" field.
func ConcurrencyEQ(v int) predicate.User {
return predicate.User(sql.FieldEQ(FieldConcurrency, v))
+85
View File
@@ -116,6 +116,20 @@ func (_c *UserCreate) SetNillableBalance(v *float64) *UserCreate {
return _c
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_c *UserCreate) SetFrozenBalance(v float64) *UserCreate {
_c.mutation.SetFrozenBalance(v)
return _c
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_c *UserCreate) SetNillableFrozenBalance(v *float64) *UserCreate {
if v != nil {
_c.SetFrozenBalance(*v)
}
return _c
}
// SetConcurrency sets the "concurrency" field.
func (_c *UserCreate) SetConcurrency(v int) *UserCreate {
_c.mutation.SetConcurrency(v)
@@ -594,6 +608,10 @@ func (_c *UserCreate) defaults() error {
v := user.DefaultBalance
_c.mutation.SetBalance(v)
}
if _, ok := _c.mutation.FrozenBalance(); !ok {
v := user.DefaultFrozenBalance
_c.mutation.SetFrozenBalance(v)
}
if _, ok := _c.mutation.Concurrency(); !ok {
v := user.DefaultConcurrency
_c.mutation.SetConcurrency(v)
@@ -676,6 +694,9 @@ func (_c *UserCreate) check() error {
if _, ok := _c.mutation.Balance(); !ok {
return &ValidationError{Name: "balance", err: errors.New(`ent: missing required field "User.balance"`)}
}
if _, ok := _c.mutation.FrozenBalance(); !ok {
return &ValidationError{Name: "frozen_balance", err: errors.New(`ent: missing required field "User.frozen_balance"`)}
}
if _, ok := _c.mutation.Concurrency(); !ok {
return &ValidationError{Name: "concurrency", err: errors.New(`ent: missing required field "User.concurrency"`)}
}
@@ -779,6 +800,10 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
_spec.SetField(user.FieldBalance, field.TypeFloat64, value)
_node.Balance = value
}
if value, ok := _c.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
_node.FrozenBalance = value
}
if value, ok := _c.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
_node.Concurrency = value
@@ -1191,6 +1216,24 @@ func (u *UserUpsert) AddBalance(v float64) *UserUpsert {
return u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsert) SetFrozenBalance(v float64) *UserUpsert {
u.Set(user.FieldFrozenBalance, v)
return u
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsert) UpdateFrozenBalance() *UserUpsert {
u.SetExcluded(user.FieldFrozenBalance)
return u
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsert) AddFrozenBalance(v float64) *UserUpsert {
u.Add(user.FieldFrozenBalance, v)
return u
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsert) SetConcurrency(v int) *UserUpsert {
u.Set(user.FieldConcurrency, v)
@@ -1580,6 +1623,27 @@ func (u *UserUpsertOne) UpdateBalance() *UserUpsertOne {
})
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsertOne) SetFrozenBalance(v float64) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.SetFrozenBalance(v)
})
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsertOne) AddFrozenBalance(v float64) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.AddFrozenBalance(v)
})
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsertOne) UpdateFrozenBalance() *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.UpdateFrozenBalance()
})
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsertOne) SetConcurrency(v int) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
@@ -2176,6 +2240,27 @@ func (u *UserUpsertBulk) UpdateBalance() *UserUpsertBulk {
})
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsertBulk) SetFrozenBalance(v float64) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.SetFrozenBalance(v)
})
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsertBulk) AddFrozenBalance(v float64) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.AddFrozenBalance(v)
})
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsertBulk) UpdateFrozenBalance() *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.UpdateFrozenBalance()
})
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsertBulk) SetConcurrency(v int) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
+54
View File
@@ -129,6 +129,27 @@ func (_u *UserUpdate) AddBalance(v float64) *UserUpdate {
return _u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_u *UserUpdate) SetFrozenBalance(v float64) *UserUpdate {
_u.mutation.ResetFrozenBalance()
_u.mutation.SetFrozenBalance(v)
return _u
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_u *UserUpdate) SetNillableFrozenBalance(v *float64) *UserUpdate {
if v != nil {
_u.SetFrozenBalance(*v)
}
return _u
}
// AddFrozenBalance adds value to the "frozen_balance" field.
func (_u *UserUpdate) AddFrozenBalance(v float64) *UserUpdate {
_u.mutation.AddFrozenBalance(v)
return _u
}
// SetConcurrency sets the "concurrency" field.
func (_u *UserUpdate) SetConcurrency(v int) *UserUpdate {
_u.mutation.ResetConcurrency()
@@ -997,6 +1018,12 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedBalance(); ok {
_spec.AddField(user.FieldBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedFrozenBalance(); ok {
_spec.AddField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
}
@@ -1778,6 +1805,27 @@ func (_u *UserUpdateOne) AddBalance(v float64) *UserUpdateOne {
return _u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_u *UserUpdateOne) SetFrozenBalance(v float64) *UserUpdateOne {
_u.mutation.ResetFrozenBalance()
_u.mutation.SetFrozenBalance(v)
return _u
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillableFrozenBalance(v *float64) *UserUpdateOne {
if v != nil {
_u.SetFrozenBalance(*v)
}
return _u
}
// AddFrozenBalance adds value to the "frozen_balance" field.
func (_u *UserUpdateOne) AddFrozenBalance(v float64) *UserUpdateOne {
_u.mutation.AddFrozenBalance(v)
return _u
}
// SetConcurrency sets the "concurrency" field.
func (_u *UserUpdateOne) SetConcurrency(v int) *UserUpdateOne {
_u.mutation.ResetConcurrency()
@@ -2676,6 +2724,12 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.AddedBalance(); ok {
_spec.AddField(user.FieldBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedFrozenBalance(); ok {
_spec.AddField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
}
+14 -12
View File
@@ -1,6 +1,6 @@
module github.com/Wei-Shaw/sub2api
go 1.26.4
go 1.26.5
require (
entgo.io/ent v0.14.5
@@ -8,11 +8,10 @@ require (
github.com/alicebob/miniredis/v2 v2.38.0
github.com/alitto/pond/v2 v2.6.2
github.com/andybalholm/brotli v1.2.0
github.com/aws/aws-sdk-go-v2 v1.41.3
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2/config v1.32.10
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
github.com/aws/smithy-go v1.24.2
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
github.com/cespare/xxhash/v2 v2.3.0
github.com/coder/websocket v1.8.14
github.com/dgraph-io/ristretto v0.2.0
@@ -39,6 +38,7 @@ require (
github.com/testcontainers/testcontainers-go/modules/redis v0.40.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
github.com/tiktoken-go/tokenizer v0.8.0
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
github.com/zeromicro/go-zero v1.9.4
go.uber.org/zap v1.24.0
@@ -59,20 +59,21 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
@@ -86,6 +87,7 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2/v2 v2.1.0 // indirect
github.com/docker/docker v28.5.1+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
+24 -42
View File
@@ -24,34 +24,34 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
@@ -108,6 +108,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2/v2 v2.1.0 h1:jHXRmHRZGbuQzDZjMlCAXOvQb75iv3HyLDzXGj5H1AY=
github.com/dlclark/regexp2/v2 v2.1.0/go.mod h1:Bz5TMy5d8fPK0ximH0Yi9KvsRHNnvXqUx9XG6a4wB+I=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
@@ -218,8 +220,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
@@ -253,8 +253,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -284,8 +282,6 @@ github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEv
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@@ -318,8 +314,6 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
@@ -358,6 +352,8 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tiktoken-go/tokenizer v0.8.0 h1:drHWno2Zx3eAm/hk/LmvBKXPpSImB7BRyh/ru4+3Q7Y=
github.com/tiktoken-go/tokenizer v0.8.0/go.mod h1:pTmPz4r14MV3JkUGAmAcdLdYhSxN68MCjrP+EoxBdx0=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -413,20 +409,14 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -440,22 +430,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+198 -8
View File
@@ -29,7 +29,7 @@ const (
// DefaultCSPPolicy is the default Content-Security-Policy with nonce support
// __CSP_NONCE__ will be replaced with actual nonce at request time by the SecurityHeaders middleware
const DefaultCSPPolicy = "default-src 'self'; script-src 'self' __CSP_NONCE__ https://challenges.cloudflare.com https://static.cloudflareinsights.com https://*.stripe.com https://static.airwallex.com https://checkout.airwallex.com https://static-demo.airwallex.com https://checkout-demo.airwallex.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://static.airwallex.com https://checkout.airwallex.com https://static-demo.airwallex.com https://checkout-demo.airwallex.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:; frame-src https://challenges.cloudflare.com https://*.stripe.com https://checkout.airwallex.com https://checkout-demo.airwallex.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
const DefaultCSPPolicy = "default-src 'self'; script-src 'self' __CSP_NONCE__ https://challenges.cloudflare.com https://static.cloudflareinsights.com https://*.stripe.com https://static.airwallex.com https://checkout.airwallex.com https://static-demo.airwallex.com https://checkout-demo.airwallex.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://static.airwallex.com https://checkout.airwallex.com https://static-demo.airwallex.com https://checkout-demo.airwallex.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:; frame-src https://challenges.cloudflare.com https://*.stripe.com https://checkout.airwallex.com https://checkout-demo.airwallex.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
// UMQ(用户消息队列)模式常量
const (
@@ -93,6 +93,7 @@ type Config struct {
Gemini GeminiConfig `mapstructure:"gemini"`
Update UpdateConfig `mapstructure:"update"`
Idempotency IdempotencyConfig `mapstructure:"idempotency"`
BatchImage BatchImageConfig `mapstructure:"batch_image"`
}
type LogConfig struct {
@@ -175,6 +176,56 @@ type IdempotencyConfig struct {
CleanupBatchSize int `mapstructure:"cleanup_batch_size"`
}
type BatchImageConfig struct {
Enabled bool `mapstructure:"enabled"`
MaxItemsPerJobDefault int `mapstructure:"max_items_per_job_default"`
MaxItemsPerJobTrial int `mapstructure:"max_items_per_job_trial"`
MaxOutputImagesPerJob int `mapstructure:"max_output_images_per_job"`
MaxOutputImagesPerItem int `mapstructure:"max_output_images_per_item"`
MaxPromptCharsPerItem int `mapstructure:"max_prompt_chars_per_item"`
MaxReferenceImagesPerJob int `mapstructure:"max_reference_images_per_job"`
MaxReferenceInlineBytesPerJob int `mapstructure:"max_reference_inline_bytes_per_job"`
DefaultResponseMimeType string `mapstructure:"default_response_mime_type"`
DefaultImageSize string `mapstructure:"default_image_size"`
MaxDownloadItemsZip int `mapstructure:"max_download_items_zip"`
MaxDownloadBytesPerRequest int64 `mapstructure:"max_download_bytes_per_request"`
MaxDownloadDurationSeconds int `mapstructure:"max_download_duration_seconds"`
MaxDownloadConcurrencyPerUser int `mapstructure:"max_download_concurrency_per_user"`
InputRetentionAfterTerminalHours int `mapstructure:"input_retention_after_terminal_hours"`
OutputRetentionAfterTerminalHours int `mapstructure:"output_retention_after_terminal_hours"`
OutputRetentionMaxDays int `mapstructure:"output_retention_max_days"`
CleanupIntervalMinutes int `mapstructure:"cleanup_interval_minutes"`
CleanupBatchSize int `mapstructure:"cleanup_batch_size"`
QueueEnabled bool `mapstructure:"queue_enabled"`
QueueReadyKey string `mapstructure:"queue_ready_key"`
QueueDelayedKey string `mapstructure:"queue_delayed_key"`
QueueActiveKey string `mapstructure:"queue_active_key"`
InflightKeyPrefix string `mapstructure:"inflight_key_prefix"`
LockKeyPrefix string `mapstructure:"lock_key_prefix"`
IdempotencyKeyPrefix string `mapstructure:"idempotency_key_prefix"`
InflightTTLSeconds int `mapstructure:"inflight_ttl_seconds"`
JobLockTTLSeconds int `mapstructure:"job_lock_ttl_seconds"`
DefaultRequeueDelaySeconds int `mapstructure:"default_requeue_delay_seconds"`
ErrorRetryDelaySeconds int `mapstructure:"error_retry_delay_seconds"`
LockConflictDelaySeconds int `mapstructure:"lock_conflict_delay_seconds"`
StaleActiveAfterSeconds int `mapstructure:"stale_active_after_seconds"`
DelayedMoverIntervalSeconds int `mapstructure:"delayed_mover_interval_seconds"`
RecoveryIntervalSeconds int `mapstructure:"recovery_interval_seconds"`
DelayedMoveLimit int `mapstructure:"delayed_move_limit"`
RecoverLimit int `mapstructure:"recover_limit"`
VertexEnabled bool `mapstructure:"vertex_enabled"`
VertexProjectID string `mapstructure:"vertex_project_id"`
VertexLocation string `mapstructure:"vertex_location"`
// VertexManagedGCSBucket is a server-owned bucket for batch JSONL input/output.
// Disable Cloud Storage soft delete on this bucket to avoid retaining deleted batch objects.
VertexManagedGCSBucket string `mapstructure:"vertex_managed_gcs_bucket"`
VertexManagedGCSPrefix string `mapstructure:"vertex_managed_gcs_prefix"`
VertexInputRetentionHours int `mapstructure:"vertex_input_retention_hours"`
VertexOutputRetentionHours int `mapstructure:"vertex_output_retention_hours"`
VertexBatchPredictionBaseURL string `mapstructure:"vertex_batch_prediction_base_url"`
VertexGCSBaseURL string `mapstructure:"vertex_gcs_base_url"`
}
type LinuxDoConnectConfig struct {
Enabled bool `mapstructure:"enabled"`
ClientID string `mapstructure:"client_id"`
@@ -643,6 +694,10 @@ type ProxyProbeConfig struct {
type BillingConfig struct {
CircuitBreaker CircuitBreakerConfig `mapstructure:"circuit_breaker"`
// MinimumBalanceReserve is the conservative preflight floor for balance billing.
// Requests in balance mode are rejected when the cached balance is below this
// amount, even if it is still positive. Set to 0 to keep the legacy balance > 0 gate.
MinimumBalanceReserve float64 `mapstructure:"minimum_balance_reserve"`
// UserPlatformQuotaCacheTTLSeconds 用户 × 平台 quota 缓存 TTL(秒),默认 86400=1天,覆盖典型 daily 窗口。
// 消费点:
// - billing_cache_service.cacheWriteWorker 异步累加
@@ -717,6 +772,9 @@ type GatewayConfig struct {
// OpenAIPassthroughAllowTimeoutHeaders: OpenAI 透传模式是否放行客户端超时头
// 关闭(默认)可避免 x-stainless-timeout 等头导致上游提前断流。
OpenAIPassthroughAllowTimeoutHeaders bool `mapstructure:"openai_passthrough_allow_timeout_headers"`
// OpenAICompactModel: /responses/compact 上游使用的模型。
// compact 端点支持模型滞后于普通 /responses 时,可用该配置降级规避上游错误。
OpenAICompactModel string `mapstructure:"openai_compact_model"`
// OpenAIWS: OpenAI Responses WebSocket 配置(默认开启,可按需回滚到 HTTP)
OpenAIWS GatewayOpenAIWSConfig `mapstructure:"openai_ws"`
// OpenAIScheduler: OpenAI 高级调度器粘性逃逸配置
@@ -863,7 +921,7 @@ func (c *UserMessageQueueConfig) GetEffectiveMode() string {
type GatewayOpenAIWSConfig struct {
// ModeRouterV2Enabled: 新版 WS mode 路由开关(默认 false;关闭时保持 legacy 行为)
ModeRouterV2Enabled bool `mapstructure:"mode_router_v2_enabled"`
// IngressModeDefault: ingress 默认模式(off/ctx_pool/passthrough)
// IngressModeDefault: ingress 默认模式(off/ctx_pool/passthrough/http_bridge)
IngressModeDefault string `mapstructure:"ingress_mode_default"`
// Enabled: 全局总开关(默认 true)
Enabled bool `mapstructure:"enabled"`
@@ -957,6 +1015,14 @@ type GatewayOpenAIWSSchedulerScoreWeights struct {
Queue float64 `mapstructure:"queue"`
ErrorRate float64 `mapstructure:"error_rate"`
TTFT float64 `mapstructure:"ttft"`
// Reset 倾向「会话窗口最早重置」的账号(use-it-or-lose-it)。
// >0 时,剩余重置时间越短的账号得分越高,从而被优先用尽。默认 0(关闭,不改变原有行为)。
Reset float64 `mapstructure:"reset"`
// QuotaHeadroom 倾向 7d 剩余额度更健康的账号;默认 0(关闭,不改变原有行为)。
QuotaHeadroom float64 `mapstructure:"quota_headroom"`
// PreviousResponse/SessionSticky 仅在开启 OpenAI 高级调度的粘性加权时生效。
PreviousResponse float64 `mapstructure:"previous_response"`
SessionSticky float64 `mapstructure:"session_sticky"`
}
// GatewayOpenAISchedulerConfig OpenAI 高级调度器配置。
@@ -1055,6 +1121,11 @@ type GatewaySchedulingConfig struct {
// 兜底层账户选择策略: "last_used"(按最后使用时间排序,默认) 或 "random"(随机)
FallbackSelectionMode string `mapstructure:"fallback_selection_mode"`
// PreferSoonestReset 开启后,负载感知选择会优先选用「会话窗口最早重置」的账号
// (use-it-or-lose-it:先用尽即将重置的账号,保留重置时间还很久的账号)。
// 默认 false,保持原有「优先级 → 负载率 → LRU」行为不变。
PreferSoonestReset bool `mapstructure:"prefer_soonest_reset"`
// 负载计算
LoadBatchEnabled bool `mapstructure:"load_batch_enabled"`
LoadBatchCacheTTLMS int `mapstructure:"load_batch_cache_ttl_ms"`
@@ -1607,6 +1678,7 @@ func setDefaults() {
viper.SetDefault("billing.circuit_breaker.failure_threshold", 5)
viper.SetDefault("billing.circuit_breaker.reset_timeout_seconds", 30)
viper.SetDefault("billing.circuit_breaker.half_open_requests", 3)
viper.SetDefault("billing.minimum_balance_reserve", 0.000001)
viper.SetDefault("billing.user_platform_quota_cache_ttl_seconds", 86400)
viper.SetDefault("billing.user_platform_quota_sentinel_ttl_seconds", 3600)
@@ -1711,6 +1783,53 @@ func setDefaults() {
viper.SetDefault("redis.min_idle_conns", 128)
viper.SetDefault("redis.enable_tls", false)
// Batch Image queue
viper.SetDefault("batch_image.enabled", false)
viper.SetDefault("batch_image.max_items_per_job_default", 200)
viper.SetDefault("batch_image.max_items_per_job_trial", 50)
viper.SetDefault("batch_image.max_output_images_per_job", 200)
viper.SetDefault("batch_image.max_output_images_per_item", 4)
viper.SetDefault("batch_image.max_prompt_chars_per_item", 8000)
viper.SetDefault("batch_image.max_reference_images_per_job", 1000)
viper.SetDefault("batch_image.max_reference_inline_bytes_per_job", 134217728)
viper.SetDefault("batch_image.default_response_mime_type", "image/png")
viper.SetDefault("batch_image.default_image_size", "1K")
viper.SetDefault("batch_image.max_download_items_zip", 200)
viper.SetDefault("batch_image.max_download_bytes_per_request", 536870912)
viper.SetDefault("batch_image.max_download_duration_seconds", 600)
viper.SetDefault("batch_image.max_download_concurrency_per_user", 1)
viper.SetDefault("batch_image.input_retention_after_terminal_hours", 24)
viper.SetDefault("batch_image.output_retention_after_terminal_hours", 72)
viper.SetDefault("batch_image.output_retention_max_days", 7)
viper.SetDefault("batch_image.cleanup_interval_minutes", 30)
viper.SetDefault("batch_image.cleanup_batch_size", 100)
viper.SetDefault("batch_image.queue_enabled", false)
viper.SetDefault("batch_image.queue_ready_key", "batch_image:queue:ready")
viper.SetDefault("batch_image.queue_delayed_key", "batch_image:queue:delayed")
viper.SetDefault("batch_image.queue_active_key", "batch_image:queue:active")
viper.SetDefault("batch_image.inflight_key_prefix", "batch_image:queue:inflight:")
viper.SetDefault("batch_image.lock_key_prefix", "batch_image:queue:lock:")
viper.SetDefault("batch_image.idempotency_key_prefix", "batch_image:queue:idem:")
viper.SetDefault("batch_image.inflight_ttl_seconds", 604800)
viper.SetDefault("batch_image.job_lock_ttl_seconds", 300)
viper.SetDefault("batch_image.default_requeue_delay_seconds", 30)
viper.SetDefault("batch_image.error_retry_delay_seconds", 60)
viper.SetDefault("batch_image.lock_conflict_delay_seconds", 5)
viper.SetDefault("batch_image.stale_active_after_seconds", 600)
viper.SetDefault("batch_image.delayed_mover_interval_seconds", 5)
viper.SetDefault("batch_image.recovery_interval_seconds", 300)
viper.SetDefault("batch_image.delayed_move_limit", 100)
viper.SetDefault("batch_image.recover_limit", 100)
viper.SetDefault("batch_image.vertex_enabled", false)
viper.SetDefault("batch_image.vertex_project_id", "")
viper.SetDefault("batch_image.vertex_location", "global")
viper.SetDefault("batch_image.vertex_managed_gcs_bucket", "")
viper.SetDefault("batch_image.vertex_managed_gcs_prefix", "batch-image/{env}/{batch_id}")
viper.SetDefault("batch_image.vertex_input_retention_hours", 24)
viper.SetDefault("batch_image.vertex_output_retention_hours", 72)
viper.SetDefault("batch_image.vertex_batch_prediction_base_url", "")
viper.SetDefault("batch_image.vertex_gcs_base_url", "")
// Ops (vNext)
viper.SetDefault("ops.enabled", true)
viper.SetDefault("ops.use_preaggregated_tables", true)
@@ -1821,6 +1940,7 @@ func setDefaults() {
viper.SetDefault("gateway.force_codex_cli", false)
viper.SetDefault("gateway.codex_image_generation_bridge_enabled", false)
viper.SetDefault("gateway.openai_passthrough_allow_timeout_headers", false)
viper.SetDefault("gateway.openai_compact_model", "gpt-5.4")
// OpenAI Responses WebSocket(默认开启;可通过 force_http 紧急回滚)
viper.SetDefault("gateway.openai_ws.enabled", true)
viper.SetDefault("gateway.openai_ws.mode_router_v2_enabled", false)
@@ -1870,6 +1990,10 @@ func setDefaults() {
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.queue", 0.7)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.error_rate", 0.8)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.ttft", 0.5)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.reset", 0.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.quota_headroom", 0.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.previous_response", 5.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.session_sticky", 3.0)
// OpenAI HTTP upstream protocol strategy
viper.SetDefault("gateway.openai_http2.enabled", true)
viper.SetDefault("gateway.openai_http2.allow_proxy_fallback_to_http1", true)
@@ -1906,6 +2030,7 @@ func setDefaults() {
viper.SetDefault("gateway.scheduling.fallback_wait_timeout", 30*time.Second)
viper.SetDefault("gateway.scheduling.fallback_max_waiting", 100)
viper.SetDefault("gateway.scheduling.fallback_selection_mode", "last_used")
viper.SetDefault("gateway.scheduling.prefer_soonest_reset", false)
viper.SetDefault("gateway.scheduling.load_batch_enabled", true)
viper.SetDefault("gateway.scheduling.load_batch_cache_ttl_ms", 200)
viper.SetDefault("gateway.scheduling.snapshot_mget_chunk_size", 128)
@@ -1923,7 +2048,10 @@ func setDefaults() {
viper.SetDefault("gateway.usage_record.worker_count", 128)
viper.SetDefault("gateway.usage_record.queue_size", 16384)
viper.SetDefault("gateway.usage_record.task_timeout_seconds", 5)
viper.SetDefault("gateway.usage_record.overflow_policy", UsageRecordOverflowPolicySample)
// 默认 sync:队列满时由提交方内联执行(提交点在响应写出之后,不阻塞客户端)。
// sample/drop 会在溢出时静默丢弃计费任务,造成扣费与 usage_logs 对账缺口(issue #3656),
// 仅供显式配置的运维场景使用。
viper.SetDefault("gateway.usage_record.overflow_policy", UsageRecordOverflowPolicySync)
viper.SetDefault("gateway.usage_record.overflow_sample_percent", 10)
viper.SetDefault("gateway.usage_record.auto_scale_enabled", true)
viper.SetDefault("gateway.usage_record.auto_scale_min_workers", 128)
@@ -2267,6 +2395,9 @@ func (c *Config) Validate() error {
return fmt.Errorf("billing.circuit_breaker.half_open_requests must be positive")
}
}
if c.Billing.MinimumBalanceReserve < 0 {
return fmt.Errorf("billing.minimum_balance_reserve must be non-negative")
}
if c.Database.MaxOpenConns <= 0 {
return fmt.Errorf("database.max_open_conns must be positive")
}
@@ -2300,6 +2431,61 @@ func (c *Config) Validate() error {
if c.Redis.MinIdleConns > c.Redis.PoolSize {
return fmt.Errorf("redis.min_idle_conns cannot exceed redis.pool_size")
}
if c.BatchImage.QueueEnabled {
if strings.TrimSpace(c.BatchImage.QueueReadyKey) == "" {
return fmt.Errorf("batch_image.queue_ready_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.QueueDelayedKey) == "" {
return fmt.Errorf("batch_image.queue_delayed_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.QueueActiveKey) == "" {
return fmt.Errorf("batch_image.queue_active_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.InflightKeyPrefix) == "" {
return fmt.Errorf("batch_image.inflight_key_prefix must not be empty")
}
if strings.TrimSpace(c.BatchImage.LockKeyPrefix) == "" {
return fmt.Errorf("batch_image.lock_key_prefix must not be empty")
}
if c.BatchImage.InflightTTLSeconds <= 0 {
return fmt.Errorf("batch_image.inflight_ttl_seconds must be positive")
}
if c.BatchImage.JobLockTTLSeconds <= 0 {
return fmt.Errorf("batch_image.job_lock_ttl_seconds must be positive")
}
if c.BatchImage.StaleActiveAfterSeconds <= 0 {
return fmt.Errorf("batch_image.stale_active_after_seconds must be positive")
}
if c.BatchImage.DelayedMoveLimit <= 0 {
return fmt.Errorf("batch_image.delayed_move_limit must be positive")
}
if c.BatchImage.RecoverLimit <= 0 {
return fmt.Errorf("batch_image.recover_limit must be positive")
}
}
if c.BatchImage.VertexEnabled {
if strings.TrimSpace(c.BatchImage.VertexManagedGCSBucket) == "" {
return fmt.Errorf("batch_image.vertex_managed_gcs_bucket must not be empty when vertex is enabled")
}
if strings.Contains(c.BatchImage.VertexManagedGCSBucket, "://") {
return fmt.Errorf("batch_image.vertex_managed_gcs_bucket must be a bucket name, not a URI")
}
if strings.TrimSpace(c.BatchImage.VertexLocation) == "" {
return fmt.Errorf("batch_image.vertex_location must not be empty when vertex is enabled")
}
if strings.TrimSpace(c.BatchImage.VertexManagedGCSPrefix) == "" {
return fmt.Errorf("batch_image.vertex_managed_gcs_prefix must not be empty when vertex is enabled")
}
if !strings.Contains(c.BatchImage.VertexManagedGCSPrefix, "{batch_id}") {
return fmt.Errorf("batch_image.vertex_managed_gcs_prefix must contain {batch_id}")
}
if c.BatchImage.VertexInputRetentionHours <= 0 {
return fmt.Errorf("batch_image.vertex_input_retention_hours must be positive")
}
if c.BatchImage.VertexOutputRetentionHours <= 0 {
return fmt.Errorf("batch_image.vertex_output_retention_hours must be positive")
}
}
if c.Dashboard.Enabled {
if c.Dashboard.StatsFreshTTLSeconds <= 0 {
return fmt.Errorf("dashboard_cache.stats_fresh_ttl_seconds must be positive")
@@ -2603,11 +2789,11 @@ func (c *Config) Validate() error {
}
if mode := strings.ToLower(strings.TrimSpace(c.Gateway.OpenAIWS.IngressModeDefault)); mode != "" {
switch mode {
case "off", "ctx_pool", "passthrough":
case "off", "ctx_pool", "passthrough", "http_bridge":
case "shared", "dedicated":
slog.Warn("gateway.openai_ws.ingress_mode_default is deprecated, treating as ctx_pool; please update to off|ctx_pool|passthrough", "value", mode)
slog.Warn("gateway.openai_ws.ingress_mode_default is deprecated, treating as ctx_pool; please update to off|ctx_pool|passthrough|http_bridge", "value", mode)
default:
return fmt.Errorf("gateway.openai_ws.ingress_mode_default must be one of off|ctx_pool|passthrough")
return fmt.Errorf("gateway.openai_ws.ingress_mode_default must be one of off|ctx_pool|passthrough|http_bridge")
}
}
if mode := strings.ToLower(strings.TrimSpace(c.Gateway.OpenAIWS.StoreDisabledConnMode)); mode != "" {
@@ -2645,14 +2831,18 @@ func (c *Config) Validate() error {
c.Gateway.OpenAIWS.SchedulerScoreWeights.Load < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT < 0 {
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.PreviousResponse < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.SessionSticky < 0 {
return fmt.Errorf("gateway.openai_ws.scheduler_score_weights.* must be non-negative")
}
weightSum := c.Gateway.OpenAIWS.SchedulerScoreWeights.Priority +
c.Gateway.OpenAIWS.SchedulerScoreWeights.Load +
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue +
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate +
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT +
c.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom
if weightSum <= 0 {
return fmt.Errorf("gateway.openai_ws.scheduler_score_weights must not all be zero")
}
+53 -3
View File
@@ -167,6 +167,9 @@ func TestLoadDefaultOpenAIWSConfig(t *testing.T) {
if cfg.Gateway.OpenAIWS.PayloadLogSampleRate != 0.2 {
t.Fatalf("Gateway.OpenAIWS.PayloadLogSampleRate = %v, want 0.2", cfg.Gateway.OpenAIWS.PayloadLogSampleRate)
}
if cfg.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom != 0 {
t.Fatalf("Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom = %v, want 0", cfg.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom)
}
if !cfg.Gateway.OpenAIWS.StoreDisabledForceNewConn {
t.Fatalf("Gateway.OpenAIWS.StoreDisabledForceNewConn = false, want true")
}
@@ -181,6 +184,23 @@ func TestLoadDefaultOpenAIWSConfig(t *testing.T) {
}
}
func TestLoadDefaultOpenAICompactModel(t *testing.T) {
resetViperWithJWTSecret(t)
cfg, err := Load()
require.NoError(t, err)
require.Equal(t, "gpt-5.4", cfg.Gateway.OpenAICompactModel)
}
func TestLoadOpenAICompactModelFromEnv(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("GATEWAY_OPENAI_COMPACT_MODEL", "gpt-5.3-codex")
cfg, err := Load()
require.NoError(t, err)
require.Equal(t, "gpt-5.3-codex", cfg.Gateway.OpenAICompactModel)
}
func TestLoadDefaultOpenAIHTTP2Enabled(t *testing.T) {
resetViperWithJWTSecret(t)
@@ -250,6 +270,14 @@ func TestLoadDefaultIdempotencyConfig(t *testing.T) {
}
}
func TestLoadDefaultBatchImageQueueDisabled(t *testing.T) {
resetViperWithJWTSecret(t)
cfg, err := Load()
require.NoError(t, err)
require.False(t, cfg.BatchImage.QueueEnabled)
}
func TestLoadIdempotencyConfigFromEnv(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("IDEMPOTENCY_OBSERVE_ONLY", "false")
@@ -1157,6 +1185,11 @@ func TestValidateConfigErrors(t *testing.T) {
mutate: func(c *Config) { c.Billing.CircuitBreaker.HalfOpenRequests = 0 },
wantErr: "billing.circuit_breaker.half_open_requests",
},
{
name: "billing minimum balance reserve",
mutate: func(c *Config) { c.Billing.MinimumBalanceReserve = -0.01 },
wantErr: "billing.minimum_balance_reserve",
},
{
name: "database max open conns",
mutate: func(c *Config) { c.Database.MaxOpenConns = 0 },
@@ -1670,7 +1703,7 @@ func TestValidateConfig_OpenAIWSRules(t *testing.T) {
wantErr: "gateway.openai_ws.store_disabled_conn_mode",
},
{
name: "ingress_mode_default 必须为 off|ctx_pool|passthrough",
name: "ingress_mode_default 必须为 off|ctx_pool|passthrough|http_bridge",
mutate: func(c *Config) { c.Gateway.OpenAIWS.IngressModeDefault = "invalid" },
wantErr: "gateway.openai_ws.ingress_mode_default",
},
@@ -1712,6 +1745,11 @@ func TestValidateConfig_OpenAIWSRules(t *testing.T) {
mutate: func(c *Config) { c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue = -0.1 },
wantErr: "gateway.openai_ws.scheduler_score_weights.* must be non-negative",
},
{
name: "scheduler_score_weights quota_headroom 不能为负数",
mutate: func(c *Config) { c.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom = -0.1 },
wantErr: "gateway.openai_ws.scheduler_score_weights.* must be non-negative",
},
{
name: "scheduler_score_weights 不能全为 0",
mutate: func(c *Config) {
@@ -1751,6 +1789,18 @@ func TestValidateConfig_OpenAIWSRules(t *testing.T) {
require.Contains(t, err.Error(), tc.wantErr)
})
}
t.Run("quota_headroom 可作为唯一有效调度权重", func(t *testing.T) {
cfg := buildValid(t)
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.Priority = 0
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.Load = 0
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.Queue = 0
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate = 0
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT = 0
cfg.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom = 0.1
require.NoError(t, cfg.Validate())
})
}
func TestValidateConfig_AutoScaleDisabledIgnoreAutoScaleFields(t *testing.T) {
@@ -1861,8 +1911,8 @@ func TestLoad_DefaultGatewayUsageRecordConfig(t *testing.T) {
if cfg.Gateway.UsageRecord.TaskTimeoutSeconds != 5 {
t.Fatalf("task_timeout_seconds = %d, want 5", cfg.Gateway.UsageRecord.TaskTimeoutSeconds)
}
if cfg.Gateway.UsageRecord.OverflowPolicy != UsageRecordOverflowPolicySample {
t.Fatalf("overflow_policy = %s, want %s", cfg.Gateway.UsageRecord.OverflowPolicy, UsageRecordOverflowPolicySample)
if cfg.Gateway.UsageRecord.OverflowPolicy != UsageRecordOverflowPolicySync {
t.Fatalf("overflow_policy = %s, want %s", cfg.Gateway.UsageRecord.OverflowPolicy, UsageRecordOverflowPolicySync)
}
if cfg.Gateway.UsageRecord.OverflowSamplePercent != 10 {
t.Fatalf("overflow_sample_percent = %d, want 10", cfg.Gateway.UsageRecord.OverflowSamplePercent)
+10 -3
View File
@@ -22,6 +22,7 @@ const (
PlatformOpenAI = "openai"
PlatformGemini = "gemini"
PlatformAntigravity = "antigravity"
PlatformGrok = "grok"
)
// Account type constants
@@ -67,6 +68,9 @@ const (
SubscriptionStatusSuspended = "suspended"
)
// AntigravityGemini31ProAgentModel is the upstream route for Gemini 3.1 Pro High.
const AntigravityGemini31ProAgentModel = "gemini-pro-agent"
// DefaultAntigravityModelMapping 是 Antigravity 平台的默认模型映射
// 当账号未配置 model_mapping 时使用此默认值
// 与前端 useModelWhitelist.ts 中的 antigravityDefaultMappings 保持一致
@@ -102,10 +106,12 @@ var DefaultAntigravityModelMapping = map[string]string{
"gemini-3-flash-preview": "gemini-3-flash",
"gemini-3-pro-preview": "gemini-3-pro-high",
// Gemini 3.1 白名单
"gemini-3.1-pro-high": "gemini-3.1-pro-high",
"gemini-3.1-pro-low": "gemini-3.1-pro-low",
AntigravityGemini31ProAgentModel: AntigravityGemini31ProAgentModel,
"gemini-3.1-pro": AntigravityGemini31ProAgentModel,
"gemini-3.1-pro-high": AntigravityGemini31ProAgentModel,
"gemini-3.1-pro-low": "gemini-3.1-pro-low",
// Gemini 3.1 preview 映射
"gemini-3.1-pro-preview": "gemini-3.1-pro-high",
"gemini-3.1-pro-preview": AntigravityGemini31ProAgentModel,
// Gemini 3.1 image 白名单
"gemini-3.1-flash-image": "gemini-3.1-flash-image",
// Gemini 3.1 image preview 映射
@@ -135,6 +141,7 @@ var DefaultBedrockModelMapping = map[string]string{
"claude-opus-4-1": "us.anthropic.claude-opus-4-1-20250805-v1:0",
"claude-opus-4-20250514": "us.anthropic.claude-opus-4-20250514-v1:0",
// Claude Sonnet
"claude-sonnet-5": "us.anthropic.claude-sonnet-5-v1",
"claude-sonnet-4-6-thinking": "us.anthropic.claude-sonnet-4-6",
"claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
"claude-sonnet-4-5": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",

Some files were not shown because too many files have changed in this diff Show More