Compare commits

...
399 Commits
Author SHA1 Message Date
Wesley Liddick 41cec0db05 Merge pull request #4154 from sir1st/codex/fix-native-responses-namespace
fix: support namespace tools in native responses
2026-07-14 14:51:14 +08:00
shaw fa1641f05f fix: keep namespace verbatim on WSv2 forwards and check test type assertions
WSv2 egress relays upstream events verbatim without the HTTP-path
namespace restore, so flattening requests that take the WSv2 branch
would surface flattened tool names the client cannot match. Resolve
the WS transport decision before flattening and skip flattening only
when the request will actually go WSv2 (passthrough accounts return
via HTTP before the WSv2 branch and still flatten).

Also check all type assertions in responses_namespace_test.go to
satisfy golangci-lint errcheck.
2026-07-14 14:48:22 +08:00
shaw 252ef8b73a Merge origin/main into codex/fix-native-responses-namespace
Resolve conflict in openai_gateway_passthrough.go streaming path: keep
main's normalizeCompletedImageGenerationStatus normalization ahead of
this branch's namespace restore block, mirroring the established order
in openai_gateway_response_handling.go.
2026-07-14 14:45:04 +08:00
Wesley Liddick 53004e2e90 Merge pull request #4238 from superman2003/feat/grok-monitor-auto-probe
feat(grok): add import probes and channel monitoring
2026-07-14 14:23:04 +08:00
Wesley Liddick 11ed22d052 Merge pull request #4240 from chensunlai/fix/openai-responses-lite-image-display
fix(openai): preserve client image tools in Responses Lite
2026-07-14 14:22:50 +08:00
Wesley Liddick 03646e9434 Merge pull request #4239 from fengshao1227/fix/grok-reasoning-content-null
fix(grok): 清洗 reasoning 项中 content: null 避免 xAI 422
2026-07-14 14:22:42 +08:00
li 2e9b8d9a64 fix: resolve golangci-lint errcheck and gofmt violations in reasoning test
Use two-value type assertions with require.True(t, ok) to satisfy
errcheck, and run gofmt to fix alignment.
2026-07-14 13:23:57 +08:00
chensunlai 2f715baf05 fix(openai): preserve client image tools in Responses Lite 2026-07-14 05:19:13 +00:00
li ff639ba757 fix(grok): 清洗 reasoning 项中 content: null 避免 xAI 422
Fixes #4237
2026-07-14 13:13:32 +08:00
superman2003 16d1fbfd4e fix(ci): keep probe scheduler snapshots test-only 2026-07-14 12:45:03 +08:00
superman2003 d2d3fcf57b feat(frontend): show Grok monitoring and Free plan badge 2026-07-14 12:24:42 +08:00
superman2003 0a64a6d8ce feat(monitor): support Grok channel health checks 2026-07-14 12:24:42 +08:00
superman2003 d8a07e91a5 fix(grok): stabilize prompt cache routing identity 2026-07-14 12:24:42 +08:00
superman2003 a1b5c75ca3 feat(grok): probe newly imported OAuth accounts 2026-07-14 12:24:42 +08:00
Wesley Liddick 69bc6a87dd Merge pull request #4231 from superman2003/fix/grok-free-rolling-24h-20260714
fix(grok): use rolling 24h free quota estimate
2026-07-14 11:31:48 +08:00
Wesley Liddick c361b0606d Merge pull request #4219 from zh239ns/codex/fix-openai-images-nonstream-keepalive
fix(images): add opt-in non-stream JSON keepalive
2026-07-14 11:30:28 +08:00
shaw ac7a141a24 fix(images): align OAuth pre-response size snapshot with keepalive-adjusted metric
The snapshot taken before dispatching the OAuth response handler still used
raw c.Writer.Size(), while handleOpenAIImagesOAuthResponseError compares it
against the keepalive-adjusted size. From the second failover round on, the
previous round's heartbeat bytes made the raw snapshot positive while the
adjusted size stayed -1, so a retryable upstream error was misjudged as
'response already written': the failover was swallowed and the client got a
committed 200 with a whitespace-only body.

Snapshot now uses OpenAIImagesJSONKeepaliveAdjustedWrittenSize, matching the
comparison side. Regression test simulates a prior-round heartbeat before
ForwardImages and asserts the retryable error still converts to
UpstreamFailoverError (verified red without the fix, green with it).
2026-07-14 11:24:44 +08:00
Wesley Liddick 527279c953 Merge pull request #4225 from BenjaminAaron196/fix/reset-credit-count-fallback
fix(openai): 提升主动重置次数识别的可靠性
2026-07-14 11:11:54 +08:00
Wesley Liddick 9717699367 Merge pull request #4218 from chensunlai/fix/openai-image-generation-final-status
fix(openai): complete streamed image generation results
2026-07-14 11:00:40 +08:00
Wesley Liddick 2590b86e31 Merge pull request #4204 from jianjianai/codex/fix-scheduler-rebuild-coalescing
修复调度器并发触发重复全量重建
2026-07-14 11:00:09 +08:00
Wesley Liddick 24d908b257 Merge pull request #4203 from jianjianai/codex/fix-scheduler-pending-lag
修复调度器使用已消费事件计算 outbox 延迟
2026-07-14 10:59:57 +08:00
Wesley Liddick 9c3c560d49 Merge pull request #4206 from jianjianai/codex/fix-scheduler-proxy-expiry-event
修复代理到期改投触发全量调度重建
2026-07-14 10:59:43 +08:00
Wesley Liddick 8711882918 Merge pull request #4205 from jianjianai/codex/fix-scheduler-auto-pause-event
修复账号到期暂停触发全量调度重建
2026-07-14 10:59:34 +08:00
Wesley Liddick 27fcbace89 Merge pull request #4207 from hongheshan-svg/upstream-pr/http2-keepalive
fix(openai): 为 Codex HTTP/2 上游启用 PING 健康探测,剔除死连接
2026-07-14 10:59:22 +08:00
superman2003 30d4301bea fix(grok): use rolling 24h free quota estimate 2026-07-14 10:53:43 +08:00
Wesley Liddick 5d1c577cb2 Merge pull request #4217 from jinfeijie/feat/grok-sso-device-oauth
feat(grok): 支持 Web SSO 批量导入并转换为 Build OAuth
2026-07-14 10:25:56 +08:00
shaw d41a10111d Merge remote-tracking branch 'origin/main' into feat/grok-sso-device-oauth
# Conflicts:
#	frontend/src/api/admin/grok.ts
2026-07-14 10:19:16 +08:00
Wesley Liddick 93f2ccf3a5 Merge pull request #4188 from superman2003/fix/grok-free-quota-429-20260713
feat(grok): improve free quota probing and usage display
2026-07-14 10:14:41 +08:00
Wesley Liddick a8927d8ec7 Merge pull request #4214 from bestony/agent/devbox-coding/25c66071-1783957460
feat: add opt-in Server-Timing for Admin UI APIs
2026-07-14 10:14:17 +08:00
Wesley Liddick 623a9647c0 Merge pull request #4110 from gebdalaoli-arch/agent/fix-codex-models-apikey-upstream
fix: proxy Codex model manifests through API key upstreams
2026-07-14 10:13:56 +08:00
Wesley Liddick 41c71a1528 Merge pull request #4216 from bestony/agent/devbox-coding/3ff3c99d
feat(ops): add Host filtering to system logs
2026-07-14 10:13:40 +08:00
Wesley Liddick 1847bdf9fd Merge pull request #4179 from scientist-blockchian/fix/messages-exact-model-mapping
fix(openai): preserve exact messages dispatch mappings
2026-07-14 10:13:27 +08:00
Wesley Liddick 7358810659 Merge pull request #4180 from jianjianai/fix/openai-request-view-single-scan
fix(openai): avoid repeated request body scans
2026-07-14 10:13:11 +08:00
Wesley Liddick 029e5ce9f9 Merge pull request #4104 from gebdalaoli-arch/codex/fix-image-gen-hosted-tool-conflict
[codex] avoid duplicate hosted image tool injection
2026-07-14 10:13:00 +08:00
benjamin 8cd848313c fix(openai): improve reset credit quota detection 2026-07-14 10:07:48 +08:00
jjaw 9033e14bb7 修复账号到期暂停触发全量重建 2026-07-14 09:49:57 +08:00
jjaw 8f328d4ab3 修复代理到期改投触发全量重建 2026-07-14 09:23:02 +08:00
Wesley Liddick 74e78c3de0 Merge pull request #4115 from Arron196/fix/api-double-billing
fix: 按账号控制 OpenAI 长上下文计费
2026-07-14 08:38:57 +08:00
zh239ns 002c0b9fda fix(images): keep non-stream requests alive 2026-07-14 07:39:21 +08:00
chensunlai f2ca16577e fix(openai): complete streamed image generation results 2026-07-13 21:24:53 +00:00
jinfeijie bot 6c441637b0 fix(grok): 移除账号类型页 SSO 卡片入口
SSO 仅作为 OAuth 流程内的输入方式,避免与 OAuth/API Key 卡片风格冲突。
2026-07-14 01:47:53 +08:00
bestonyandmultica-agent 0f2ec134b5 fix(ops): bound indexed log host length
Co-authored-by: multica-agent <github@multica.ai>
2026-07-14 01:29:47 +08:00
bestonyandmultica-agent 2c2e50ba58 feat(ops): add host filtering to system logs
Co-authored-by: multica-agent <github@multica.ai>
2026-07-14 01:29:46 +08:00
bestonyandmultica-agent 966afd1b4b fix: preserve instrumented client contracts
Co-authored-by: multica-agent <github@multica.ai>
2026-07-14 01:29:30 +08:00
bestonyandmultica-agent 54d228dda5 feat(admin): add opt-in server timing metrics
Co-authored-by: multica-agent <github@multica.ai>
2026-07-14 01:29:30 +08:00
jinfeijie bot ad4bf5c60d feat(grok): 支持 Web SSO 批量导入并转换为 Build OAuth
新增 Grok Web SSO → xAI Device Flow → Grok Build OAuth 导入链路,
支持管理员批量粘贴 SSO key 创建 OAuth 账号。

- 后端:ConvertSSOToBuild、ConvertFromSSO、POST /admin/grok/sso-to-oauth
- 批量:3 worker 并发,失败跳过并汇总 created/failed,worker panic recover
- 无 refresh_token 时写入 expires_at 并强制 auto_pause_on_expired
- 前端:SSO Cookie 导入入口、动态超时、中英文案、部分成功不关弹窗
- 测试:pkg/service/handler/前端超时单测;本地 Docker 真实 SSO e2e 通过
2026-07-14 01:09:07 +08:00
benjamin e9fb5983cd fix(billing): 默认关闭 OpenAI 长上下文计费 2026-07-13 23:32:16 +08:00
hongheshan-svgandClaude Opus 4.8 98027cdded fix(openai): enable HTTP/2 keep-alive PING to evict dead Codex connections
Codex/OpenAI upstream switched from a WebSocket pool to HTTP/2. The
outbound H2 transport set neither ReadIdleTimeout nor ResponseHeaderTimeout
(the OpenAI profile forces ResponseHeaderTimeout=0), so a pooled H2
connection silently killed by a proxy/NAT becomes a "dead connection":
both ends believe it is alive and a request assigned to it hangs until the
OS TCP retransmit timeout (minutes) before the first byte — observed as an
8m37s TTFT with an eventual 200. Occasional (only when a request lands on a
dead pooled conn) and across all groups (shared OpenAI transport); worse on
larger idle pools.

Explicitly configure http2 on the openai_h2 transport and enable active
PING health checks (ReadIdleTimeout=15s, PingTimeout=15s) so dead
connections are detected and evicted at the source, instead of relying on
ResponseHeaderTimeout as an after-the-fact backstop. Scoped to the
openai_h2 path only; Claude/Gemini (default) and h1 modes are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 23:31:18 +08:00
jjaw 831862b924 修复调度全量重建并发合并 2026-07-13 21:59:55 +08:00
jjaw a0778e9a42 修复调度器使用已消费事件计算延迟 2026-07-13 21:56:47 +08:00
gebdalaoli-arch 3c68b2e369 fix: fail over Codex manifest accounts 2026-07-13 20:36:37 +08:00
superman2003 c896cacf6d feat(grok): improve free quota probing and usage 2026-07-13 19:49:56 +08:00
gebdalaoli-arch ed31a52424 fix: stabilize API key Codex manifest refreshes 2026-07-13 19:31:38 +08:00
benjamin f63d168ae0 fix(billing): 检查长上下文开关类型断言 2026-07-13 18:45:06 +08:00
benjamin 3e4d48e010 Merge remote-tracking branch 'upstream/main' into fix/api-double-billing
# Conflicts:
#	frontend/src/components/account/EditAccountModal.vue
2026-07-13 18:06:44 +08:00
zz80900 40ec74b9fc fix(openai): preserve exact messages dispatch mappings
Treat a non-empty messages dispatch model as a caller-approved fallback after account-level mapping, without reclassifying the requested Claude family.\n\nAdd Fable forwarding and endpoint boundary regression coverage.\n\nFixes #4177
2026-07-13 17:57:08 +08:00
benjamin a0ac5e0240 fix(billing): 默认开启 OpenAI 长上下文计费 2026-07-13 17:55:01 +08:00
jjaw 8d5bc448b5 fix(openai): avoid repeated request body scans 2026-07-13 17:43:59 +08:00
Wesley Liddick 7d239d62e8 Merge pull request #4173 from heathermhuang/codex/fix-grok-oauth-media-routing
fix(grok): keep OAuth media on the official API
2026-07-13 17:37:00 +08:00
Wesley Liddick adb5106c1f Merge pull request #4169 from bestony/fix/openai-oauth-model-capability-cooldown
fix(scheduler): cool down Codex plan-gated models per account
2026-07-13 17:19:59 +08:00
Heatherm Huang bb73416733 fix(grok): route OAuth media through official API 2026-07-13 17:08:55 +08:00
github-actions[bot] 55ed0ab0da chore: sync VERSION to 0.1.153 [skip ci] 2026-07-13 08:32:35 +00:00
Bestony 5aeb03018c fix(scheduler): cool down Codex plan-gated models per account
OpenAI OAuth (ChatGPT) accounts deterministically reject plan-gated
models with 400 "The 'X' model is not supported when using Codex with
a ChatGPT account". Account selection had no capability filtering for
this, so the scheduler kept picking the same account for the same model
forever; every attempt burned an upstream call and surfaced to clients
as a retryable 502, sustaining client retry storms.

Treat this 400 like upstream model-not-found: mark the (account, model)
pair via SetModelRateLimit (30min cooldown) so
IsSchedulableForModelWithContext skips the account for that model
during selection, and return true so the in-flight request fails over
to another account through the existing UpstreamFailoverError path.
2026-07-13 16:29:18 +08:00
Wesley Liddick a2bc133747 Merge pull request #4163 from bestony/fix/openai-ws-ingress-lifecycle
fix(openai-ws): bound ingress session lifecycle
2026-07-13 16:07:53 +08:00
Wesley Liddick 540e90ca8b Merge pull request #4162 from heathermhuang/codex/fix-grok-third-party-api
fix(grok): allow third-party API base URLs
2026-07-13 16:07:36 +08:00
Wesley Liddick 4bc7486c3b Merge pull request #4161 from fengshao1227/fix/remove-payment-channels-endpoint
fix(payment): 删除泄露内部 AI 渠道配置的废弃接口
2026-07-13 15:39:32 +08:00
Wesley Liddick 664b7be30f Merge pull request #4055 from iMouseWu/feat/account-plan-type-edit
feat(account): 账号编辑弹窗支持手动覆盖 OpenAI 订阅档位 plan_type(仅 OAuth)
2026-07-13 15:32:57 +08:00
Bestony@Homelab c8cfc93632 fix(openai-ws): bound ingress session lifecycle 2026-07-13 15:32:42 +08:00
Wesley Liddick b4aa3eb023 Merge pull request #4148 from feeeei/fix/retry_count
fix: 池模式同账号重试次数配置对 Anthropic/Gemini/通用转发路径生效
2026-07-13 15:32:40 +08:00
Wesley Liddick 0465540195 Merge pull request #4048 from iMouseWu/fix/datatable-scroll-jank
fix(frontend): 小数据量关闭 DataTable 虚拟化并按行主键缓存行高,消除账号列表滚动抖动
2026-07-13 15:32:08 +08:00
Wesley Liddick b0d0de0547 Merge pull request #4045 from xiaopixiu2019/codex/upstream-gpt56-oauth-test
fix: normalize GPT-5.6 alias in OpenAI OAuth account tests
2026-07-13 15:31:44 +08:00
Heatherm Huang bc5d6ecb46 fix(grok): allow third-party API base URLs 2026-07-13 15:27:57 +08:00
li 03ccb2a08e fix(payment): 删除泄露内部 AI 渠道配置的废弃接口
Fixes #4160
2026-07-13 15:22:39 +08:00
Wesley Liddick 551e2570dd Merge pull request #4118 from badaozhai/feat/grok-video-edit-extension
[codex] support Grok video edits and extensions
2026-07-13 15:04:51 +08:00
shaw 8315defe8e Merge branch 'main' into feat/grok-video-edit-extension 2026-07-13 14:50:18 +08:00
Wesley Liddick fc9b489106 Merge pull request #4125 from adamglin0/feat/apple-container-support
feat(deploy): add Apple container support
2026-07-13 14:43:41 +08:00
Wesley Liddick d774948e09 Merge pull request #4082 from zuelu/agent/fix-codex-additional-tools-chat-bridge
fix(apicompat): 支持 Codex additional_tools 到 Chat Completions 桥接
2026-07-13 14:42:16 +08:00
Wesley Liddick baab0adf7c Merge pull request #4075 from jianjianai/codex/fix-scheduler-cache-invalid-time-upstream
fix(scheduler): skip cache records with unencodable times
2026-07-13 14:42:05 +08:00
Wesley Liddick a60a282473 Merge pull request #4143 from zvensmoluya/fix/alpha-search-frontend-bypass
fix(web): bypass frontend for alpha search
2026-07-13 14:41:55 +08:00
xingzhi 317de9c04b fix: support namespace tools in native responses 2026-07-13 14:38:05 +08:00
Wesley Liddick b8dcae3bcf Merge pull request #4150 from suuuuuu-1/fix/i18n-zh-missing-keys
fix(i18n): 补齐 zh 语言包 overview 和 misc 缺失 key
2026-07-13 14:16:09 +08:00
Wesley Liddick fbc3f42a22 Merge pull request #4138 from fengshao1227/fix/read-tool-args-streaming
fix(apicompat): Read 工具参数实时流式发送,不再依赖 .done 事件
2026-07-13 14:14:08 +08:00
Wesley Liddick 98cc641008 Merge pull request #4134 from yan9651688/fix/usage-window-pagination-local-dates
fix: align usage windows, pagination offsets, and local dates
2026-07-13 14:13:34 +08:00
Wesley Liddick daf0b99dcb Merge pull request #4130 from Drswith/fix/web-static-asset-cache-headers
fix(web): 为嵌入式前端静态资源设置长期 Cache-Control
2026-07-13 14:13:21 +08:00
Wesley Liddick 90bff0ea17 Merge pull request #4050 from bestony/perf/keys-last-used-ip-query
perf(keys): bound latest IP lookup per key
2026-07-13 14:13:11 +08:00
Wesley Liddick d8fa425a27 Merge pull request #4142 from fengshao1227/fix/streaming-stop-reason-and-content-filter
fix(apicompat): 流式 Anthropic max_tokens 截断语义 + content_filter finish_reason
2026-07-13 14:13:00 +08:00
Wesley Liddick 50e5372fed Merge pull request #4139 from heathermhuang/codex/fix-grok-upstream-model-sync
fix(grok): support upstream model sync for API keys
2026-07-13 14:12:17 +08:00
feeeei c7c933776d fix: 池模式同账号重试次数配置对 Anthropic/Gemini/通用转发路径生效
e643fc38 引入 pool_mode_retry_count 账号配置时,只覆盖了 OpenAI 族
handler 的内联重试循环;走 HandleFailoverError 的 Anthropic/
Antigravity/Gemini/通用转发路径仍硬编码同账号重试 3 次,配置不生效。

为 HandleFailoverError 增加 retryLimit 参数,由调用方传入
account.GetPoolModeRetryCount();未配置账号默认仍为 3 次,行为不变。
2026-07-13 12:57:00 +08:00
xingzhi 1d86c1bf81 docs: plan native responses namespace compatibility 2026-07-13 12:13:52 +08:00
li b6427d4ec0 fix(apicompat): 流式 Anthropic max_tokens 映射为 incomplete + content_filter 映射为 finish_reason 2026-07-13 12:08:32 +08:00
Zven b0fa2b352f fix(web): bypass frontend for alpha search 2026-07-13 12:06:51 +08:00
xingzhi 54a8606e2a docs: design native responses namespace compatibility 2026-07-13 12:03:01 +08:00
suuuuuu-1 a7ddca8930 fix(i18n): 补齐 zh 语言包 overview 和 misc 缺失 key
- admin/overview.ts: 添加 12 个缺失的翻译 key
  - newUsersToday, active, ok, err, create
  - userUsageTrend, claudeMaxSimulation 对象
- misc.ts: 添加 allowUserRefund 翻译

改进中文用户体验,确保所有 UI 文本都有对应的中文翻译
2026-07-13 12:01:09 +08:00
li a5d40c9845 fix(apicompat): Read 工具参数实时流式发送,不再依赖 .done 事件
Fixes #4114
2026-07-13 11:47:18 +08:00
Heatherm Huang b0441ca5aa fix(grok): support upstream model sync for API keys 2026-07-13 11:46:52 +08:00
yan9651688 3605a316af Keep usage ranges consistent across API and dashboards
Expose the active weekly subscription window through /v1/usage, calculate offsets with the same normalized page size used by queries, and keep user-facing date ranges on the browser's local calendar date.

Constraint: Preserve existing response fields and avoid new dependencies
Rejected: Keep duplicate inline date formatters | a shared local-date utility prevents the same UTC regression in both views
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Offset and Limit based on the same normalized page size
Tested: go test ./internal/pkg/pagination ./internal/handler; go vet ./internal/pkg/pagination ./internal/handler; frontend 923 tests; pnpm typecheck; pnpm lint:check; pnpm build
Not-tested: Live API request against a deployed subscription
Related: #4121
2026-07-13 11:35:46 +08:00
Drswith 1e97e4cee4 fix(web): set long-lived Cache-Control for embedded static assets
Direct Go/Docker deployments were serving Vite-hashed /assets without
Cache-Control, so browsers re-downloaded the console on every visit.
Align with deploy/Caddyfile while keeping index.html on no-cache.

Closes #4129
2026-07-13 11:14:15 +08:00
benjamin 0d9c140bc2 Merge upstream/main into fix/api-double-billing 2026-07-13 11:10:33 +08:00
github-actions[bot] a1930ea6f2 chore: sync VERSION to 0.1.152 [skip ci] 2026-07-13 02:53:05 +00:00
chenjian 909b96edd2 feat: support Grok video edits and extensions 2026-07-13 10:50:42 +08:00
adamglin0 83c10133d1 feat(deploy): add Apple container support 2026-07-13 10:45:45 +08:00
Wesley Liddick b73d8c3efe Merge pull request #4009 from heathermhuang/codex/fix-recent-grok-issues
fix: expand Grok API, CLI, billing, and setup support
2026-07-13 10:36:11 +08:00
Wesley Liddick 0d318195bf Merge pull request #4116 from Wei-Shaw/feat/alpha-search-per-call-billing
feat(billing): Codex alpha/search 网页搜索按次计费
2026-07-13 10:35:46 +08:00
shaw e5af699d0f test(contract): groups/available 契约夹具补 web_search_price_per_call 字段 2026-07-13 10:21:44 +08:00
Heatherm Huang f73031f436 test(grok): align scheduling reasons with upstream 2026-07-13 10:21:34 +08:00
shaw 64a2a31729 fix(billing): 修复复审发现的三处按次计费问题
- HIGH: GetByKeyForAuth 的 WithGroup 显式 Select 补 web_search_price_per_call 列,
  否则 auth 快照里分组覆盖价恒为 nil,覆盖价/免费配置在实际计费中静默失效
- MEDIUM: 按次搜索倍率改用不含高峰因子的基础倍率(与 image/video 按次不变式
  及分组表单价格预览承诺一致),calculateOpenAIRecordUsageCost 新增独立倍率参数
- LOW: 计费门槛从 <400 收紧为严格 2xx,1xx/3xx 透传不计费
2026-07-13 10:14:53 +08:00
Heatherm Huang 8a22dc7347 fix(grok): diagnose unavailable models by platform 2026-07-13 10:11:34 +08:00
Heatherm Huang aeb34d2003 fix(grok): sanitize composer reasoning parameters 2026-07-13 10:11:33 +08:00
Heatherm Huang c4ff604e93 test(grok): cover OAuth chat permission identity 2026-07-13 10:11:33 +08:00
Heatherm Huang ce3f12bbff test(grok): cover CLI identity at transport boundary 2026-07-13 10:11:33 +08:00
Heatherm Huang f187f08ae3 fix(grok): harden OAuth routing and CLI version guard 2026-07-13 10:11:33 +08:00
Heatherm Huang cbddb57dec fix(grok): display remaining quota capacity 2026-07-13 10:11:33 +08:00
Heatherm Huang 3375b4ed2b fix(grok): route OAuth subscriptions through CLI proxy 2026-07-13 10:11:33 +08:00
Heatherm Huang d9e466ad3a feat(grok): support xAI API key accounts
Allow Grok API-key accounts in Responses forwarding and connection tests, expose creation and edit defaults in the dashboard, and document the supported setup.
2026-07-13 10:11:33 +08:00
Heatherm Huang ad18ee7c4f fix(grok): use responses adapter for OpenCode 2026-07-13 10:09:53 +08:00
Heatherm Huang 038b25c0b1 fix(grok): resolve recent integration issues 2026-07-13 10:09:53 +08:00
benjamin 139f79b85a test(server): 补齐长上下文计费字段契约 2026-07-13 10:02:53 +08:00
shaw 7cbb36f278 feat(billing): Codex alpha/search 网页搜索按次计费
- alpha/search 成功请求(上游 2xx)按次计费落 usage_logs(billing_mode=per_request),
  上游错误透传/failover 不计费;使用 mandatory 池提交,池满同步兜底不丢扣费
- 单价默认 0.01 USD/次(官方 $10/1000 次),分组新增 web_search_price_per_call
  覆盖价(0=免费,负数/留空=默认价),实际扣费叠加分组费率倍数(与 token 口径一致)
- 分组字段全链路:ent schema + 迁移 174 + 端口快照 v15 + admin 创建/更新 + DTO
- 前端分组表单(openai 平台)新增单次价格配置,实时预览应用当前倍率后的单次价格
- 该端点鉴权维持仅 OpenAI 分组(非 OpenAI 分组 404)
2026-07-13 09:54:51 +08:00
benjamin 92dcfb5ebc fix(billing): 按账号控制 OpenAI 长上下文计费 2026-07-13 09:47:19 +08:00
Wesley Liddick 42f3c22830 Merge pull request #4060 from superman2003/feat/grok-prompt-cache-identity
feat(grok): enable Free OAuth prompt caching and quota recovery
2026-07-13 09:20:57 +08:00
shaw 8d51364c3d Merge remote-tracking branch 'origin/main' into feat/grok-prompt-cache-identity
# Conflicts:
#	backend/internal/handler/endpoint.go
#	backend/internal/service/openai_gateway_grok_test.go
2026-07-13 09:12:44 +08:00
Wesley Liddick 33b1d772f7 Merge pull request #4063 from yardbirds0/fix/codex-alpha-search
fix: 转发 Codex alpha/search 独立搜索端点
2026-07-13 08:58:01 +08:00
Wesley Liddick 1c214eaca4 Merge pull request #4059 from visa2/fix/openai-messages-codex-identity
fix(openai): restore Codex identity headers for OAuth Messages
2026-07-13 08:56:21 +08:00
Wesley Liddick 841481e051 Merge pull request #3971 from yardbirds0/codex/fix-remote-compaction-v2
fix: 保留 remote_compaction_v2 原生 Responses 请求链路
2026-07-13 08:54:53 +08:00
Wesley Liddick 2f4478fd32 Merge pull request #4019 from wucm667/fix/compact-keepalive-writer-nil-guard
fix(service): 补齐 compact keepalive writer nil 守卫
2026-07-13 08:54:31 +08:00
Wesley Liddick 877bee84a1 Merge pull request #4072 from jianjianai/codex/fix-upstream-review
修复 tool_search 参数解析与 compact writer 生命周期
2026-07-13 08:54:15 +08:00
Wesley Liddick d734dbdac4 Merge pull request #3997 from lyon-le/feat/openai-fast-policy-user-scope
feat(frontend): Fast/Flex 策略支持搜索选择用户
2026-07-13 08:54:01 +08:00
Wesley Liddick 05865d9b65 Merge pull request #4003 from wucm667/fix/codex-message-item-id-prefix
fix(codex): 剥离续链 message item 的非法 item_* id
2026-07-13 08:53:51 +08:00
gebdalaoli-arch 0dce07ee8b fix: proxy Codex models through API key upstreams 2026-07-13 03:47:39 +08:00
gebdalaoli-arch b6bb74b6fa fix(openai): avoid duplicate Codex image tool injection 2026-07-12 22:54:56 +08:00
superman2003 7050070aa3 fix(grok): route cacheable chat requests via responses 2026-07-12 11:50:56 +08:00
zuelu ff5c216189 fix(apicompat): bridge Codex additional tools 2026-07-12 11:33:09 +08:00
jjaw fe184f8c33 修复调度缓存异常时间阻塞
Refs Wei-Shaw/sub2api#60

Refs Wei-Shaw/sub2api#1608
2026-07-12 05:54:46 +08:00
jjaw 73ffd13430 关联上游 issue #3818 #3887 #3961
相关修复 PR:#3989 #3994
2026-07-12 05:12:20 +08:00
jjaw f57d06d959 合并 compact writer 修复 2026-07-12 04:53:55 +08:00
jjaw 1a8401f5f3 合并 tool_search 参数修复 2026-07-12 04:53:54 +08:00
jjaw 06af8115f7 修复 compact 心跳 writer 生命周期 2026-07-12 04:53:41 +08:00
jjaw 5015b7a1c1 修复 tool_search 参数对象反序列化 2026-07-12 04:53:34 +08:00
visa2 d5b47c2142 fix(openai): restore Codex identity for OAuth Messages 2026-07-11 20:47:52 +08:00
superman2003 1dedb2097d fix(grok): persist quota exhaustion as rate limit 2026-07-11 20:41:32 +08:00
Tian Lee 52071d391b fix(openai): 转发 Codex alpha/search 独立搜索端点 2026-07-11 20:31:33 +08:00
superman2003 0478fd3668 feat(grok): enable free OAuth prompt caching 2026-07-11 19:34:01 +08:00
iMouseWuandClaude Opus 4.8 c56a64fabd feat(account): 账号编辑弹窗支持手动覆盖 OpenAI 订阅档位 plan_type(仅 OAuth)
此前 plan_type(ChatGPT Plus/Pro/Free)只由系统自动写入(OAuth 换码/刷新、
429 同步、导入),UI 无编辑入口。现于 EditAccountModal 为 OpenAI OAuth 非影子
账号新增一个订阅档位下拉,可手动纠正(走既有 PUT /admin/accounts/:id 合并,
plan_type 非敏感字段无需改后端)。

- 仅 OAuth 非影子账号显示/提交:setup-token 无订阅调度语义
  (IsOpenAIChatGPTSubscription 要求 oauth),影子账号凭据由母账号管理
- 下拉:清空 + Plus/Pro/Free;别名(chatgptpro→Pro)与自定义值(team 等)
  友好显示并保留 canonical 值,避免编辑丢失或写脏
- readPlanType 仅接受字符串,挡住脏数据;清空则删除该键(恢复自动识别)
- 抽 buildPlanTypeOptions/applyPlanType/readPlanType/planTypeDisplayLabel 为
  纯函数入 credentialsBuilder.ts,并补 12 个单测(别名去重、保留其余凭据键、
  set/delete 语义)
- 文案(zh/en)注明:令牌临期刷新或 429 会用真实档位自动覆盖手改值

经三方评审会(Claude + Codex + 作者)一轮交叉评审达成共识后落地。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 18:31:45 +08:00
Bestony 1c02158c2a perf(migrations): index latest API key IP lookups 2026-07-11 17:32:06 +08:00
Bestony 80b7a8d4cb perf(keys): bound latest IP lookup per key 2026-07-11 17:31:30 +08:00
iMouseWuandClaude Opus 4.8 94a22b62f7 fix(frontend): 小数据量关闭 DataTable 虚拟化并按行主键缓存行高,消除账号列表滚动抖动
账号列表(可变行高)在 @tanstack/vue-virtual 始终开启虚拟化下,估算行高(72)
远小于真实行高(~157),滚动时每行首次测量触发滚动补偿,表现为"一跳一跳"。

- DataTable 新增 virtualizeThreshold(默认 100):行数不超过阈值时全量渲染,
  彻底绕开虚拟器的估算/测量/补偿链路;超过才虚拟化,保留大列表性能
- DataTable 补 getItemKey(与模板 :key 一致的行主键):itemSizeCache 由 index 键
  改为行主键,排序/筛选/跨阈值来回都复用正确的已测行高,连带修复 >阈值 档
  排序即抖的既存问题
- 虚拟/全量两模式统一为 renderRows,共用同一套行与单元格;measure ref 仅虚拟模式绑定
- useSwipeSelect:全量渲染(虚拟窗口为空)时改用真实 DOM 行按坐标二分定位,
  修复关闭虚拟化后滑动多选点错行的隐患;定位逻辑提取为可测的导出函数
- AccountsView:estimate-row-height 72→156 对齐真实行高;virtualize-threshold=50,
  常用分页档全量渲染无抖动,pageSize=100 仍虚拟化以约束每行 AccountUsageCell 的首屏请求扇出
- 新增/强化测试:DataTable 虚拟化决策与 getItemKey、useSwipeSelect DOM 兜底定位

经三方评审会(Claude + Codex + 作者)两轮讨论达成共识后落地。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:13:19 +08:00
xiaopixiu2019 51de58b37f fix: normalize GPT-5.6 alias in OpenAI OAuth account tests 2026-07-11 15:39:32 +08:00
Tian Lee 84bb7d0709 fix: 保留 remote_compaction_v2 原生 Responses 链路 2026-07-11 12:51:53 +08:00
wucm667 6e2bb31281 fix(service): guard compact keepalive writer delegates 2026-07-11 08:52:46 +08:00
wucm667andClaude Opus 4.8 4d4ba64bf7 fix(codex): 剥离续链 message item 的非法 item_* id
OpenAI OAuth 转发续链请求时,type=message 的 item 的 id 被客户端以
item_* 形式回放,但上游要求以 msg 开头,返回 400
"Expected an ID that begins with 'msg'",sub2api 随后向客户端返回 502。
客户端自动重试会不断重放同一份坏上下文,导致连续失败。

filterCodexInputWithOptions 在 PreserveReferences=true 路径下为
type=message 增加与 #3785 (fd64d07e6) 平行的 id 前缀检查:非 msg 开头
即删除。合法的 msg* id 原样保留,不改写 item_* 为 msg_*,因为改写出的
id 未必对应真实上游对象。

原 TestFilterCodexInput_NonToolCallItemKeepsID 以 message + item_msg_001
断言"保留 id",该行为已被上游拒绝,改用 web_search_call 覆盖同一意图。

Fixes #3981

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:32:34 +08:00
Lyonle 0464856c4a feat(frontend): Fast/Flex 策略支持搜索选择用户
将手工 User ID 输入替换为带防抖的邮箱模糊搜索和多选标签,同时继续保存 user_ids。

回显已有用户邮箱并保留无法解析的历史 ID,补齐中英文文案路径及组件回归测试。
2026-07-10 22:27:15 +08:00
Wesley Liddick e316ebf528 Merge pull request #3989 from xlplbo/fix/codex-mcp-tools-bridge
fix(apicompat): 补齐 Codex 0.14x 工具链经 chat 回退桥的转换(custom/tool_search/namespace)
2026-07-10 22:09:25 +08:00
Wesley Liddick 07fac34713 Merge pull request #3991 from fengshao1227/fix/anthropic-usage-cache-creation-tokens
fix(apicompat): Responses↔Anthropic 转换补齐 cache_creation_input_tokens
2026-07-10 21:49:29 +08:00
Wesley Liddick 151b9265fc Merge pull request #3994 from fengshao1227/fix/compact-keepalive-nil-pointer
fix(handler): opsCaptureWriter 释放后 nil pointer panic
2026-07-10 21:49:07 +08:00
github-actions[bot] 6c588bb950 chore: sync VERSION to 0.1.151 [skip ci] 2026-07-10 13:24:13 +00:00
xlplboandClaude Fable 5 90e9d03dec fix(apicompat): 强制选择 tool_search 的 tool_choice 降级为指向代理的 function 选择
tool_search 工具未被丢弃而是降级为同名 function 代理,此前 tool_choice 过滤
把它连同其他服务端工具类型一起静默丢弃,强制工具搜索被退化为自动选择,
违反调用方明确指定的工具选择语义。改为映射到指向代理的 function 选择;
未声明 type=tool_search 时无可指向的代理,维持丢弃。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:14:33 +08:00
xlplboandClaude Fable 5 e2b68d1f90 fix(apicompat): tool_choice 仅转发实际存在于转换结果中的工具
此前只检查转换后是否还剩任意工具,不校验 tool_choice 指向的工具是否幸存:
强制选择被丢弃的服务端工具(如 web_search)或指向不存在名字时,选择项被
原样转发,chat 上游因 tool_choice 指向未声明工具而 400。改为具名选择项仅
在目标存在于转换后工具集时转发,服务端工具类选择项随工具本身丢弃;
"auto" 等字符串形式保持原样转发。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:06:45 +08:00
xlplboandClaude Fable 5 a2cdaa6419 fix(apicompat): 内置 tool_search 与同名工具撞名时显式拒绝请求
tool_search 服务端工具降级后的代理 function 不能改名(codex 的模型侧按
tool_search 这个名字调用),与客户端声明的同名 function/custom 工具无法
区分:去程产生重复声明,回程会把普通工具的调用劫持还原成 tool_search_call。
与 namespace 摊平撞名的处理一致,在请求转换阶段显式报错(网关 400);
重复声明 type=tool_search 去重后只产出一个代理,不拒绝。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:06:26 +08:00
li bc3cb29027 fix(handler): 补齐 opsCaptureWriter 全部委托方法的 nil 守卫 2026-07-10 21:03:40 +08:00
li 89a551b964 fix(handler): opsCaptureWriter 释放后访问 nil panic
Fixes #3961
2026-07-10 20:56:31 +08:00
li 83f169e4fa fix(apicompat): 补齐 Responses→Anthropic 流式路径的 cache_creation_input_tokens 2026-07-10 20:49:44 +08:00
xlplbo eb4d005031 Merge remote-tracking branch 'upstream/main' 2026-07-10 20:46:38 +08:00
li 0d28f7f90d fix(apicompat): Responses↔Anthropic 转换补齐 cache_creation_input_tokens
Fixes #3935
2026-07-10 20:40:45 +08:00
Wesley Liddick deff3123de Merge pull request #3984 from Wei-Shaw/fix/issue-3901-codex-identity-pairing
fix(openai): originator 与最终 User-Agent 首段配套收口,修复 codex 上游 404(#3901)
2026-07-10 19:36:46 +08:00
shaw 8a51119e39 fix(openai): pair originator with final User-Agent for codex upstream
上游 /backend-api/codex 的 404 gating 是配套校验:originator 必须与
User-Agent 首段(首个 '/' 前的 client 名)配套且为官方 codex 身份,
错配(如 originator=codex_cli_rs + UA=codex-tui/...)一律 404;version
头若携带须 >= 0.144.0。

修复:
- pkg/openai 新增 PairCodexClientIdentity:由最终出站 UA 推导配套
  originator(含 CODEX_INTERNAL_ORIGINATOR_OVERRIDE 尾部括号组身份恢复、
  精确集合大小写归一、长度/字符集校验)
- service 新增 enforceCodexIdentityHeaders 终态收口:originator 按最终
  UA 重配,推导不出官方身份整体回退默认 Codex CLI 身份;version 低于
  0.144.0 提升为内置版本;originator 缺失时 no-op(保护 messages bridge)
- 接入非透传/透传/WS 三条转发路径(透传与 WS 删除会把 codex-tui 等官方
  UA 强改为 codex_cli_rs 造成错配的旧兜底)、用量探针(指纹缓存 UA 与硬
  编码 originator 错配)、账号 responses/图像测试(opencode 错配)
- messages 兼容桥构建前显式打 bridge 标记,防止映射到非 gpt-5/codex
  模型时收口误改其刻意最小化的请求形态

Fixes #3901
2026-07-10 19:11:26 +08:00
xlplboandClaude Fable 5 f1082bb78f fix(apicompat): namespace 摊平名撞名时显式拒绝请求
摊平名与顶层 function/custom 工具撞名、或跨 namespace 摊平出同名时,
chat 上游无法按 namespace 区分调用归属:此前重复声明照发上游、回程
固定命中其中一条映射,调用可能被还原到错误工具。这类请求在原生
Responses 上游合法,歧义由摊平转换制造且不可消除,改为在请求转换
阶段直接报错(网关返回 400 invalid_request_error 并点名冲突双方);
同一 (namespace, 子工具) 的重复声明去重后不拒绝。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 17:38:48 +08:00
xlplboandClaude Fable 5 7942338328 fix(apicompat): 回程还原 namespace 子工具调用,修复 Codex MCP 工具 unsupported call
Codex 0.14x 将 MCP 工具声明为 namespace 工具,chat 桥去程摊平为
"<namespace>__<name>" function 工具后,回程仅原样回传平铺名的
function_call 项;codex 按 namespace+name 路由查不到该名字,所有
MCP 工具调用被判为 unsupported call。

- NamespaceToolNames 构建摊平名 →(namespace, 子工具名)反查表
  (摊平名超长带截断哈希,无法按字符串切分还原)
- 非流式/流式回程命中映射时还原为裸子工具名 + namespace 字段,
  ResponsesOutput 新增 Namespace 字段并同步 wire 层白名单
- 回退桥入口将映射与 CustomTools/ToolSearchDeclared 一并穿入

已在测试机经 codex exec + MCP server 端到端验证。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 17:38:17 +08:00
Wesley Liddick 5260a42a0b Merge pull request #3953 from lyon-le/feat/openai-fast-policy-user-scope
feat(openai): 支持用户级 Fast/Flex 策略
2026-07-10 17:31:55 +08:00
Wesley Liddick 815516d8d3 Merge pull request #3969 from jinfeijie/fix/grok-responses-reasoning-effort
fix(grok): preserve compatible reasoning effort
2026-07-10 17:31:41 +08:00
jinfeijie bot 5a0dd510e5 fix(grok): remove obsolete helper 2026-07-10 17:11:04 +08:00
jinfeijie bot 0fa1eb85ec fix(grok): preserve compatible reasoning effort 2026-07-10 16:58:17 +08:00
Wesley Liddick 6dd3274aaf Merge pull request #3948 from superman2003/fix/gpt56-billing-usage-integrity
fix(openai): correct GPT-5.6 billing and usage gaps
2026-07-10 15:45:18 +08:00
Wesley Liddick d0f6f27d5f Merge pull request #3941 from Tassoi/codex/fix-image-gen-namespace-strip
fix(image): strip Codex image_gen namespace declarations
2026-07-10 15:45:05 +08:00
Wesley Liddick 6a2063b374 Merge pull request #3883 from ShuminFu/fix/setup-token-auto-refresh
fix: 后台自动刷新纳入 setup-token 账号(修复 8h 后 401)
2026-07-10 15:44:54 +08:00
Lyonle f2966530c5 feat(openai): 支持用户级 Fast/Flex 策略 2026-07-10 15:16:09 +08:00
superman2003 de28eba3c8 fix(openai): harden GPT-5.6 billing and usage 2026-07-10 15:13:11 +08:00
Tassoi d3a1835ed7 fix(image): strip Codex image_gen namespace declarations 2026-07-10 06:37:06 +00:00
Shumin a495d5e302 test: 更新断言以覆盖 setup-token 纳入后台刷新
- ListOAuthRefreshCandidates SQL 断言由 type = 'oauth' 改为
  type IN ('oauth', 'setup-token')。
- ClaudeTokenRefresher.CanRefresh 增加 anthropic setup-token → true 用例。
2026-07-10 02:27:33 -04:00
github-actions[bot] 9a2f11b4e2 chore: sync VERSION to 0.1.150 [skip ci] 2026-07-10 06:27:11 +00:00
shaw 0dec1ad292 fix(service): 消除 isOpenAIGPT56Model 重复声明,统一到 openai_model_alias.go
PR #3898 与 #3909 各自新增了同名函数(billing 精确匹配版 / gateway
前缀匹配版),合并后 internal/service 编译失败。保留前缀匹配语义作为
唯一实现:对 normalizeKnownOpenAICodexModel 的输出(gpt-5.6-* 精确
基名)canonicalize 幂等且精确分支命中,计费行为不变;同时保住
gpt-5.6-*-max 等后缀变体的识别。
2026-07-10 14:09:07 +08:00
Wesley Liddick 8b96acde97 Merge pull request #3898 from Arron196/feat/gpt-5.6-cache-billing-stats
feat(openai): 支持 GPT-5.6 系列缓存写入计费与统计
2026-07-10 13:57:29 +08:00
Wesley Liddick 5c9748cef9 Merge pull request #3922 from fengshao1227/fix/user-breakdown-request-type-filter
fix(admin): GetUserBreakdown request_type 筛选使用字符串解析
2026-07-10 13:56:40 +08:00
Wesley Liddick 312ab1f0bd Merge pull request #3926 from fengshao1227/fix/ws-passthrough-reasoning-effort-candidates
fix(usage): WS passthrough effort 提取补入映射后模型候选
2026-07-10 13:46:25 +08:00
li ea9f40b63f fix(frontend): UserBreakdownParams.request_type 类型从 number 改为 UsageRequestType 2026-07-10 12:38:21 +08:00
li b9b013a088 fix(usage): WS passthrough effort 提取补入映射后模型候选
Fixes #3924
2026-07-10 11:56:47 +08:00
li dda8f78733 fix(admin): GetUserBreakdown 使用 ParseUsageRequestType 解析 request_type
Fixes #3920
2026-07-10 11:43:44 +08:00
Wesley Liddick ddb1a210ce Merge pull request #3912 from superman2003/fix/audit-concurrency-and-recovery
fix: harden billing concurrency and payment recovery
2026-07-10 11:38:45 +08:00
Wesley Liddick 301c99a26c Merge pull request #3916 from Wei-Shaw/fix/reasoning-effort-model-candidates
fix: effort 提取改用模型候选列表,修复后缀模型用量元数据丢失
2026-07-10 11:16:35 +08:00
shaw c3ae5fc3c5 fix(usage): effort 提取改用模型候选列表,修复后缀模型元数据丢失
#3909 将 effort 提取的模型参数改为 firstNonEmpty(upstreamModel, ...),
而 OAuth 的 normalizeCodexModel 会剥掉模型 effort 后缀
(gpt-5.4-xhigh -> gpt-5.4),导致后缀式模型且 body 无显式
reasoning 字段的请求在 usage 元数据中丢失 effort。

extractOpenAIReasoningEffortFromBody/extractOpenAIReasoningEffort
改为接受模型候选变参:显式 effort 的 max 保留判定沿用第一个非空
候选(映射后模型),后缀推导回退依次尝试每个候选(原始模型名兜底)。
纯元数据修复,不改变任何转发行为。
2026-07-10 11:09:06 +08:00
Wesley Liddick cddcf49044 Merge pull request #3884 from CHOS1N11111/codex/add-parallel-tool-calls-compat
Add parallel_tool_calls compatibility mapping
2026-07-10 11:01:03 +08:00
Wesley Liddick 9389503c56 Merge pull request #3913 from fengshao1227/fix/en-locale-missing-keys
fix(i18n): 补齐 en 语言包 overview 和 resources 缺失 key
2026-07-10 10:59:49 +08:00
Wesley Liddick 72ef0b3884 Merge pull request #3909 from githubbzxs/fix/gpt56-max-compact
fix: support GPT-5.6 max reasoning effort
2026-07-10 10:57:35 +08:00
superman2003 fc66a30ffc fix: harden billing concurrency and payment recovery 2026-07-10 10:56:49 +08:00
li e984b4e2e1 fix(i18n): 补齐 en 语言包 overview 和 resources 缺失 key
Fixes #3909
2026-07-10 10:45:44 +08:00
Wesley Liddick 066a85949a Merge pull request #3905 from fengshao1227/fix/codex-client-version-bump
fix(openai): 升级 Codex 客户端版本至 0.144.1,修复 gpt-5.6-luna 404
2026-07-10 10:43:51 +08:00
Wesley Liddick 3099362074 Merge pull request #3906 from Wei-Shaw/fix/compact-sse-raw-output-item-preservation
fix(compact): SSE→JSON 保留 raw output_item.done 并为 unary 等待补下游心跳(修复 #3887)
2026-07-10 10:41:02 +08:00
psyche 80b3d4c1f8 fix: 兼容 GPT-5.6 max 推理强度 2026-07-10 10:39:16 +08:00
shaw 000f6dc655 fix(compact): 终态 output 非空但缺 compaction 时从事件流补入(146 等价性收尾)
146 纯流式透传下 Codex 只从 raw output_item.done 收集 item,无论终态
output 形态如何都能拿到 compaction;SSE→JSON 提取链路此前仅在终态 output
为空时修补。补齐最后一处不等价:compact 请求终态 output 非空但缺
compaction、事件流中存在(done 优先 added 兜底)时以 raw JSON 追加。
门控:仅 compact 路径 + 仅缺失时触发,已含 compaction 或非 compact
请求逐字节不变。

Refs #3887 #3777
2026-07-10 10:25:09 +08:00
benjamin 5c15d32ff7 test(repository): stabilize expired lock reconciliation 2026-07-10 10:18:50 +08:00
shaw ae9a01d852 fix(compact): 二轮审计加固——心跳字节不得污染 failover 判定与并发写安全
对首轮修复的对抗式审计发现并修复以下问题:

1. failover 判定污染(真实回归风险):handler 以 "Forward 前后
   c.Writer.Size() 是否变化" 判定响应是否已写出并据此放弃换号。心跳注释
   字节会使该判定恒真,compact 请求一旦在上游等待期间发过心跳,上游
   429/5xx 将不再 failover。新增
   OpenAICompactKeepaliveAdjustedWrittenSize(扣除心跳字节、互斥锁下
   一致读取、仅心跳字节归一化为未写哨兵 -1),快照、failover 比较与
   openAIForwardErrorAlreadyCommunicated 三处判定统一改用该口径;无心跳
   请求完全等价于原 c.Writer.Size()。

2. 并发写竞争:心跳 goroutine 与未被显式拦截的写回路径(Forward 内部
   本地拒绝等)存在 ResponseWriter 数据竞争。StartOpenAICompactSSE-
   Keepalive 现将 c.Writer 替换为 openAICompactKeepaliveWriter:写侧
   方法(Header/Write/WriteString/WriteHeader/WriteHeaderNow/Flush)
   先在互斥锁下停拍,读侧(Status/Size/Written)仅加锁不停拍——任何
   请求侧响应构造与心跳从构造上互斥,热路径状态读取不误杀心跳。

3. 语义拦截补齐:rejectIfCyberSessionBlocked(在用户槽位长等待之后
   执行的直接 c.JSON)与 writeOpenAIFastPolicyBlockedResponse 在心跳
   提交后降级为 response.failed 终止事件;未提交时先停拍再写 JSON,
   状态码语义不变。失败事件 errType 参数化(permission_error 等)。

4. reconstruct 混合形态:done 事件存在但 compaction 只在
   output_item.added 中时也要补入;done 已含 compaction 时跳过 added,
   避免无 id 可去重时收集两份(Codex 要求恰好一个)。

5. 观测性:logOpenAIRemoteCompactOutcome 对心跳提交后的失败(wire 200)
   以 GetOpsStreamError 纠正 outcome,不再误记 succeeded。

新增 5 个测试:failover 口径不变式、包装器停拍语义(-race)、fast
policy 提交前后两态、混合 done/added 形态(含去重)。

Refs #3887 #3777
2026-07-10 10:18:16 +08:00
benjamin 0a5f34a2ef fix(openai): recognize Windows websocket resets 2026-07-10 10:11:21 +08:00
benjamin 062af81fb5 fix(openai): preserve explicit GPT-5.6 cache write prices 2026-07-10 10:00:47 +08:00
shaw 2cffe1cf5f fix(compact): SSE→JSON 保留 raw output_item.done 并为 unary 等待补下游心跳(修复 #3887)
#3887 报告 v0.1.149(已含 #3880 桥接修复)remote compact 仍失败且持续
计费。核实为 #3880 明示的两个遗留:

1. 上游对 unary compact 返回 SSE 且 compaction item 只出现在 raw
   response.output_item.done、终态 completed.response.output 为空
   (#3777 实录形态)时,reconstructResponseOutputFromSSE 只累加
   text/function_call/reasoning 三类 delta,compaction item 被丢弃,
   桥接合成 0 个 output_item.done,Codex 报 "expected exactly one
   compaction output item, got 0" 后盲目重试,每次重试重新消耗上游
   compact 配额。
2. 桥接为全缓冲写回:上游 unary 完成前(大上下文可达数分钟,且上游
   处理期间不发送任何字节)下游连响应头都收不到,反向代理
   (Nginx/Cloudflare Tunnel)空闲超时掐断连接同样触发盲重连
   (同类问题见 #2243/#2976)。

修复:

- reconstructResponseOutputFromSSE 优先以 raw JSON 逐字节收集
  output_item.done item(协议上的最终完整形态),不经窄结构体,
  encrypted_content/summary/opaque 等 compact 专属字段全部保留;
  无 done 事件时退回收集 output_item.added 中的 compaction 类 item;
  两者皆无才回到原 delta 重建。path-based v1 JSON 写回同样受益。
- 新增 openAICompactSSEKeepalive:body-signal 客户端流式 compact 在
  上游等待期间按 gateway.stream_keepalive_interval 向下游写 SSE 注释
  行心跳(eventsource 解析层直接忽略)。首拍延迟一个间隔,快速失败
  仍走 JSON+状态码;首拍后状态码固化为 200,桥接/错误链路
  (writeOpenAICompactSSEBridge、errorResponse、
  handleStreamingAwareError、ensureForwardErrorResponse、
  writeOpenAINonStreamingProtocolError)统一降级为 response.failed
  终止事件并标记 ops 流内错误。
- API-key 账号的 compact 上游请求也强制 accept: application/json
  (#3777 期望行为 4;透传白名单原会放行客户端的 text/event-stream)。

测试:#3777 实录形态经 handleSSEToJSON / 透传 / path-based 三条链路
的修补断言;raw-done 优先不与 delta 重复;added 回退门控;心跳提交、
提交后 2xx 续写、提交后失败降级、未提交行为不变;-race 通过。

Fixes #3887
Refs #3777 #3875 #3880
2026-07-10 09:56:56 +08:00
li 657c4f97d9 fix(openai): 升级 Codex 客户端版本至 0.144.1,修复 gpt-5.6-luna 404
Fixes #3895
2026-07-10 09:56:33 +08:00
benjamin 383f61d0e9 fix(openai): align GPT-5.6 billing with official pricing 2026-07-10 09:42:31 +08:00
xlplboandClaude Fable 5 18e26c127c Merge remote-tracking branch 'upstream/main'
解决 backend/internal/service/openai_gateway_responses_chat_fallback.go 冲突:
将本地 tool_search/custom 工具改动(customTools/toolSearch 穿参与流状态标记)
移植到上游重构后的 CC 公共管线;同步适配上游新增调用点
openai_gateway_messages_chat_fallback.go 的 ChatCompletionsResponseToResponses 签名。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8zMzeFAumSddraEw1FUeG
2026-07-10 09:27:48 +08:00
benjamin 4a2b10c94e feat(openai): support GPT-5.6 cache write billing 2026-07-10 09:08:58 +08:00
Shumin 99da308196 fix: 后台自动刷新纳入 setup-token 账号
setup-token 的 access_token 同为 8h 短期令牌(expires_in=28800),
此前被后台刷新服务排除,到期后请求返回 401 authentication_error。
放开 CanRefresh 与候选查询的 type='oauth' 限制,与手动刷新入口
(account.IsOAuth()) 保持一致;实际刷新仍由 NeedsRefresh 基于
expires_at 门控并在分布式锁下执行,不会过度刷新。
2026-07-09 11:05:05 -04:00
github-actions[bot] 12d811bd76 chore: sync VERSION to 0.1.149 [skip ci] 2026-07-09 14:57:53 +00:00
CHOS1N ad8afc8a2e Add parallel_tool_calls compatibility mapping
Preserve Chat Completions parallel_tool_calls when converting requests to the Responses API, and map Responses parallel_tool_calls back when falling back to Chat Completions upstreams.

Cover both true and explicit false values so clients can disable parallel tool calls without the field being dropped by omitempty.
2026-07-09 22:52:35 +08:00
Wesley Liddick dd1a116f48 Merge pull request #3881 from Wei-Shaw/feat/version-rollback
feat: 版本徽章新增近3个历史版本在线回退与手动回退指引
2026-07-09 22:39:41 +08:00
Wesley Liddick 2e61914c48 Merge pull request #3880 from Wei-Shaw/fix/compact-body-signal-sse-bridge
fix(compact): body-signal 客户端流式请求的响应合成回 SSE(修复 #3875)
2026-07-09 22:39:31 +08:00
shaw 92b283d9eb fix: 修复 SSE 桥接 appendEvent 中 errcheck 告警 2026-07-09 22:34:13 +08:00
shaw 1c2e6503c6 feat: 版本徽章新增近3个历史版本在线回退与手动回退指引
后端:
- UpdateService 新增 ListRollbackVersions/RollbackToVersion,回退目标强制
  限定为比当前版本更旧的近3个正式版本(排除当前/更新/预发布/草稿),
  越界返回 400 ROLLBACK_VERSION_NOT_ALLOWED;下载复用既有 HTTPS 域名
  白名单 + checksum 校验 + 原子换二进制管线
- GitHubReleaseClient 新增 FetchRecentReleases(releases 列表 API)
- 新增 GET /admin/system/rollback-versions;POST /admin/system/rollback
  支持可选 {"version"} body,无 body 保持原 .backup 本地回退行为不变
- 端点均在 /admin 组 adminAuth(IsAdmin+TokenVersion)保护内

前端:
- VersionBadge「已是最新版本」状态下新增版本回退面板:radio 卡片选择
  近3个版本,手动回退方式按部署形态 tab 切换(脚本部署 curl 命令 /
  Docker 镜像 tag 指引),支持一键复制;release 构建提供一键回退并
  复用重启流程;源码构建仅提示不支持在线回退
- 下拉根元素重置 whitespace-normal,修复 sidebar-brand 的
  white-space:nowrap 继承导致的长文本溢出裁切

测试:服务层过滤/排序/上限/非法目标拒绝、handler 双模式与鉴权错误映射、
GitHub client 列表拉取、前端 API 请求体行为共 16 个新增用例
2026-07-09 22:30:04 +08:00
shaw 16c0613b21 fix(compact): body-signal 客户端流式请求的响应合成回 SSE
v0.1.147 的 body-signal 提升(#3804)把 Codex remote compact v2 的流式
/responses 请求改写为上游 unary /responses/compact(JSON),但把 JSON
文档原样写回给了按 Responses SSE 协议消费的客户端。Codex 只从
response.output_item.done 收集 item 且必须收到 response.completed,
收到 JSON 后报 "stream disconnected before completion: stream closed
before response.completed" 无限重连,每次重连再白烧一次上游 compact
配额(#3875,v0.1.146 无提升逻辑故正常)。

修复保留提升的全部收益(requireCompact 调度过滤、compact 模型映射、
白名单归一化、上游 unary JSON),补上协议转换缺失的响应半程:

- handler 在 body-signal 提升时记录原始 body 的 stream:true 意图
  (MarkOpenAICompactClientStream);path-based 请求不标记,Codex v1
  unary 协议与链式 sub2api 的 JSON 写回行为保持不变。
- 四个非流式写回点(handleNonStreamingResponse / handleSSEToJSON /
  handleNonStreamingResponsePassthrough / handlePassthroughSSEToJSON)
  对已标记的 2xx JSON 响应经 writeOpenAICompactSSEBridge 合成最小
  Responses SSE:每个 output[] item 一条 response.output_item.done
  (原始字段逐字节保留),最后一条 response.completed 携带完整
  response 对象。
- 按 codex-rs 解析器硬约束兜底:先 json.Compact 防 pretty JSON 换行
  破坏 SSE 帧;response.id 缺失时注入 resp_*(必填 string);usage
  缺 input_tokens/output_tokens/total_tokens 整数字段时整体删除,
  避免整条 completed 事件解析失败。
- 非 2xx 一律保持 JSON 原样,Codex 依赖 HTTP 状态码走重试链路。

新增 9 个 service 测试 + 4 个 handler 测试覆盖合成形态、id/usage 兜底、
path-based 不受影响、主链路/透传链路/上游 SSE 提取再合成三条端到端。

Fixes #3875
Refs #3777
2026-07-09 21:40:51 +08:00
shaw 1a3cc2a787 refactor: 重新设计用量页布局并新增延迟健康列
- /admin/usage 明细区收进单卡片:三个下划线 tab(用量明细/错误请求/用户排行) + 自适应筛选栏 + 内容
- 用户 Token 排行收编为第三个 tab:去掉独立邮箱搜索,懒挂载,点击行下钻回用量明细并回显用户
- 首Token/耗时合并为延迟列(首字/总耗时):阈值 10s/30s/60s 与 1min/3min/5min 四档配色,色条上下两段短渐变过渡(from-40% to-60%),管理端与用户端 /usage 共用
- 时长格式 ≥1min 简化为 Xm Ys、≥1h 为 Xh Ym
- UsageFilters/UsageTable/OpsErrorLogTable 增加 flat 嵌入模式(默认关闭不影响其他使用方)
2026-07-09 21:09:40 +08:00
Wesley Liddick e2c731abe4 Merge pull request #3873 from Wei-Shaw/fix/post-merge-audit-followups
fix: 落实 #3867-#3870 合并审计的全部跟进项
2026-07-09 20:13:21 +08:00
shaw 7918b1a9c5 fix: 落实 #3867-#3870 合并审计的全部跟进项
透传规则(跟进 #3868/#3870,refs #3857):
- CC/Messages 4 条协议转换路径改用共享 helper,走语义状态推断 +
  body 归一化,使按错误码配置的透传规则也能命中(原先传 0 恒不命中)
- helper 增加 platform 参数:本服务同时承载 openai/grok 平台账号,
  规则须按 account.Platform 匹配,消除硬编码平台错配
- /v1/responses 两条路径命中透传规则时补记 ops 上游错误事件,
  对齐 CC/Messages 与 antigravity 先例,消除监控盲区

用户角色管理(跟进 #3869):
- 补齐 EN 语言包缺失的 admin.users.form.roleLabel
- 新增"最后一个管理员不可降级"守卫,覆盖跨管理员互降致零 admin 锁死
- 角色变更/创建管理员落审计日志(含操作者 actor_admin_id)
2026-07-09 20:06:09 +08:00
Wesley Liddick 10102807e2 Merge pull request #3869 from Birditch/feat/admin-user-role-and-token-ranking
feat(admin): 用户角色管理 + 用户 Token 排行
2026-07-09 19:50:01 +08:00
Wesley Liddick 751cf5eea3 Merge pull request #3870 from fengshao1227/fix/response-failed-passthrough-rules
fix(gateway): CC/Messages 路径 SSE response.failed 应用错误透传规则
2026-07-09 19:49:32 +08:00
Wesley Liddick a8505877ff Merge pull request #3868 from wucm667/fix/openai-sse-response-failed-error-passthrough
fix: OpenAI SSE response.failed 应用错误透传规则
2026-07-09 19:49:04 +08:00
Wesley Liddick f3380dbd62 Merge pull request #3867 from heathermhuang/codex/postmerge-grok-issues-3848-3861
fix: Grok OAuth, image, usage, and quota handling
2026-07-09 19:48:32 +08:00
shaw feef0bc0b1 chore: update sponsors 2026-07-09 19:08:00 +08:00
github-actions[bot] 0ee51b45f8 chore: sync VERSION to 0.1.147 [skip ci] 2026-07-09 10:10:28 +00:00
li 8f97953e51 fix(gateway): SSE response.failed 应用错误透传规则,不再硬编码 502
Fixes #3857
2026-07-09 18:09:37 +08:00
BirditchandClaude Opus 4.8 b062b36646 feat(admin): 用量记录页新增"用户 Token 排行"面板
- 在 /admin/usage 新增按用户聚合的 Token 排行表(输入/输出/缓存/总 Token、请求数、费用)
- 支持按列排序、邮箱搜索、Top N (20/50/100/200),点击行下钻到该用户
- 复用现有筛选(用户/时间/模型/分组等)与既有 KPI 卡片、逐条日志
- 后端对 /admin/dashboard/user-breakdown 做加法扩展(向后兼容):
  - UserBreakdownItem 增加 input_tokens/output_tokens/cache_tokens
  - 新增 sort_by(白名单排序,防注入,缺省仍按 actual_cost)
- 新增 sort_by 转发单元测试;更新 UsageView.spec 稳定桩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 06:04:22 -04:00
BirditchandClaude Opus 4.8 64fdc11ec4 feat(admin): 用户创建/编辑支持选择与修改角色 (user/admin)
- 创建用户时可指定角色,缺省仍为 user,不再硬编码为普通用户
- 编辑用户时可在 user/admin 之间切换角色
- 后端对角色做 admin/user 合法性校验
- 防锁死保护:管理员不能把自己降级为普通用户(与既有"不能禁用/删除 admin"保护一致;降级其他管理员仍允许)
- 前端创建/编辑弹窗新增角色下拉,复用既有 i18n 文案
- 新增角色创建/更新/非法值/防降级单元测试

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 06:04:06 -04:00
wucm667 1da3501af5 fix: apply error passthrough to OpenAI response.failed streams 2026-07-09 17:59:38 +08:00
Heatherm Huang 1a0a6ea9a8 fix: stabilize Grok quota probe model 2026-07-09 17:58:31 +08:00
Heatherm Huang b480545c19 fix: improve Grok OAuth, image, and usage flows 2026-07-09 17:54:08 +08:00
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
xlplbo 27e29f0562 feat(apicompat): 添加 tool_search 支持,增强工具调用处理逻辑 2026-07-07 19:19:35 +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
xlplbo 75fb3c41c2 fix(apicompat): responses→chat 桥支持 custom 工具,修复 Codex exec 丢失
Codex 0.14x 的核心执行工具 exec 是 type=custom(freeform)工具,
responsesToolsToChatTools 丢弃所有非 function 工具,导致模型工具列表中
没有 exec、无法执行任何命令;SSE 序列化层 responsesItemWire 也缺少
custom_tool_call 分支,即使产出该类型 item,call_id/name/input 也会被
白名单剥离。

- custom 工具降级为单一 input:string 参数的 function 工具
- 回程(非流式+流式)按名字还原 custom_tool_call 项/事件
- 历史 custom_tool_call / custom_tool_call_output 项转换(此前被跳过)
- responsesItemWire 补 custom_tool_call 分支
- tools 全部被丢弃时不再转发 tool_choice(修复上游 400
  "'tool_choice' is only allowed when 'tools' are specified")

feat(apicompat): chat 桥补齐 tool_search/namespace/字符串工具声明

对齐参考实现(cc-switch transform_codex_chat.rs)的工具类型覆盖:
- tool_search → 固定 function 代理工具(query 必填/limit 可选),
  历史项 tool_search_call/tool_search_output 一并转换
- namespace → 子 function 工具摊平为 <namespace>__<name>,
  超 64 字符截断加 sha256 短哈希;历史项带 namespace 字段时同规则摊平
- tools: ["name"] 字符串简写按 custom 工具解析

fix(apicompat): 工具调用宣告延迟到类型可判定,兼容 name 迟到的上游

output_item.added 的 custom/function 判定依赖工具名。名字晚于首个分片
到达时,此前会按 function_call 宣告造成 added/done 项类型不一致。
改为存在 custom 工具且名字未到时延迟宣告,宣告时补发已累积的参数增量;
请求无 custom 工具时行为不变。

PR 标题与正文(可直接粘贴)

标题:fix(apicompat): responses→chat 桥丢弃 custom 工具导致 Codex 无法执行命令

正文:

## 问题

OpenAI APIKey 账号接入仅支持 /v1/chat/completions 的第三方上游
(DeepSeek/GLM/Kimi 等)时,Codex 0.14x 客户端文本对话正常,但无法执行
任何命令:exec 调用全部 "aborted",spawn_agent 派出的子智能体永远卡在
running,多轮后模型自述"环境无法执行文件系统命令"放弃。

## 根因

Codex 0.14x 的核心执行工具 exec 是 Responses API 的 custom(freeform)
工具,而非普通 function:

    {"type": "custom", "name": "exec", "format": {"type": "grammar", ...}}

两处缺陷叠加:

1. `responsesToolsToChatTools` 丢弃所有非 function 工具
   (chatcompletions_responses_bridge.go),模型工具列表中从未出现 exec;
2. SSE 序列化 `responsesItemWire` 按 item 类型白名单重组字段,无
   custom_tool_call 分支,call_id/name/input 会被剥离
   (responses_stream_event_wire.go)。

次生缺陷:工具全部被丢弃时 tool_choice 仍被转发,部分上游报 400
"'tool_choice' is only allowed when 'tools' are specified"。

## 改动

(三个提交的要点清单,见各提交说明)

设计与 cc-switch 的 Codex chat 转换层(transform_codex_chat.rs /
streaming_codex_chat.rs)对齐:custom 工具降级为 {"input": string}
function 工具;custom 调用的参数增量不可流式还原为自由文本,缓冲至
收尾一次性下发 custom_tool_call_input.delta/done。

## 测试

- 新增 17 个单元测试:请求工具转换、tool_choice 兜底、历史项转换、
  非流式/流式回程还原、wire 序列化字段完整性、name 迟到宣告、命名摊平
- `go test ./internal/pkg/apicompat/...` 与 service 相关测试全绿
- 真机验证:Codex CLI 0.142.5 → 本桥 → GLM/DeepSeek 系 chat-only 上游,
  exec 实际执行命令并多轮续作正常

## 影响面

仅影响 "APIKey 账号 + 判定不支持 Responses" 的 chat 直转路径:

- OAuth 账号与透传路径不经过本桥(透传为字节级原文转发)
- CC→Responses 反方向仅构造 ResponsesRequest,新增字段带 omitempty
  且不赋值,序列化输出不变
- 探测逻辑未动,路由判定不变
- 共享类型改动均为增量容错(新字段 omitempty;UnmarshalJSON 只新增
  字符串工具的解析成功路径)
2026-07-07 16:23:44 +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
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
Turtle_Li d73fa8eab2 fix: keep image generation migration immutable 2026-07-06 14:25:10 +08:00
Turtle_Li 202c6989a1 fix: preserve immutable initial migration 2026-07-06 14:11:49 +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
白喵喵喵喵 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
CHOS1N 5fcbe7e307 Add response_format compatibility mapping 2026-07-05 13:58:08 +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
Turtle_Li a994fbd77a feat: add batch image MVP 2026-07-04 05:30:50 +08:00
Bestony@Homelab fa70a7217c test(keys): update api key contract concurrency field 2026-07-02 16:12:24 +08:00
Bestony@Homelab 089a7b7fae feat(keys): add api key concurrency stats 2026-07-02 15:58:29 +08:00
724 changed files with 152493 additions and 59650 deletions
+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"
+11 -2
View File
@@ -8,6 +8,15 @@ permissions:
contents: read
jobs:
shell:
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- name: Check deployment scripts
run: |
/bin/bash -n deploy/apple-container.sh
/bin/bash deploy/tests/apple-container-test.sh
test:
runs-on: ubuntu-latest
steps:
@@ -20,7 +29,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
@@ -60,7 +69,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:
+1 -1
View File
@@ -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
+1 -1
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: |
+2
View File
@@ -116,6 +116,8 @@ backend/.installed
# 其他
# ===================
tests
!deploy/tests/
!deploy/tests/**
CLAUDE.md
.claude
scripts
+7 -2
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,7 +28,9 @@ 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.
# LegalDocumentView.vue (admin-compliance gate) build-time imports
+1 -11
View File
@@ -1,4 +1,4 @@
.PHONY: build build-backend build-frontend build-datamanagementd test test-backend test-frontend test-frontend-critical test-datamanagementd secret-scan
.PHONY: build build-backend build-frontend test test-backend test-frontend test-frontend-critical
FRONTEND_CRITICAL_VITEST := \
src/views/auth/__tests__/LinuxDoCallbackView.spec.ts \
@@ -19,10 +19,6 @@ build-backend:
build-frontend:
@pnpm --dir frontend run build
# 编译 datamanagementd(宿主机数据管理进程)
build-datamanagementd:
@cd datamanagement && go build -o datamanagementd ./cmd/datamanagementd
# 运行测试(后端 + 前端)
test: test-backend test-frontend
@@ -36,9 +32,3 @@ test-frontend:
test-frontend-critical:
@pnpm --dir frontend exec vitest run $(FRONTEND_CRITICAL_VITEST)
test-datamanagementd:
@cd datamanagement && go test ./...
secret-scan:
@python3 tools/secret_scan.py
+103 -12
View File
@@ -31,6 +31,11 @@ Please read the following carefully before using this project:
<table>
<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>Thanks to CCTK.AI for sponsoring this project! <a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> is an AI API gateway focused on stability and cost-effectiveness, offering fast relay services for Claude, OpenAI, Gemini, and other popular models. It works seamlessly with Claude Code, Codex, and other mainstream coding tools, delivering the same model capabilities at a fraction of the official cost. Register via <a href="https://cctk.ai/register?aff=SUB2API">this link</a> for faster, more stable, and more affordable AI API access.</td>
</tr>
<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>One API, every top model! <a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> is a production-grade, high-availability AI API gateway that makes your applications truly fast and stable: automatic failover, smart routing to the best-performing channel, and a production-grade SLA. An SLA that far surpasses any single provider — making stability your core competitive advantage. Works directly with Claude Code, Codex, and Gemini CLI. Register via this link to get started.</td>
@@ -41,6 +46,11 @@ Please read the following carefully before using this project:
<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>
@@ -145,6 +155,14 @@ Model authenticity: no content intervention or secondary filtering — experienc
</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
@@ -311,6 +329,7 @@ cd sub2api/deploy
# 2. Copy environment configuration
cp .env.example .env
chmod 600 .env
# 3. Edit configuration (generate secure passwords)
nano .env
@@ -430,7 +449,23 @@ rm -rf data/ postgres_data/ redis_data/
---
### Method 3: Build from Source
### Method 3: Apple container (macOS)
Apple-silicon Macs running macOS 26 can run the full Sub2API, PostgreSQL, and Redis stack with Apple `container` 1.1.0 or newer:
```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```
This is an operator-managed local workflow; Docker Compose remains the recommended production path. See [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md) for lifecycle commands, persistence, upgrades, and runtime limitations.
---
### Method 4: Build from Source
Build and run from source code for development or customization.
@@ -561,6 +596,27 @@ 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
#### OpenAI Responses WebSocket ingress limits
`gateway.openai_ws` bounds the lifetime and aggregate count of client-facing
Responses WebSocket sessions. These safeguards apply independently from
per-turn user and account concurrency slots, which are released between turns.
```yaml
gateway:
openai_ws:
# Close a client socket idle between completed turns; 0 disables this safeguard.
ingress_inter_turn_idle_timeout_seconds: 300
# Distributed API-key limit for live client ingress sessions; 0 disables it.
max_ingress_connections_per_api_key: 64
```
The connection cap is coordinated through Redis using a 60-second lease that
is refreshed every 20 seconds. A process that cannot confirm a lease for a
full lease lifetime closes its local WebSocket rather than continuing outside
the global cap. Use `http_bridge` for client-WebSocket/upstream-HTTP operation
when rolling out or mitigating upstream WebSocket issues.
#### ⚠️ 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.
@@ -619,20 +675,20 @@ Simple Mode is designed for individual developers or internal teams who want qui
---
## Grok / xAI OAuth Support
## Grok / xAI Support
Sub2API supports Grok subscription accounts through xAI OAuth and forwards OpenAI-compatible Responses traffic to xAI.
Sub2API supports both Grok subscription accounts through xAI OAuth and standard xAI API-key accounts. Both account types forward 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`
- Account types: OAuth subscription accounts and xAI API-key accounts
- Public Responses targets: `/v1/responses`, `/responses`, and `/backend-api/codex/responses`, forwarded to the Grok subscription proxy for OAuth accounts or `https://api.x.ai/v1/responses` for API-key accounts
- 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`
- Public Chat Completions targets: `/v1/chat/completions` and `/chat/completions`, forwarded to the account-type-specific xAI upstream
- 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.
- Text models: `grok-4.5`, `grok-4.3`, `grok-build-0.1`, `grok-composer-2.5-fast`, `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/edits`, `/videos/edits`, `/v1/videos/extensions`, `/videos/extensions`, `/v1/videos/{request_id}`, and `/videos/{request_id}`. Generation, editing, and extension 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
@@ -647,9 +703,10 @@ The Grok OAuth flow uses PKCE and does not require committing private secrets. T
| `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` |
| `XAI_BASE_URL` | `https://api.x.ai/v1`; runtime-diagnostics override (account `base_url` controls request forwarding) |
| `XAI_GROK_CLI_VERSION` | `0.2.93`; optional override for the client identity sent to `cli-chat-proxy.grok.com` |
Administrators can create or reauthorize Grok accounts from the dashboard, or use the admin API:
Administrators can create Grok OAuth or API-key accounts from the dashboard. OAuth authorization and reauthorization are also available through the admin API:
| Endpoint | Purpose |
|----------|---------|
@@ -658,13 +715,47 @@ Administrators can create or reauthorize Grok accounts from the dashboard, or us
| `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`.
OAuth credential storage reuses the existing account JSON fields: `access_token`, `refresh_token`, `token_type`, `expires_at`, `base_url`, optional `email`, optional `subscription_tier`, and `entitlement_status`. OAuth inference defaults to `https://cli-chat-proxy.grok.com/v1`; existing OAuth accounts that stored the old `https://api.x.ai/v1` default are redirected to the subscription proxy at runtime. Explicit custom upstreams remain unchanged.
For API-key accounts, select **Grok → API Key** in the create-account dialog. The official base URL defaults to `https://api.x.ai/v1`; credentials use the existing `base_url` and `api_key` account fields. OAuth accounts continue to use the subscription flow above.
### Grok Build CLI Configuration
1. In the Sub2API admin dashboard, add either a `grok` OAuth account and complete xAI authorization, or add a Grok API-key account.
2. Create a Grok group, attach the account to it, then create a Sub2API API key assigned to that group.
3. In the user API-key page, click **Use Key** and select **Grok CLI**. The modal generates the correct file and base URL for macOS/Linux or Windows. It also provides an OpenCode configuration on the **OpenCode** tab.
4. If configuring manually, save the following as `~/.grok/config.toml` (Windows: `%USERPROFILE%\.grok\config.toml`):
```toml
[models]
default = "sub2api-grok"
web_search = "sub2api-grok"
[model."sub2api-grok"]
model = "grok-4.5"
base_url = "https://your-sub2api.example.com/v1"
name = "Grok 4.5 via Sub2API"
description = "Grok 4.5 through a Sub2API Grok group"
api_key = "sk-your-sub2api-key"
api_backend = "responses"
context_window = 1000000
supports_backend_search = true
```
Back up an existing `config.toml` before merging the entry. The file contains a Sub2API API key, so keep it private and restrict its permissions where supported. Verify the effective configuration and make a smoke request:
```bash
grok inspect
grok -p "Reply with sub2api-ok" -m sub2api-grok
```
The `base_url` above is the public Sub2API URL ending in `/v1`, not `api.x.ai` or the internal xAI OAuth proxy URL.
### 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.
`401` responses temporarily remove accounts with invalid credentials from scheduling. `403` responses are treated as access or entitlement failures instead of token-refresh loops. `429` responses use `Retry-After` or a short cooldown to temporarily remove the account from scheduling.
---
+36 -1
View File
@@ -32,6 +32,11 @@
<table>
<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>感谢 CCTK.AI 赞助了本项目!<a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> 是一个专注于稳定与性价比的 AI API 网关平台,提供 Claude、OpenAI、Gemini 等主流模型的高速中转服务,无缝兼容 Claude Code、Codex 等主流编程工具,以远低于官方的成本获得同等的模型能力。点击<a href="https://cctk.ai/register?aff=SUB2API">此链接</a>注册,即刻体验更快、更稳、更省的 AI API 接入。</td>
</tr>
<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>
@@ -42,6 +47,11 @@
<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>
@@ -148,6 +158,14 @@
</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>
@@ -315,6 +333,7 @@ cd sub2api/deploy
# 2. 复制环境配置文件
cp .env.example .env
chmod 600 .env
# 3. 编辑配置(生成安全密码)
nano .env
@@ -446,7 +465,23 @@ rm -rf data/ postgres_data/ redis_data/
---
### 方式三:源码编译
### 方式三:Apple container(macOS)
Apple 芯片 Mac 在 macOS 26 上可使用 Apple `container` 1.1.0 或更高版本运行完整的 Sub2API、PostgreSQL 和 Redis:
```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```
该方式面向本地开发和人工运维,不提供持续重启监管;生产部署仍推荐 Docker Compose。生命周期命令、持久化、升级和运行时限制见 [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md)。
---
### 方式四:源码编译
从源码编译安装,适合开发或定制需求。
+36 -1
View File
@@ -31,6 +31,11 @@
<table>
<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>CCTK.AI のご支援に感謝します!<a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> は安定性とコストパフォーマンスにこだわった AI API ゲートウェイで、Claude、OpenAI、Gemini など主要モデルの高速中継サービスを提供しています。Claude Code や Codex などの主要なコーディングツールにシームレスに対応し、公式価格を大きく下回るコストで同等のモデル能力を利用できます。<a href="https://cctk.ai/register?aff=SUB2API">こちらのリンク</a>から登録して、より速く、より安定した、よりお得な AI API 接続をお試しください。</td>
</tr>
<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>1つの API で、トップモデルを使い放題!<a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> は本番環境グレードで高可用性の AI API ゲートウェイに特化し、アプリを真に高速・安定させます:自動フェイルオーバー、最適なチャネルへのスマートルーティング、本番グレードの SLA 保証。単一プロバイダーをはるかに上回る SLA で、安定性をあなたの核心的な競争力にします。</td>
@@ -41,6 +46,11 @@
<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>
@@ -143,6 +153,14 @@
</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>
## 概要
@@ -309,6 +327,7 @@ cd sub2api/deploy
# 2. 環境設定ファイルをコピー
cp .env.example .env
chmod 600 .env
# 3. 設定を編集(セキュアなパスワードを生成)
nano .env
@@ -428,7 +447,23 @@ rm -rf data/ postgres_data/ redis_data/
---
### 方法3: ソースからビルド
### 方法3: Apple container(macOS)
Apple シリコン搭載 Mac と macOS 26 では、Apple `container` 1.1.0 以降を使用して Sub2API、PostgreSQL、Redis の完全なスタックを実行できます:
```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```
これはローカル開発および手動運用向けです。本番環境では引き続き Docker Compose を推奨します。ライフサイクル、永続化、アップグレード、制限については [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md) を参照してください。
---
### 方法4: ソースからビルド
開発やカスタマイズのためにソースコードからビルドして実行します。
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.26.4-alpine
FROM golang:1.26.5-alpine
WORKDIR /app
+1 -1
View File
@@ -1 +1 @@
0.1.144
0.1.153
+14
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,
@@ -167,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
+34 -11
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)
@@ -182,12 +190,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
claudeUsageFetcher := repository.NewClaudeUsageFetcher(httpUpstream)
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
grokQuotaFetcher := service.NewGrokQuotaFetcher()
grokQuotaService := service.ProvideGrokQuotaService(accountRepository, proxyRepository, grokTokenProvider, httpUpstream, usageLogRepository)
openAIQuotaService := service.ProvideOpenAIQuotaService(accountRepository, proxyRepository, openAITokenProvider, privacyClientFactory)
usageCache := service.NewUsageCache()
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, grokQuotaFetcher, openAIQuotaService, usageCache, identityCache, tlsFingerprintProfileService)
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, grokQuotaFetcher, grokQuotaService, 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)
accountHandler := admin.ProvideAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator, grokQuotaService)
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
dataManagementService := service.NewDataManagementService()
dataManagementHandler := admin.NewDataManagementHandler(dataManagementService)
@@ -199,7 +208,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
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)
@@ -256,12 +264,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
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)
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService)
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)
@@ -280,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, grokOAuthService, 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,
@@ -322,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,
@@ -403,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
+2
View File
@@ -65,6 +65,8 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
subscriptionExpirySvc,
&service.UsageCleanupService{},
idempotencyCleanupSvc,
&service.BatchImageCleanupService{},
nil, // batchImageWorker
pricingSvc,
emailQueueSvc,
billingCacheSvc,
+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
+2 -1
View File
@@ -167,6 +167,7 @@ const (
ProviderOpenai Provider = "openai"
ProviderAnthropic Provider = "anthropic"
ProviderGemini Provider = "gemini"
ProviderGrok Provider = "grok"
)
func (pr Provider) String() string {
@@ -176,7 +177,7 @@ func (pr Provider) String() string {
// ProviderValidator is a validator for the "provider" field enum values. It is called by the builders before save.
func ProviderValidator(pr Provider) error {
switch pr {
case ProviderOpenai, ProviderAnthropic, ProviderGemini:
case ProviderOpenai, ProviderAnthropic, ProviderGemini, ProviderGrok:
return nil
default:
return fmt.Errorf("channelmonitor: invalid enum value for provider field: %q", pr)
@@ -103,6 +103,7 @@ const (
ProviderOpenai Provider = "openai"
ProviderAnthropic Provider = "anthropic"
ProviderGemini Provider = "gemini"
ProviderGrok Provider = "grok"
)
func (pr Provider) String() string {
@@ -112,7 +113,7 @@ func (pr Provider) String() string {
// ProviderValidator is a validator for the "provider" field enum values. It is called by the builders before save.
func ProviderValidator(pr Provider) error {
switch pr {
case ProviderOpenai, ProviderAnthropic, ProviderGemini:
case ProviderOpenai, ProviderAnthropic, ProviderGemini, ProviderGrok:
return nil
default:
return fmt.Errorf("channelmonitorrequesttemplate: invalid enum value for provider field: %q", pr)
+457 -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:
@@ -1671,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
@@ -6242,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,
+115 -4
View File
@@ -33,9 +33,9 @@ type Group struct {
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
// 是否启用高峰时段倍率
PeakRateEnabled bool `json:"peak_rate_enabled,omitempty"`
// 高峰开始时间 HH:MM(含),如 14:00;空表示未配置
// 高峰开始时间 HH:MM(含),如 14:00;空表示未配置;不支持跨天
PeakStart string `json:"peak_start,omitempty"`
// 高峰结束时间 HH:MM(不含),如 18:00
// 高峰结束时间 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"`
@@ -57,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 时生效
@@ -67,6 +69,22 @@ 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"`
// Codex alpha/search 网页搜索单次价格(USD/次);nil 表示使用默认价 0.01(官方 $10/1000 次)
WebSearchPricePerCall *float64 `json:"web_search_price_per_call,omitempty"`
// 是否仅允许 Claude Code 客户端
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
// 非 Claude Code 请求降级使用的分组 ID
@@ -205,9 +223,9 @@ 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.FieldPeakRateEnabled, 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.FieldPeakRateMultiplier, 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, group.FieldWebSearchPricePerCall:
values[i] = new(sql.NullFloat64)
case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder, group.FieldRpmLimit:
values[i] = new(sql.NullInt64)
@@ -355,6 +373,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])
@@ -388,6 +412,58 @@ 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.FieldWebSearchPricePerCall:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field web_search_price_per_call", values[i])
} else if value.Valid {
_m.WebSearchPricePerCall = new(float64)
*_m.WebSearchPricePerCall = 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])
@@ -631,6 +707,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(", ")
@@ -652,6 +731,38 @@ 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(", ")
if v := _m.WebSearchPricePerCall; v != nil {
builder.WriteString("web_search_price_per_call=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("claude_code_only=")
builder.WriteString(fmt.Sprintf("%v", _m.ClaudeCodeOnly))
builder.WriteString(", ")
+82
View File
@@ -54,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.
@@ -64,6 +66,22 @@ 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"
// FieldWebSearchPricePerCall holds the string denoting the web_search_price_per_call field in the database.
FieldWebSearchPricePerCall = "web_search_price_per_call"
// 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.
@@ -188,11 +206,20 @@ var Columns = []string{
FieldMonthlyLimitUsd,
FieldDefaultValidityDays,
FieldAllowImageGeneration,
FieldAllowBatchImageGeneration,
FieldImageRateIndependent,
FieldImageRateMultiplier,
FieldImagePrice1k,
FieldImagePrice2k,
FieldImagePrice4k,
FieldBatchImageDiscountMultiplier,
FieldBatchImageHoldMultiplier,
FieldVideoRateIndependent,
FieldVideoRateMultiplier,
FieldVideoPrice480p,
FieldVideoPrice720p,
FieldVideoPrice1080p,
FieldWebSearchPricePerCall,
FieldClaudeCodeOnly,
FieldFallbackGroupID,
FieldFallbackGroupIDOnInvalidRequest,
@@ -277,10 +304,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.
@@ -412,6 +449,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()
@@ -437,6 +479,46 @@ 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()
}
// ByWebSearchPricePerCall orders the results by the web_search_price_per_call field.
func ByWebSearchPricePerCall(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWebSearchPricePerCall, opts...).ToFunc()
}
// ByClaudeCodeOnly orders the results by the claude_code_only field.
func ByClaudeCodeOnly(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldClaudeCodeOnly, opts...).ToFunc()
+385
View File
@@ -150,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))
@@ -175,6 +180,46 @@ 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))
}
// WebSearchPricePerCall applies equality check predicate on the "web_search_price_per_call" field. It's identical to WebSearchPricePerCallEQ.
func WebSearchPricePerCall(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldWebSearchPricePerCall, 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))
@@ -1125,6 +1170,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))
@@ -1325,6 +1380,336 @@ 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))
}
// WebSearchPricePerCallEQ applies the EQ predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallNEQ applies the NEQ predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallNEQ(v float64) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallIn applies the In predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldIn(FieldWebSearchPricePerCall, vs...))
}
// WebSearchPricePerCallNotIn applies the NotIn predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallNotIn(vs ...float64) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldWebSearchPricePerCall, vs...))
}
// WebSearchPricePerCallGT applies the GT predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallGT(v float64) predicate.Group {
return predicate.Group(sql.FieldGT(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallGTE applies the GTE predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallGTE(v float64) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallLT applies the LT predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallLT(v float64) predicate.Group {
return predicate.Group(sql.FieldLT(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallLTE applies the LTE predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallLTE(v float64) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldWebSearchPricePerCall, v))
}
// WebSearchPricePerCallIsNil applies the IsNil predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallIsNil() predicate.Group {
return predicate.Group(sql.FieldIsNull(FieldWebSearchPricePerCall))
}
// WebSearchPricePerCallNotNil applies the NotNil predicate on the "web_search_price_per_call" field.
func WebSearchPricePerCallNotNil() predicate.Group {
return predicate.Group(sql.FieldNotNull(FieldWebSearchPricePerCall))
}
// ClaudeCodeOnlyEQ applies the EQ predicate on the "claude_code_only" field.
func ClaudeCodeOnlyEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
+777
View File
@@ -287,6 +287,20 @@ func (_c *GroupCreate) SetNillableAllowImageGeneration(v *bool) *GroupCreate {
return _c
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (_c *GroupCreate) SetAllowBatchImageGeneration(v bool) *GroupCreate {
_c.mutation.SetAllowBatchImageGeneration(v)
return _c
}
// SetNillableAllowBatchImageGeneration sets the "allow_batch_image_generation" field if the given value is not nil.
func (_c *GroupCreate) SetNillableAllowBatchImageGeneration(v *bool) *GroupCreate {
if v != nil {
_c.SetAllowBatchImageGeneration(*v)
}
return _c
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (_c *GroupCreate) SetImageRateIndependent(v bool) *GroupCreate {
_c.mutation.SetImageRateIndependent(v)
@@ -357,6 +371,118 @@ func (_c *GroupCreate) SetNillableImagePrice4k(v *float64) *GroupCreate {
return _c
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (_c *GroupCreate) SetBatchImageDiscountMultiplier(v float64) *GroupCreate {
_c.mutation.SetBatchImageDiscountMultiplier(v)
return _c
}
// SetNillableBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field if the given value is not nil.
func (_c *GroupCreate) SetNillableBatchImageDiscountMultiplier(v *float64) *GroupCreate {
if v != nil {
_c.SetBatchImageDiscountMultiplier(*v)
}
return _c
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (_c *GroupCreate) SetBatchImageHoldMultiplier(v float64) *GroupCreate {
_c.mutation.SetBatchImageHoldMultiplier(v)
return _c
}
// SetNillableBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field if the given value is not nil.
func (_c *GroupCreate) SetNillableBatchImageHoldMultiplier(v *float64) *GroupCreate {
if v != nil {
_c.SetBatchImageHoldMultiplier(*v)
}
return _c
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (_c *GroupCreate) SetVideoRateIndependent(v bool) *GroupCreate {
_c.mutation.SetVideoRateIndependent(v)
return _c
}
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
func (_c *GroupCreate) SetNillableVideoRateIndependent(v *bool) *GroupCreate {
if v != nil {
_c.SetVideoRateIndependent(*v)
}
return _c
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (_c *GroupCreate) SetVideoRateMultiplier(v float64) *GroupCreate {
_c.mutation.SetVideoRateMultiplier(v)
return _c
}
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
func (_c *GroupCreate) SetNillableVideoRateMultiplier(v *float64) *GroupCreate {
if v != nil {
_c.SetVideoRateMultiplier(*v)
}
return _c
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (_c *GroupCreate) SetVideoPrice480p(v float64) *GroupCreate {
_c.mutation.SetVideoPrice480p(v)
return _c
}
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
func (_c *GroupCreate) SetNillableVideoPrice480p(v *float64) *GroupCreate {
if v != nil {
_c.SetVideoPrice480p(*v)
}
return _c
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (_c *GroupCreate) SetVideoPrice720p(v float64) *GroupCreate {
_c.mutation.SetVideoPrice720p(v)
return _c
}
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
func (_c *GroupCreate) SetNillableVideoPrice720p(v *float64) *GroupCreate {
if v != nil {
_c.SetVideoPrice720p(*v)
}
return _c
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (_c *GroupCreate) SetVideoPrice1080p(v float64) *GroupCreate {
_c.mutation.SetVideoPrice1080p(v)
return _c
}
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
func (_c *GroupCreate) SetNillableVideoPrice1080p(v *float64) *GroupCreate {
if v != nil {
_c.SetVideoPrice1080p(*v)
}
return _c
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (_c *GroupCreate) SetWebSearchPricePerCall(v float64) *GroupCreate {
_c.mutation.SetWebSearchPricePerCall(v)
return _c
}
// SetNillableWebSearchPricePerCall sets the "web_search_price_per_call" field if the given value is not nil.
func (_c *GroupCreate) SetNillableWebSearchPricePerCall(v *float64) *GroupCreate {
if v != nil {
_c.SetWebSearchPricePerCall(*v)
}
return _c
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_c *GroupCreate) SetClaudeCodeOnly(v bool) *GroupCreate {
_c.mutation.SetClaudeCodeOnly(v)
@@ -736,6 +862,10 @@ func (_c *GroupCreate) defaults() error {
v := group.DefaultAllowImageGeneration
_c.mutation.SetAllowImageGeneration(v)
}
if _, ok := _c.mutation.AllowBatchImageGeneration(); !ok {
v := group.DefaultAllowBatchImageGeneration
_c.mutation.SetAllowBatchImageGeneration(v)
}
if _, ok := _c.mutation.ImageRateIndependent(); !ok {
v := group.DefaultImageRateIndependent
_c.mutation.SetImageRateIndependent(v)
@@ -744,6 +874,22 @@ func (_c *GroupCreate) defaults() error {
v := group.DefaultImageRateMultiplier
_c.mutation.SetImageRateMultiplier(v)
}
if _, ok := _c.mutation.BatchImageDiscountMultiplier(); !ok {
v := group.DefaultBatchImageDiscountMultiplier
_c.mutation.SetBatchImageDiscountMultiplier(v)
}
if _, ok := _c.mutation.BatchImageHoldMultiplier(); !ok {
v := group.DefaultBatchImageHoldMultiplier
_c.mutation.SetBatchImageHoldMultiplier(v)
}
if _, ok := _c.mutation.VideoRateIndependent(); !ok {
v := group.DefaultVideoRateIndependent
_c.mutation.SetVideoRateIndependent(v)
}
if _, ok := _c.mutation.VideoRateMultiplier(); !ok {
v := group.DefaultVideoRateMultiplier
_c.mutation.SetVideoRateMultiplier(v)
}
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
v := group.DefaultClaudeCodeOnly
_c.mutation.SetClaudeCodeOnly(v)
@@ -869,12 +1015,27 @@ func (_c *GroupCreate) check() error {
if _, ok := _c.mutation.AllowImageGeneration(); !ok {
return &ValidationError{Name: "allow_image_generation", err: errors.New(`ent: missing required field "Group.allow_image_generation"`)}
}
if _, ok := _c.mutation.AllowBatchImageGeneration(); !ok {
return &ValidationError{Name: "allow_batch_image_generation", err: errors.New(`ent: missing required field "Group.allow_batch_image_generation"`)}
}
if _, ok := _c.mutation.ImageRateIndependent(); !ok {
return &ValidationError{Name: "image_rate_independent", err: errors.New(`ent: missing required field "Group.image_rate_independent"`)}
}
if _, ok := _c.mutation.ImageRateMultiplier(); !ok {
return &ValidationError{Name: "image_rate_multiplier", err: errors.New(`ent: missing required field "Group.image_rate_multiplier"`)}
}
if _, ok := _c.mutation.BatchImageDiscountMultiplier(); !ok {
return &ValidationError{Name: "batch_image_discount_multiplier", err: errors.New(`ent: missing required field "Group.batch_image_discount_multiplier"`)}
}
if _, ok := _c.mutation.BatchImageHoldMultiplier(); !ok {
return &ValidationError{Name: "batch_image_hold_multiplier", err: errors.New(`ent: missing required field "Group.batch_image_hold_multiplier"`)}
}
if _, ok := _c.mutation.VideoRateIndependent(); !ok {
return &ValidationError{Name: "video_rate_independent", err: errors.New(`ent: missing required field "Group.video_rate_independent"`)}
}
if _, ok := _c.mutation.VideoRateMultiplier(); !ok {
return &ValidationError{Name: "video_rate_multiplier", err: errors.New(`ent: missing required field "Group.video_rate_multiplier"`)}
}
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
}
@@ -1019,6 +1180,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
_spec.SetField(group.FieldAllowImageGeneration, field.TypeBool, value)
_node.AllowImageGeneration = value
}
if value, ok := _c.mutation.AllowBatchImageGeneration(); ok {
_spec.SetField(group.FieldAllowBatchImageGeneration, field.TypeBool, value)
_node.AllowBatchImageGeneration = value
}
if value, ok := _c.mutation.ImageRateIndependent(); ok {
_spec.SetField(group.FieldImageRateIndependent, field.TypeBool, value)
_node.ImageRateIndependent = value
@@ -1039,6 +1204,38 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
_spec.SetField(group.FieldImagePrice4k, field.TypeFloat64, value)
_node.ImagePrice4k = &value
}
if value, ok := _c.mutation.BatchImageDiscountMultiplier(); ok {
_spec.SetField(group.FieldBatchImageDiscountMultiplier, field.TypeFloat64, value)
_node.BatchImageDiscountMultiplier = value
}
if value, ok := _c.mutation.BatchImageHoldMultiplier(); ok {
_spec.SetField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
_node.BatchImageHoldMultiplier = value
}
if value, ok := _c.mutation.VideoRateIndependent(); ok {
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
_node.VideoRateIndependent = value
}
if value, ok := _c.mutation.VideoRateMultiplier(); ok {
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
_node.VideoRateMultiplier = value
}
if value, ok := _c.mutation.VideoPrice480p(); ok {
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
_node.VideoPrice480p = &value
}
if value, ok := _c.mutation.VideoPrice720p(); ok {
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
_node.VideoPrice720p = &value
}
if value, ok := _c.mutation.VideoPrice1080p(); ok {
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
_node.VideoPrice1080p = &value
}
if value, ok := _c.mutation.WebSearchPricePerCall(); ok {
_spec.SetField(group.FieldWebSearchPricePerCall, field.TypeFloat64, value)
_node.WebSearchPricePerCall = &value
}
if value, ok := _c.mutation.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
_node.ClaudeCodeOnly = value
@@ -1537,6 +1734,18 @@ func (u *GroupUpsert) UpdateAllowImageGeneration() *GroupUpsert {
return u
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (u *GroupUpsert) SetAllowBatchImageGeneration(v bool) *GroupUpsert {
u.Set(group.FieldAllowBatchImageGeneration, v)
return u
}
// UpdateAllowBatchImageGeneration sets the "allow_batch_image_generation" field to the value that was provided on create.
func (u *GroupUpsert) UpdateAllowBatchImageGeneration() *GroupUpsert {
u.SetExcluded(group.FieldAllowBatchImageGeneration)
return u
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (u *GroupUpsert) SetImageRateIndependent(v bool) *GroupUpsert {
u.Set(group.FieldImageRateIndependent, v)
@@ -1639,6 +1848,168 @@ func (u *GroupUpsert) ClearImagePrice4k() *GroupUpsert {
return u
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (u *GroupUpsert) SetBatchImageDiscountMultiplier(v float64) *GroupUpsert {
u.Set(group.FieldBatchImageDiscountMultiplier, v)
return u
}
// UpdateBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field to the value that was provided on create.
func (u *GroupUpsert) UpdateBatchImageDiscountMultiplier() *GroupUpsert {
u.SetExcluded(group.FieldBatchImageDiscountMultiplier)
return u
}
// AddBatchImageDiscountMultiplier adds v to the "batch_image_discount_multiplier" field.
func (u *GroupUpsert) AddBatchImageDiscountMultiplier(v float64) *GroupUpsert {
u.Add(group.FieldBatchImageDiscountMultiplier, v)
return u
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (u *GroupUpsert) SetBatchImageHoldMultiplier(v float64) *GroupUpsert {
u.Set(group.FieldBatchImageHoldMultiplier, v)
return u
}
// UpdateBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field to the value that was provided on create.
func (u *GroupUpsert) UpdateBatchImageHoldMultiplier() *GroupUpsert {
u.SetExcluded(group.FieldBatchImageHoldMultiplier)
return u
}
// AddBatchImageHoldMultiplier adds v to the "batch_image_hold_multiplier" field.
func (u *GroupUpsert) AddBatchImageHoldMultiplier(v float64) *GroupUpsert {
u.Add(group.FieldBatchImageHoldMultiplier, v)
return u
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (u *GroupUpsert) SetVideoRateIndependent(v bool) *GroupUpsert {
u.Set(group.FieldVideoRateIndependent, v)
return u
}
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
func (u *GroupUpsert) UpdateVideoRateIndependent() *GroupUpsert {
u.SetExcluded(group.FieldVideoRateIndependent)
return u
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (u *GroupUpsert) SetVideoRateMultiplier(v float64) *GroupUpsert {
u.Set(group.FieldVideoRateMultiplier, v)
return u
}
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
func (u *GroupUpsert) UpdateVideoRateMultiplier() *GroupUpsert {
u.SetExcluded(group.FieldVideoRateMultiplier)
return u
}
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
func (u *GroupUpsert) AddVideoRateMultiplier(v float64) *GroupUpsert {
u.Add(group.FieldVideoRateMultiplier, v)
return u
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (u *GroupUpsert) SetVideoPrice480p(v float64) *GroupUpsert {
u.Set(group.FieldVideoPrice480p, v)
return u
}
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
func (u *GroupUpsert) UpdateVideoPrice480p() *GroupUpsert {
u.SetExcluded(group.FieldVideoPrice480p)
return u
}
// AddVideoPrice480p adds v to the "video_price_480p" field.
func (u *GroupUpsert) AddVideoPrice480p(v float64) *GroupUpsert {
u.Add(group.FieldVideoPrice480p, v)
return u
}
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
func (u *GroupUpsert) ClearVideoPrice480p() *GroupUpsert {
u.SetNull(group.FieldVideoPrice480p)
return u
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (u *GroupUpsert) SetVideoPrice720p(v float64) *GroupUpsert {
u.Set(group.FieldVideoPrice720p, v)
return u
}
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
func (u *GroupUpsert) UpdateVideoPrice720p() *GroupUpsert {
u.SetExcluded(group.FieldVideoPrice720p)
return u
}
// AddVideoPrice720p adds v to the "video_price_720p" field.
func (u *GroupUpsert) AddVideoPrice720p(v float64) *GroupUpsert {
u.Add(group.FieldVideoPrice720p, v)
return u
}
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
func (u *GroupUpsert) ClearVideoPrice720p() *GroupUpsert {
u.SetNull(group.FieldVideoPrice720p)
return u
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (u *GroupUpsert) SetVideoPrice1080p(v float64) *GroupUpsert {
u.Set(group.FieldVideoPrice1080p, v)
return u
}
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
func (u *GroupUpsert) UpdateVideoPrice1080p() *GroupUpsert {
u.SetExcluded(group.FieldVideoPrice1080p)
return u
}
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
func (u *GroupUpsert) AddVideoPrice1080p(v float64) *GroupUpsert {
u.Add(group.FieldVideoPrice1080p, v)
return u
}
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
func (u *GroupUpsert) ClearVideoPrice1080p() *GroupUpsert {
u.SetNull(group.FieldVideoPrice1080p)
return u
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (u *GroupUpsert) SetWebSearchPricePerCall(v float64) *GroupUpsert {
u.Set(group.FieldWebSearchPricePerCall, v)
return u
}
// UpdateWebSearchPricePerCall sets the "web_search_price_per_call" field to the value that was provided on create.
func (u *GroupUpsert) UpdateWebSearchPricePerCall() *GroupUpsert {
u.SetExcluded(group.FieldWebSearchPricePerCall)
return u
}
// AddWebSearchPricePerCall adds v to the "web_search_price_per_call" field.
func (u *GroupUpsert) AddWebSearchPricePerCall(v float64) *GroupUpsert {
u.Add(group.FieldWebSearchPricePerCall, v)
return u
}
// ClearWebSearchPricePerCall clears the value of the "web_search_price_per_call" field.
func (u *GroupUpsert) ClearWebSearchPricePerCall() *GroupUpsert {
u.SetNull(group.FieldWebSearchPricePerCall)
return u
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsert) SetClaudeCodeOnly(v bool) *GroupUpsert {
u.Set(group.FieldClaudeCodeOnly, v)
@@ -2235,6 +2606,20 @@ func (u *GroupUpsertOne) UpdateAllowImageGeneration() *GroupUpsertOne {
})
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (u *GroupUpsertOne) SetAllowBatchImageGeneration(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetAllowBatchImageGeneration(v)
})
}
// UpdateAllowBatchImageGeneration sets the "allow_batch_image_generation" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateAllowBatchImageGeneration() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateAllowBatchImageGeneration()
})
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (u *GroupUpsertOne) SetImageRateIndependent(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
@@ -2354,6 +2739,195 @@ func (u *GroupUpsertOne) ClearImagePrice4k() *GroupUpsertOne {
})
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (u *GroupUpsertOne) SetBatchImageDiscountMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetBatchImageDiscountMultiplier(v)
})
}
// AddBatchImageDiscountMultiplier adds v to the "batch_image_discount_multiplier" field.
func (u *GroupUpsertOne) AddBatchImageDiscountMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddBatchImageDiscountMultiplier(v)
})
}
// UpdateBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateBatchImageDiscountMultiplier() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateBatchImageDiscountMultiplier()
})
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (u *GroupUpsertOne) SetBatchImageHoldMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetBatchImageHoldMultiplier(v)
})
}
// AddBatchImageHoldMultiplier adds v to the "batch_image_hold_multiplier" field.
func (u *GroupUpsertOne) AddBatchImageHoldMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddBatchImageHoldMultiplier(v)
})
}
// UpdateBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateBatchImageHoldMultiplier() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateBatchImageHoldMultiplier()
})
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (u *GroupUpsertOne) SetVideoRateIndependent(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetVideoRateIndependent(v)
})
}
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateVideoRateIndependent() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoRateIndependent()
})
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (u *GroupUpsertOne) SetVideoRateMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetVideoRateMultiplier(v)
})
}
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
func (u *GroupUpsertOne) AddVideoRateMultiplier(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddVideoRateMultiplier(v)
})
}
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateVideoRateMultiplier() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoRateMultiplier()
})
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (u *GroupUpsertOne) SetVideoPrice480p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice480p(v)
})
}
// AddVideoPrice480p adds v to the "video_price_480p" field.
func (u *GroupUpsertOne) AddVideoPrice480p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice480p(v)
})
}
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateVideoPrice480p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice480p()
})
}
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
func (u *GroupUpsertOne) ClearVideoPrice480p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice480p()
})
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (u *GroupUpsertOne) SetVideoPrice720p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice720p(v)
})
}
// AddVideoPrice720p adds v to the "video_price_720p" field.
func (u *GroupUpsertOne) AddVideoPrice720p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice720p(v)
})
}
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateVideoPrice720p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice720p()
})
}
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
func (u *GroupUpsertOne) ClearVideoPrice720p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice720p()
})
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (u *GroupUpsertOne) SetVideoPrice1080p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice1080p(v)
})
}
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
func (u *GroupUpsertOne) AddVideoPrice1080p(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice1080p(v)
})
}
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateVideoPrice1080p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice1080p()
})
}
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
func (u *GroupUpsertOne) ClearVideoPrice1080p() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice1080p()
})
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (u *GroupUpsertOne) SetWebSearchPricePerCall(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetWebSearchPricePerCall(v)
})
}
// AddWebSearchPricePerCall adds v to the "web_search_price_per_call" field.
func (u *GroupUpsertOne) AddWebSearchPricePerCall(v float64) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.AddWebSearchPricePerCall(v)
})
}
// UpdateWebSearchPricePerCall sets the "web_search_price_per_call" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateWebSearchPricePerCall() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateWebSearchPricePerCall()
})
}
// ClearWebSearchPricePerCall clears the value of the "web_search_price_per_call" field.
func (u *GroupUpsertOne) ClearWebSearchPricePerCall() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.ClearWebSearchPricePerCall()
})
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsertOne) SetClaudeCodeOnly(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
@@ -3153,6 +3727,20 @@ func (u *GroupUpsertBulk) UpdateAllowImageGeneration() *GroupUpsertBulk {
})
}
// SetAllowBatchImageGeneration sets the "allow_batch_image_generation" field.
func (u *GroupUpsertBulk) SetAllowBatchImageGeneration(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetAllowBatchImageGeneration(v)
})
}
// UpdateAllowBatchImageGeneration sets the "allow_batch_image_generation" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateAllowBatchImageGeneration() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateAllowBatchImageGeneration()
})
}
// SetImageRateIndependent sets the "image_rate_independent" field.
func (u *GroupUpsertBulk) SetImageRateIndependent(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
@@ -3272,6 +3860,195 @@ func (u *GroupUpsertBulk) ClearImagePrice4k() *GroupUpsertBulk {
})
}
// SetBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field.
func (u *GroupUpsertBulk) SetBatchImageDiscountMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetBatchImageDiscountMultiplier(v)
})
}
// AddBatchImageDiscountMultiplier adds v to the "batch_image_discount_multiplier" field.
func (u *GroupUpsertBulk) AddBatchImageDiscountMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddBatchImageDiscountMultiplier(v)
})
}
// UpdateBatchImageDiscountMultiplier sets the "batch_image_discount_multiplier" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateBatchImageDiscountMultiplier() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateBatchImageDiscountMultiplier()
})
}
// SetBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field.
func (u *GroupUpsertBulk) SetBatchImageHoldMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetBatchImageHoldMultiplier(v)
})
}
// AddBatchImageHoldMultiplier adds v to the "batch_image_hold_multiplier" field.
func (u *GroupUpsertBulk) AddBatchImageHoldMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddBatchImageHoldMultiplier(v)
})
}
// UpdateBatchImageHoldMultiplier sets the "batch_image_hold_multiplier" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateBatchImageHoldMultiplier() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateBatchImageHoldMultiplier()
})
}
// SetVideoRateIndependent sets the "video_rate_independent" field.
func (u *GroupUpsertBulk) SetVideoRateIndependent(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetVideoRateIndependent(v)
})
}
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateVideoRateIndependent() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoRateIndependent()
})
}
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
func (u *GroupUpsertBulk) SetVideoRateMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetVideoRateMultiplier(v)
})
}
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
func (u *GroupUpsertBulk) AddVideoRateMultiplier(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddVideoRateMultiplier(v)
})
}
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateVideoRateMultiplier() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoRateMultiplier()
})
}
// SetVideoPrice480p sets the "video_price_480p" field.
func (u *GroupUpsertBulk) SetVideoPrice480p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice480p(v)
})
}
// AddVideoPrice480p adds v to the "video_price_480p" field.
func (u *GroupUpsertBulk) AddVideoPrice480p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice480p(v)
})
}
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateVideoPrice480p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice480p()
})
}
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
func (u *GroupUpsertBulk) ClearVideoPrice480p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice480p()
})
}
// SetVideoPrice720p sets the "video_price_720p" field.
func (u *GroupUpsertBulk) SetVideoPrice720p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice720p(v)
})
}
// AddVideoPrice720p adds v to the "video_price_720p" field.
func (u *GroupUpsertBulk) AddVideoPrice720p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice720p(v)
})
}
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateVideoPrice720p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice720p()
})
}
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
func (u *GroupUpsertBulk) ClearVideoPrice720p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice720p()
})
}
// SetVideoPrice1080p sets the "video_price_1080p" field.
func (u *GroupUpsertBulk) SetVideoPrice1080p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetVideoPrice1080p(v)
})
}
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
func (u *GroupUpsertBulk) AddVideoPrice1080p(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddVideoPrice1080p(v)
})
}
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateVideoPrice1080p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateVideoPrice1080p()
})
}
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
func (u *GroupUpsertBulk) ClearVideoPrice1080p() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.ClearVideoPrice1080p()
})
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (u *GroupUpsertBulk) SetWebSearchPricePerCall(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetWebSearchPricePerCall(v)
})
}
// AddWebSearchPricePerCall adds v to the "web_search_price_per_call" field.
func (u *GroupUpsertBulk) AddWebSearchPricePerCall(v float64) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.AddWebSearchPricePerCall(v)
})
}
// UpdateWebSearchPricePerCall sets the "web_search_price_per_call" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateWebSearchPricePerCall() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateWebSearchPricePerCall()
})
}
// ClearWebSearchPricePerCall clears the value of the "web_search_price_per_call" field.
func (u *GroupUpsertBulk) ClearWebSearchPricePerCall() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.ClearWebSearchPricePerCall()
})
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsertBulk) SetClaudeCodeOnly(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
+518
View File
@@ -352,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)
@@ -468,6 +482,191 @@ 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
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (_u *GroupUpdate) SetWebSearchPricePerCall(v float64) *GroupUpdate {
_u.mutation.ResetWebSearchPricePerCall()
_u.mutation.SetWebSearchPricePerCall(v)
return _u
}
// SetNillableWebSearchPricePerCall sets the "web_search_price_per_call" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableWebSearchPricePerCall(v *float64) *GroupUpdate {
if v != nil {
_u.SetWebSearchPricePerCall(*v)
}
return _u
}
// AddWebSearchPricePerCall adds value to the "web_search_price_per_call" field.
func (_u *GroupUpdate) AddWebSearchPricePerCall(v float64) *GroupUpdate {
_u.mutation.AddWebSearchPricePerCall(v)
return _u
}
// ClearWebSearchPricePerCall clears the value of the "web_search_price_per_call" field.
func (_u *GroupUpdate) ClearWebSearchPricePerCall() *GroupUpdate {
_u.mutation.ClearWebSearchPricePerCall()
return _u
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdate) SetClaudeCodeOnly(v bool) *GroupUpdate {
_u.mutation.SetClaudeCodeOnly(v)
@@ -1116,6 +1315,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)
}
@@ -1152,6 +1354,63 @@ 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.WebSearchPricePerCall(); ok {
_spec.SetField(group.FieldWebSearchPricePerCall, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedWebSearchPricePerCall(); ok {
_spec.AddField(group.FieldWebSearchPricePerCall, field.TypeFloat64, value)
}
if _u.mutation.WebSearchPricePerCallCleared() {
_spec.ClearField(group.FieldWebSearchPricePerCall, field.TypeFloat64)
}
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
}
@@ -1853,6 +2112,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)
@@ -1969,6 +2242,191 @@ 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
}
// SetWebSearchPricePerCall sets the "web_search_price_per_call" field.
func (_u *GroupUpdateOne) SetWebSearchPricePerCall(v float64) *GroupUpdateOne {
_u.mutation.ResetWebSearchPricePerCall()
_u.mutation.SetWebSearchPricePerCall(v)
return _u
}
// SetNillableWebSearchPricePerCall sets the "web_search_price_per_call" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableWebSearchPricePerCall(v *float64) *GroupUpdateOne {
if v != nil {
_u.SetWebSearchPricePerCall(*v)
}
return _u
}
// AddWebSearchPricePerCall adds value to the "web_search_price_per_call" field.
func (_u *GroupUpdateOne) AddWebSearchPricePerCall(v float64) *GroupUpdateOne {
_u.mutation.AddWebSearchPricePerCall(v)
return _u
}
// ClearWebSearchPricePerCall clears the value of the "web_search_price_per_call" field.
func (_u *GroupUpdateOne) ClearWebSearchPricePerCall() *GroupUpdateOne {
_u.mutation.ClearWebSearchPricePerCall()
return _u
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdateOne) SetClaudeCodeOnly(v bool) *GroupUpdateOne {
_u.mutation.SetClaudeCodeOnly(v)
@@ -2647,6 +3105,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)
}
@@ -2683,6 +3144,63 @@ 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.WebSearchPricePerCall(); ok {
_spec.SetField(group.FieldWebSearchPricePerCall, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedWebSearchPricePerCall(); ok {
_spec.AddField(group.FieldWebSearchPricePerCall, field.TypeFloat64, value)
}
if _u.mutation.WebSearchPricePerCallCleared() {
_spec.ClearField(group.FieldWebSearchPricePerCall, 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:
+226 -18
View File
@@ -435,13 +435,195 @@ 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},
{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: "name", Type: field.TypeString, Size: 100},
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini"}},
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini", "grok"}},
{Name: "api_mode", Type: field.TypeString, Size: 32, Default: "chat_completions"},
{Name: "endpoint", Type: field.TypeString, Size: 500},
{Name: "api_key_encrypted", Type: field.TypeString},
@@ -586,7 +768,7 @@ var (
{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: "name", Type: field.TypeString, Size: 100},
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini"}},
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini", "grok"}},
{Name: "api_mode", Type: field.TypeString, Size: 32, Default: "chat_completions"},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 500, Default: ""},
{Name: "extra_headers", Type: field.TypeJSON},
@@ -670,11 +852,20 @@ 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: "web_search_price_per_call", 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},
@@ -725,7 +916,7 @@ var (
{
Name: "group_sort_order",
Unique: false,
Columns: []*schema.Column{GroupsColumns[32]},
Columns: []*schema.Column{GroupsColumns[41]},
},
},
}
@@ -1369,6 +1560,7 @@ var (
{Name: "total_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "actual_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "long_context_billing_applied", Type: field.TypeBool, Default: false},
{Name: "account_rate_multiplier", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "billing_type", Type: field.TypeInt8, Default: 0},
{Name: "stream", Type: field.TypeBool, Default: false},
@@ -1382,6 +1574,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},
@@ -1398,31 +1593,31 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "usage_logs_api_keys_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[41]},
RefColumns: []*schema.Column{APIKeysColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_accounts_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[42]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_groups_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[43]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "usage_logs_users_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[44]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_user_subscriptions_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[45]},
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
OnDelete: schema.SetNull,
},
@@ -1431,32 +1626,32 @@ var (
{
Name: "usagelog_user_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[44]},
},
{
Name: "usagelog_api_key_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[41]},
},
{
Name: "usagelog_account_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[42]},
},
{
Name: "usagelog_group_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[43]},
},
{
Name: "usagelog_subscription_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[45]},
},
{
Name: "usagelog_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[40]},
},
{
Name: "usagelog_model",
@@ -1476,17 +1671,17 @@ var (
{
Name: "usagelog_user_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[44], UsageLogsColumns[40]},
},
{
Name: "usagelog_api_key_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[41], UsageLogsColumns[40]},
},
{
Name: "usagelog_group_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[43], UsageLogsColumns[40]},
},
},
}
@@ -1500,6 +1695,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: ""},
@@ -1526,7 +1722,7 @@ var (
{
Name: "user_status",
Unique: false,
Columns: []*schema.Column{UsersColumns[9]},
Columns: []*schema.Column{UsersColumns[10]},
},
{
Name: "user_deleted_at",
@@ -1799,6 +1995,9 @@ var (
AnnouncementReadsTable,
AuthIdentitiesTable,
AuthIdentityChannelsTable,
BatchImageEventsTable,
BatchImageItemsTable,
BatchImageJobsTable,
ChannelMonitorsTable,
ChannelMonitorDailyRollupsTable,
ChannelMonitorHistoriesTable,
@@ -1862,6 +2061,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",
+6756 -75
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)
+241 -36
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
@@ -846,62 +1015,82 @@ func init() {
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[16].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[17].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[21].Descriptor()
groupDescClaudeCodeOnly := groupFields[30].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[25].Descriptor()
groupDescModelRoutingEnabled := groupFields[34].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[26].Descriptor()
groupDescMcpXMLInject := groupFields[35].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[27].Descriptor()
groupDescSupportedModelScopes := groupFields[36].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[28].Descriptor()
groupDescSortOrder := groupFields[37].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[29].Descriptor()
groupDescAllowMessagesDispatch := groupFields[38].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[30].Descriptor()
groupDescRequireOauthOnly := groupFields[39].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[31].Descriptor()
groupDescRequirePrivacySet := groupFields[40].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[32].Descriptor()
groupDescDefaultMappedModel := groupFields[41].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[33].Descriptor()
groupDescMessagesDispatchModelConfig := groupFields[42].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[34].Descriptor()
groupDescModelsListConfig := groupFields[43].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[35].Descriptor()
groupDescRpmLimit := groupFields[44].Descriptor()
// group.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
group.DefaultRpmLimit = groupDescRpmLimit.Default.(int)
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
@@ -1751,48 +1940,60 @@ func init() {
usagelogDescRateMultiplier := usagelogFields[25].Descriptor()
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64)
// usagelogDescLongContextBillingApplied is the schema descriptor for long_context_billing_applied field.
usagelogDescLongContextBillingApplied := usagelogFields[26].Descriptor()
// usagelog.DefaultLongContextBillingApplied holds the default value on creation for the long_context_billing_applied field.
usagelog.DefaultLongContextBillingApplied = usagelogDescLongContextBillingApplied.Default.(bool)
// usagelogDescBillingType is the schema descriptor for billing_type field.
usagelogDescBillingType := usagelogFields[27].Descriptor()
usagelogDescBillingType := usagelogFields[28].Descriptor()
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
usagelog.DefaultBillingType = usagelogDescBillingType.Default.(int8)
// usagelogDescStream is the schema descriptor for stream field.
usagelogDescStream := usagelogFields[28].Descriptor()
usagelogDescStream := usagelogFields[29].Descriptor()
// usagelog.DefaultStream holds the default value on creation for the stream field.
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
// usagelogDescUserAgent is the schema descriptor for user_agent field.
usagelogDescUserAgent := usagelogFields[31].Descriptor()
usagelogDescUserAgent := usagelogFields[32].Descriptor()
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
usagelog.UserAgentValidator = usagelogDescUserAgent.Validators[0].(func(string) error)
// usagelogDescIPAddress is the schema descriptor for ip_address field.
usagelogDescIPAddress := usagelogFields[32].Descriptor()
usagelogDescIPAddress := usagelogFields[33].Descriptor()
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
usagelog.IPAddressValidator = usagelogDescIPAddress.Validators[0].(func(string) error)
// usagelogDescImageCount is the schema descriptor for image_count field.
usagelogDescImageCount := usagelogFields[33].Descriptor()
usagelogDescImageCount := usagelogFields[34].Descriptor()
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
usagelog.DefaultImageCount = usagelogDescImageCount.Default.(int)
// usagelogDescImageSize is the schema descriptor for image_size field.
usagelogDescImageSize := usagelogFields[34].Descriptor()
usagelogDescImageSize := usagelogFields[35].Descriptor()
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
// usagelogDescImageInputSize is the schema descriptor for image_input_size field.
usagelogDescImageInputSize := usagelogFields[35].Descriptor()
usagelogDescImageInputSize := usagelogFields[36].Descriptor()
// usagelog.ImageInputSizeValidator is a validator for the "image_input_size" field. It is called by the builders before save.
usagelog.ImageInputSizeValidator = usagelogDescImageInputSize.Validators[0].(func(string) error)
// usagelogDescImageOutputSize is the schema descriptor for image_output_size field.
usagelogDescImageOutputSize := usagelogFields[36].Descriptor()
usagelogDescImageOutputSize := usagelogFields[37].Descriptor()
// usagelog.ImageOutputSizeValidator is a validator for the "image_output_size" field. It is called by the builders before save.
usagelog.ImageOutputSizeValidator = usagelogDescImageOutputSize.Validators[0].(func(string) error)
// usagelogDescImageSizeSource is the schema descriptor for image_size_source field.
usagelogDescImageSizeSource := usagelogFields[37].Descriptor()
usagelogDescImageSizeSource := usagelogFields[38].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[40].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[41].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[43].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[44].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()
@@ -1860,54 +2061,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()
+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"),
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ func (ChannelMonitor) Fields() []ent.Field {
NotEmpty().
MaxLen(100),
field.Enum("provider").
Values("openai", "anthropic", "gemini"),
Values("openai", "anthropic", "gemini", "grok"),
field.String("api_mode").
Default("chat_completions").
MaxLen(32).
@@ -39,7 +39,7 @@ func (ChannelMonitorRequestTemplate) Fields() []ent.Field {
NotEmpty().
MaxLen(100),
field.Enum("provider").
Values("openai", "anthropic", "gemini"),
Values("openai", "anthropic", "gemini", "grok"),
field.String("api_mode").
Default("chat_completions").
MaxLen(32).
+35
View File
@@ -93,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 表示共享分组有效倍率"),
@@ -112,6 +115,38 @@ 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)"}),
field.Float("web_search_price_per_call").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Comment("Codex alpha/search 网页搜索单次价格(USD/次);nil 表示使用默认价 0.01(官方 $10/1000 次)"),
// Claude Code 客户端限制 (added by migration 029)
field.Bool("claude_code_only").
+17
View File
@@ -100,6 +100,9 @@ func (UsageLog) Fields() []ent.Field {
field.Float("rate_multiplier").
Default(1).
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
field.Bool("long_context_billing_applied").
Default(false).
Comment("Whether long-context pricing changed token prices for this request"),
// account_rate_multiplier: 账号计费倍率快照(NULL 表示按 1.0 处理)
field.Float("account_rate_multiplier").
@@ -149,6 +152,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").
+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)
+53 -3
View File
@@ -75,6 +75,8 @@ type UsageLog struct {
ActualCost float64 `json:"actual_cost,omitempty"`
// RateMultiplier holds the value of the "rate_multiplier" field.
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
// Whether long-context pricing changed token prices for this request
LongContextBillingApplied bool `json:"long_context_billing_applied,omitempty"`
// AccountRateMultiplier holds the value of the "account_rate_multiplier" field.
AccountRateMultiplier *float64 `json:"account_rate_multiplier,omitempty"`
// BillingType holds the value of the "billing_type" field.
@@ -101,6 +103,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.
@@ -190,13 +198,13 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case usagelog.FieldImageSizeBreakdown:
values[i] = new([]byte)
case usagelog.FieldStream, usagelog.FieldCacheTTLOverridden:
case usagelog.FieldLongContextBillingApplied, usagelog.FieldStream, usagelog.FieldCacheTTLOverridden:
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)
@@ -385,6 +393,12 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.RateMultiplier = value.Float64
}
case usagelog.FieldLongContextBillingApplied:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field long_context_billing_applied", values[i])
} else if value.Valid {
_m.LongContextBillingApplied = value.Bool
}
case usagelog.FieldAccountRateMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field account_rate_multiplier", values[i])
@@ -474,6 +488,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])
@@ -641,6 +675,9 @@ func (_m *UsageLog) String() string {
builder.WriteString("rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
builder.WriteString(", ")
builder.WriteString("long_context_billing_applied=")
builder.WriteString(fmt.Sprintf("%v", _m.LongContextBillingApplied))
builder.WriteString(", ")
if v := _m.AccountRateMultiplier; v != nil {
builder.WriteString("account_rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", *v))
@@ -698,6 +735,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(", ")
+38
View File
@@ -66,6 +66,8 @@ const (
FieldActualCost = "actual_cost"
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
FieldRateMultiplier = "rate_multiplier"
// FieldLongContextBillingApplied holds the string denoting the long_context_billing_applied field in the database.
FieldLongContextBillingApplied = "long_context_billing_applied"
// FieldAccountRateMultiplier holds the string denoting the account_rate_multiplier field in the database.
FieldAccountRateMultiplier = "account_rate_multiplier"
// FieldBillingType holds the string denoting the billing_type field in the database.
@@ -92,6 +94,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.
@@ -174,6 +182,7 @@ var Columns = []string{
FieldTotalCost,
FieldActualCost,
FieldRateMultiplier,
FieldLongContextBillingApplied,
FieldAccountRateMultiplier,
FieldBillingType,
FieldStream,
@@ -187,6 +196,9 @@ var Columns = []string{
FieldImageOutputSize,
FieldImageSizeSource,
FieldImageSizeBreakdown,
FieldVideoCount,
FieldVideoResolution,
FieldVideoDurationSeconds,
FieldCacheTTLOverridden,
FieldCreatedAt,
}
@@ -242,6 +254,8 @@ var (
DefaultActualCost float64
// DefaultRateMultiplier holds the default value on creation for the "rate_multiplier" field.
DefaultRateMultiplier float64
// DefaultLongContextBillingApplied holds the default value on creation for the "long_context_billing_applied" field.
DefaultLongContextBillingApplied bool
// DefaultBillingType holds the default value on creation for the "billing_type" field.
DefaultBillingType int8
// DefaultStream holds the default value on creation for the "stream" field.
@@ -260,6 +274,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.
@@ -404,6 +422,11 @@ func ByRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
}
// ByLongContextBillingApplied orders the results by the long_context_billing_applied field.
func ByLongContextBillingApplied(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLongContextBillingApplied, opts...).ToFunc()
}
// ByAccountRateMultiplier orders the results by the account_rate_multiplier field.
func ByAccountRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAccountRateMultiplier, opts...).ToFunc()
@@ -464,6 +487,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()
+195
View File
@@ -185,6 +185,11 @@ func RateMultiplier(v float64) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldRateMultiplier, v))
}
// LongContextBillingApplied applies equality check predicate on the "long_context_billing_applied" field. It's identical to LongContextBillingAppliedEQ.
func LongContextBillingApplied(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldLongContextBillingApplied, v))
}
// AccountRateMultiplier applies equality check predicate on the "account_rate_multiplier" field. It's identical to AccountRateMultiplierEQ.
func AccountRateMultiplier(v float64) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldAccountRateMultiplier, v))
@@ -245,6 +250,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))
@@ -1450,6 +1470,16 @@ func RateMultiplierLTE(v float64) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldRateMultiplier, v))
}
// LongContextBillingAppliedEQ applies the EQ predicate on the "long_context_billing_applied" field.
func LongContextBillingAppliedEQ(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldLongContextBillingApplied, v))
}
// LongContextBillingAppliedNEQ applies the NEQ predicate on the "long_context_billing_applied" field.
func LongContextBillingAppliedNEQ(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldLongContextBillingApplied, v))
}
// AccountRateMultiplierEQ applies the EQ predicate on the "account_rate_multiplier" field.
func AccountRateMultiplierEQ(v float64) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldAccountRateMultiplier, v))
@@ -2150,6 +2180,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))
+331
View File
@@ -351,6 +351,20 @@ func (_c *UsageLogCreate) SetNillableRateMultiplier(v *float64) *UsageLogCreate
return _c
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (_c *UsageLogCreate) SetLongContextBillingApplied(v bool) *UsageLogCreate {
_c.mutation.SetLongContextBillingApplied(v)
return _c
}
// SetNillableLongContextBillingApplied sets the "long_context_billing_applied" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableLongContextBillingApplied(v *bool) *UsageLogCreate {
if v != nil {
_c.SetLongContextBillingApplied(*v)
}
return _c
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (_c *UsageLogCreate) SetAccountRateMultiplier(v float64) *UsageLogCreate {
_c.mutation.SetAccountRateMultiplier(v)
@@ -525,6 +539,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)
@@ -665,6 +721,10 @@ func (_c *UsageLogCreate) defaults() {
v := usagelog.DefaultRateMultiplier
_c.mutation.SetRateMultiplier(v)
}
if _, ok := _c.mutation.LongContextBillingApplied(); !ok {
v := usagelog.DefaultLongContextBillingApplied
_c.mutation.SetLongContextBillingApplied(v)
}
if _, ok := _c.mutation.BillingType(); !ok {
v := usagelog.DefaultBillingType
_c.mutation.SetBillingType(v)
@@ -677,6 +737,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)
@@ -778,6 +842,9 @@ func (_c *UsageLogCreate) check() error {
if _, ok := _c.mutation.RateMultiplier(); !ok {
return &ValidationError{Name: "rate_multiplier", err: errors.New(`ent: missing required field "UsageLog.rate_multiplier"`)}
}
if _, ok := _c.mutation.LongContextBillingApplied(); !ok {
return &ValidationError{Name: "long_context_billing_applied", err: errors.New(`ent: missing required field "UsageLog.long_context_billing_applied"`)}
}
if _, ok := _c.mutation.BillingType(); !ok {
return &ValidationError{Name: "billing_type", err: errors.New(`ent: missing required field "UsageLog.billing_type"`)}
}
@@ -817,6 +884,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"`)}
}
@@ -943,6 +1018,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
_spec.SetField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
_node.RateMultiplier = value
}
if value, ok := _c.mutation.LongContextBillingApplied(); ok {
_spec.SetField(usagelog.FieldLongContextBillingApplied, field.TypeBool, value)
_node.LongContextBillingApplied = value
}
if value, ok := _c.mutation.AccountRateMultiplier(); ok {
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
_node.AccountRateMultiplier = &value
@@ -995,6 +1074,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
@@ -1584,6 +1675,18 @@ func (u *UsageLogUpsert) AddRateMultiplier(v float64) *UsageLogUpsert {
return u
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (u *UsageLogUpsert) SetLongContextBillingApplied(v bool) *UsageLogUpsert {
u.Set(usagelog.FieldLongContextBillingApplied, v)
return u
}
// UpdateLongContextBillingApplied sets the "long_context_billing_applied" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateLongContextBillingApplied() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldLongContextBillingApplied)
return u
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (u *UsageLogUpsert) SetAccountRateMultiplier(v float64) *UsageLogUpsert {
u.Set(usagelog.FieldAccountRateMultiplier, v)
@@ -1830,6 +1933,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)
@@ -2405,6 +2568,20 @@ func (u *UsageLogUpsertOne) UpdateRateMultiplier() *UsageLogUpsertOne {
})
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (u *UsageLogUpsertOne) SetLongContextBillingApplied(v bool) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetLongContextBillingApplied(v)
})
}
// UpdateLongContextBillingApplied sets the "long_context_billing_applied" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateLongContextBillingApplied() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateLongContextBillingApplied()
})
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (u *UsageLogUpsertOne) SetAccountRateMultiplier(v float64) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
@@ -2692,6 +2869,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) {
@@ -3435,6 +3682,20 @@ func (u *UsageLogUpsertBulk) UpdateRateMultiplier() *UsageLogUpsertBulk {
})
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (u *UsageLogUpsertBulk) SetLongContextBillingApplied(v bool) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetLongContextBillingApplied(v)
})
}
// UpdateLongContextBillingApplied sets the "long_context_billing_applied" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateLongContextBillingApplied() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateLongContextBillingApplied()
})
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (u *UsageLogUpsertBulk) SetAccountRateMultiplier(v float64) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
@@ -3722,6 +3983,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) {
+222
View File
@@ -542,6 +542,20 @@ func (_u *UsageLogUpdate) AddRateMultiplier(v float64) *UsageLogUpdate {
return _u
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (_u *UsageLogUpdate) SetLongContextBillingApplied(v bool) *UsageLogUpdate {
_u.mutation.SetLongContextBillingApplied(v)
return _u
}
// SetNillableLongContextBillingApplied sets the "long_context_billing_applied" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableLongContextBillingApplied(v *bool) *UsageLogUpdate {
if v != nil {
_u.SetLongContextBillingApplied(*v)
}
return _u
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (_u *UsageLogUpdate) SetAccountRateMultiplier(v float64) *UsageLogUpdate {
_u.mutation.ResetAccountRateMultiplier()
@@ -811,6 +825,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 +1061,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"`)
}
@@ -1126,6 +1213,9 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.LongContextBillingApplied(); ok {
_spec.SetField(usagelog.FieldLongContextBillingApplied, field.TypeBool, value)
}
if value, ok := _u.mutation.AccountRateMultiplier(); ok {
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
}
@@ -1210,6 +1300,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)
}
@@ -1888,6 +1999,20 @@ func (_u *UsageLogUpdateOne) AddRateMultiplier(v float64) *UsageLogUpdateOne {
return _u
}
// SetLongContextBillingApplied sets the "long_context_billing_applied" field.
func (_u *UsageLogUpdateOne) SetLongContextBillingApplied(v bool) *UsageLogUpdateOne {
_u.mutation.SetLongContextBillingApplied(v)
return _u
}
// SetNillableLongContextBillingApplied sets the "long_context_billing_applied" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableLongContextBillingApplied(v *bool) *UsageLogUpdateOne {
if v != nil {
_u.SetLongContextBillingApplied(*v)
}
return _u
}
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
func (_u *UsageLogUpdateOne) SetAccountRateMultiplier(v float64) *UsageLogUpdateOne {
_u.mutation.ResetAccountRateMultiplier()
@@ -2157,6 +2282,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 +2531,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"`)
}
@@ -2502,6 +2700,9 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.LongContextBillingApplied(); ok {
_spec.SetField(usagelog.FieldLongContextBillingApplied, field.TypeBool, value)
}
if value, ok := _u.mutation.AccountRateMultiplier(); ok {
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
}
@@ -2586,6 +2787,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 -14
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,11 +38,13 @@ 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
golang.org/x/crypto v0.51.0
golang.org/x/image v0.39.0
golang.org/x/mod v0.35.0
golang.org/x/net v0.55.0
golang.org/x/sync v0.20.0
golang.org/x/term v0.43.0
@@ -59,20 +60,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
@@ -156,7 +158,6 @@ require (
github.com/testcontainers/testcontainers-go v0.40.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tiktoken-go/tokenizer v0.8.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@@ -176,7 +177,6 @@ require (
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/tools v0.44.0 // indirect
+20 -36
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=
@@ -166,8 +166,6 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
@@ -419,20 +417,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=
@@ -446,22 +438,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=
+183 -1
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"`
@@ -550,6 +601,7 @@ type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"` // debug/release
EnableServerTiming bool `mapstructure:"enable_server_timing"` // Admin UI Server-Timing response header
FrontendURL string `mapstructure:"frontend_url"` // 前端基础 URL,用于生成邮件中的外部链接
ReadHeaderTimeout int `mapstructure:"read_header_timeout"` // 读取请求头超时(秒)
IdleTimeout int `mapstructure:"idle_timeout"` // 空闲连接超时(秒)
@@ -767,6 +819,8 @@ type GatewayConfig struct {
ImageStreamDataIntervalTimeout int `mapstructure:"image_stream_data_interval_timeout"`
// ImageStreamKeepaliveInterval: 图片流式 keepalive 间隔(秒),0表示禁用
ImageStreamKeepaliveInterval int `mapstructure:"image_stream_keepalive_interval"`
// ImageNonstreamKeepaliveInterval: 图片非流式 JSON keepalive 间隔(秒),0表示禁用
ImageNonstreamKeepaliveInterval int `mapstructure:"image_nonstream_keepalive_interval"`
// MaxLineSize: 上游 SSE 单行最大字节数(0使用默认值)
MaxLineSize int `mapstructure:"max_line_size"`
@@ -872,6 +926,12 @@ type GatewayOpenAIWSConfig struct {
ModeRouterV2Enabled bool `mapstructure:"mode_router_v2_enabled"`
// IngressModeDefault: ingress 默认模式(off/ctx_pool/passthrough/http_bridge)
IngressModeDefault string `mapstructure:"ingress_mode_default"`
// IngressInterTurnIdleTimeoutSeconds bounds the time a client may remain idle
// between completed ingress turns. Zero disables this protection.
IngressInterTurnIdleTimeoutSeconds int `mapstructure:"ingress_inter_turn_idle_timeout_seconds"`
// MaxIngressConnectionsPerAPIKey bounds live client WebSocket ingress sessions
// per API key across all instances. Zero disables this protection.
MaxIngressConnectionsPerAPIKey int `mapstructure:"max_ingress_connections_per_api_key"`
// Enabled: 全局总开关(默认 true)
Enabled bool `mapstructure:"enabled"`
// OAuthEnabled: 是否允许 OpenAI OAuth 账号使用 WS
@@ -1402,6 +1462,9 @@ func load(allowMissingJWTSecret bool) (*Config, error) {
// 环境变量支持
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := viper.BindEnv("server.enable_server_timing", "ENABLE_SERVER_TIMING"); err != nil {
return nil, fmt.Errorf("bind ENABLE_SERVER_TIMING: %w", err)
}
// 默认值
setDefaults()
@@ -1557,6 +1620,7 @@ func setDefaults() {
viper.SetDefault("server.host", "0.0.0.0")
viper.SetDefault("server.port", 8080)
viper.SetDefault("server.mode", "release")
viper.SetDefault("server.enable_server_timing", false)
viper.SetDefault("server.frontend_url", "")
viper.SetDefault("server.read_header_timeout", 30) // 30秒读取请求头
viper.SetDefault("server.idle_timeout", 120) // 120秒空闲超时
@@ -1732,6 +1796,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)
@@ -1847,6 +1958,8 @@ func setDefaults() {
viper.SetDefault("gateway.openai_ws.enabled", true)
viper.SetDefault("gateway.openai_ws.mode_router_v2_enabled", false)
viper.SetDefault("gateway.openai_ws.ingress_mode_default", "ctx_pool")
viper.SetDefault("gateway.openai_ws.ingress_inter_turn_idle_timeout_seconds", 300)
viper.SetDefault("gateway.openai_ws.max_ingress_connections_per_api_key", 64)
viper.SetDefault("gateway.openai_ws.oauth_enabled", true)
viper.SetDefault("gateway.openai_ws.apikey_enabled", true)
viper.SetDefault("gateway.openai_ws.force_http", false)
@@ -1926,6 +2039,7 @@ func setDefaults() {
viper.SetDefault("gateway.stream_keepalive_interval", 10)
viper.SetDefault("gateway.image_stream_data_interval_timeout", 900)
viper.SetDefault("gateway.image_stream_keepalive_interval", 10)
viper.SetDefault("gateway.image_nonstream_keepalive_interval", 0)
viper.SetDefault("gateway.max_line_size", 500*1024*1024)
viper.SetDefault("gateway.scheduling.sticky_session_max_waiting", 3)
viper.SetDefault("gateway.scheduling.sticky_session_wait_timeout", 120*time.Second)
@@ -2333,6 +2447,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")
@@ -2557,6 +2726,13 @@ func (c *Config) Validate() error {
(c.Gateway.ImageStreamKeepaliveInterval < 5 || c.Gateway.ImageStreamKeepaliveInterval > 60) {
return fmt.Errorf("gateway.image_stream_keepalive_interval must be 0 or between 5-60 seconds")
}
if c.Gateway.ImageNonstreamKeepaliveInterval < 0 {
return fmt.Errorf("gateway.image_nonstream_keepalive_interval must be non-negative")
}
if c.Gateway.ImageNonstreamKeepaliveInterval != 0 &&
(c.Gateway.ImageNonstreamKeepaliveInterval < 5 || c.Gateway.ImageNonstreamKeepaliveInterval > 60) {
return fmt.Errorf("gateway.image_nonstream_keepalive_interval must be 0 or between 5-60 seconds")
}
// 兼容旧键 sticky_previous_response_ttl_seconds
if c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds <= 0 && c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds > 0 {
c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds = c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds
@@ -2564,6 +2740,12 @@ func (c *Config) Validate() error {
if c.Gateway.OpenAIWS.MaxConnsPerAccount <= 0 {
return fmt.Errorf("gateway.openai_ws.max_conns_per_account must be positive")
}
if c.Gateway.OpenAIWS.IngressInterTurnIdleTimeoutSeconds < 0 {
return fmt.Errorf("gateway.openai_ws.ingress_inter_turn_idle_timeout_seconds must be non-negative")
}
if c.Gateway.OpenAIWS.MaxIngressConnectionsPerAPIKey < 0 {
return fmt.Errorf("gateway.openai_ws.max_ingress_connections_per_api_key must be non-negative")
}
if c.Gateway.OpenAIWS.MinIdlePerAccount < 0 {
return fmt.Errorf("gateway.openai_ws.min_idle_per_account must be non-negative")
}
+63
View File
@@ -17,6 +17,23 @@ func resetViperWithJWTSecret(t *testing.T) {
t.Setenv("JWT_SECRET", strings.Repeat("x", 32))
}
func TestLoadServerTimingConfig(t *testing.T) {
t.Run("disabled by default", func(t *testing.T) {
resetViperWithJWTSecret(t)
cfg, err := Load()
require.NoError(t, err)
require.False(t, cfg.Server.EnableServerTiming)
})
t.Run("enabled by exact environment variable", func(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("ENABLE_SERVER_TIMING", "true")
cfg, err := Load()
require.NoError(t, err)
require.True(t, cfg.Server.EnableServerTiming)
})
}
func TestLoadForBootstrapAllowsMissingJWTSecret(t *testing.T) {
viper.Reset()
t.Setenv("JWT_SECRET", "")
@@ -182,6 +199,12 @@ func TestLoadDefaultOpenAIWSConfig(t *testing.T) {
if cfg.Gateway.OpenAIWS.IngressModeDefault != "ctx_pool" {
t.Fatalf("Gateway.OpenAIWS.IngressModeDefault = %q, want %q", cfg.Gateway.OpenAIWS.IngressModeDefault, "ctx_pool")
}
if cfg.Gateway.OpenAIWS.IngressInterTurnIdleTimeoutSeconds != 300 {
t.Fatalf("Gateway.OpenAIWS.IngressInterTurnIdleTimeoutSeconds = %d, want 300", cfg.Gateway.OpenAIWS.IngressInterTurnIdleTimeoutSeconds)
}
if cfg.Gateway.OpenAIWS.MaxIngressConnectionsPerAPIKey != 64 {
t.Fatalf("Gateway.OpenAIWS.MaxIngressConnectionsPerAPIKey = %d, want 64", cfg.Gateway.OpenAIWS.MaxIngressConnectionsPerAPIKey)
}
}
func TestLoadDefaultOpenAICompactModel(t *testing.T) {
@@ -236,6 +259,15 @@ func TestLoadOpenAIResponseHeaderTimeoutFromEnv(t *testing.T) {
require.Equal(t, 1800, cfg.Gateway.OpenAIResponseHeaderTimeout)
}
func TestLoadImageNonstreamKeepaliveFromEnv(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("GATEWAY_IMAGE_NONSTREAM_KEEPALIVE_INTERVAL", "15")
cfg, err := Load()
require.NoError(t, err)
require.Equal(t, 15, cfg.Gateway.ImageNonstreamKeepaliveInterval)
}
func TestLoadOpenAIWSStickyTTLCompatibility(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("GATEWAY_OPENAI_WS_STICKY_RESPONSE_ID_TTL_SECONDS", "0")
@@ -270,6 +302,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")
@@ -1398,6 +1438,16 @@ func TestValidateConfigErrors(t *testing.T) {
mutate: func(c *Config) { c.Gateway.ImageStreamKeepaliveInterval = -1 },
wantErr: "gateway.image_stream_keepalive_interval must be non-negative",
},
{
name: "gateway image nonstream keepalive range",
mutate: func(c *Config) { c.Gateway.ImageNonstreamKeepaliveInterval = 4 },
wantErr: "gateway.image_nonstream_keepalive_interval",
},
{
name: "gateway image nonstream keepalive negative",
mutate: func(c *Config) { c.Gateway.ImageNonstreamKeepaliveInterval = -1 },
wantErr: "gateway.image_nonstream_keepalive_interval must be non-negative",
},
{
name: "gateway image stream data interval range",
mutate: func(c *Config) { c.Gateway.ImageStreamDataIntervalTimeout = 30 },
@@ -1632,6 +1682,16 @@ func TestValidateConfig_OpenAIWSRules(t *testing.T) {
mutate: func(c *Config) { c.Gateway.OpenAIWS.MaxConnsPerAccount = 0 },
wantErr: "gateway.openai_ws.max_conns_per_account",
},
{
name: "ingress_inter_turn_idle_timeout_seconds 不能为负数",
mutate: func(c *Config) { c.Gateway.OpenAIWS.IngressInterTurnIdleTimeoutSeconds = -1 },
wantErr: "gateway.openai_ws.ingress_inter_turn_idle_timeout_seconds",
},
{
name: "max_ingress_connections_per_api_key 不能为负数",
mutate: func(c *Config) { c.Gateway.OpenAIWS.MaxIngressConnectionsPerAPIKey = -1 },
wantErr: "gateway.openai_ws.max_ingress_connections_per_api_key",
},
{
name: "min_idle_per_account 不能为负数",
mutate: func(c *Config) { c.Gateway.OpenAIWS.MinIdlePerAccount = -1 },
@@ -1956,6 +2016,9 @@ func TestLoad_DefaultGatewayImageStreamConfig(t *testing.T) {
if cfg.Gateway.ImageStreamKeepaliveInterval != 10 {
t.Fatalf("image_stream_keepalive_interval = %d, want 10", cfg.Gateway.ImageStreamKeepaliveInterval)
}
if cfg.Gateway.ImageNonstreamKeepaliveInterval != 0 {
t.Fatalf("image_nonstream_keepalive_interval = %d, want 0", cfg.Gateway.ImageNonstreamKeepaliveInterval)
}
if cfg.Gateway.ImageConcurrency.Enabled {
t.Fatalf("image_concurrency.enabled = true, want false")
}
@@ -115,6 +115,10 @@ func (h *AccountHandler) ImportCodexSession(c *gin.Context) {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := service.ValidateOpenAILongContextBillingExtra(service.PlatformOpenAI, req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
if req.Concurrency != nil && *req.Concurrency < 0 {
response.BadRequest(c, "concurrency must be >= 0")
return
@@ -630,6 +630,7 @@ func TestImportCodexSessionsAccessTokenOnlySameUserUpdatesExisting(t *testing.T)
"chatgpt_user_id": "user-1",
"access_token": existingToken,
},
Extra: map[string]any{"openai_long_context_billing_enabled": false},
}})
handler := NewAccountHandler(svc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
req := CodexSessionImportRequest{SkipDefaultGroupBind: boolPtr(true)}
@@ -650,6 +651,9 @@ func TestImportCodexSessionsAccessTokenOnlySameUserUpdatesExisting(t *testing.T)
if len(svc.updatedAccounts) != 1 || svc.updatedAccounts[0].id != 10 {
t.Fatalf("updated accounts = %+v, want account 10", svc.updatedAccounts)
}
if got := svc.updatedAccounts[0].input.Extra["openai_long_context_billing_enabled"]; got != false {
t.Fatalf("openai_long_context_billing_enabled = %v, want false", got)
}
}
func TestImportCodexSessionsUpgradesAccessTokenOnlyAccountWithRefreshToken(t *testing.T) {
@@ -460,6 +460,7 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
if created.Platform == service.PlatformAntigravity && created.Type == service.AccountTypeOAuth {
privacyAccounts = append(privacyAccounts, created)
}
h.scheduleGrokImportProbe(created)
result.AccountCreated++
}
@@ -60,6 +60,7 @@ type AccountHandler struct {
sessionLimitCache service.SessionLimitCache
rpmCache service.RPMCache
tokenCacheInvalidator service.TokenCacheInvalidator
grokImportProber grokUsageProber
}
// NewAccountHandler creates a new admin account handler
@@ -485,6 +486,8 @@ func (h *AccountHandler) List(c *gin.Context) {
search = search[:100]
}
lite := parseBoolQueryWithDefault(c.Query("lite"), false)
// 调度分需要跨候选池批量打分并读取负载,默认列表不计算;只有前端列可见时才显式开启。
includeSchedulerScore := parseBoolQueryWithDefault(c.Query("include_scheduler_score"), false)
var groupID int64
if groupIDStr := c.Query("group"); groupIDStr != "" {
@@ -520,7 +523,7 @@ func (h *AccountHandler) List(c *gin.Context) {
var windowCosts map[int64]float64
var activeSessions map[int64]int
var rpmCounts map[int64]int
// 仅当前页存在 OpenAI 账号时才计算调度分数,避免为空结果付出池查询开销。
// 双重门控:用户要看该列,且当前页确实有 OpenAI 账号,才进入昂贵的候选池打分路径。
var schedulerScores map[int64]*AccountSchedulerScore
var schedulerGroupScores map[int64][]AccountSchedulerGroupScore
pageHasOpenAIAccounts := false
@@ -530,7 +533,7 @@ func (h *AccountHandler) List(c *gin.Context) {
break
}
}
if pageHasOpenAIAccounts {
if includeSchedulerScore && pageHasOpenAIAccounts {
schedulerFilterPool := h.listAccountSchedulerScoreFilterPool(c.Request.Context(), platform, accountType, status, search, groupID, privacyMode)
schedulerScores, schedulerGroupScores = h.buildOpenAIAccountSchedulerScores(c.Request.Context(), accounts, schedulerFilterPool)
}
@@ -782,6 +785,10 @@ func (h *AccountHandler) Create(c *gin.Context) {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := service.ValidateOpenAILongContextBillingExtra(req.Platform, req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
if req.RateMultiplier != nil && *req.RateMultiplier < 0 {
response.BadRequest(c, "rate_multiplier must be >= 0")
return
@@ -849,6 +856,7 @@ func (h *AccountHandler) Create(c *gin.Context) {
// OpenAI APIKey 账号创建后异步探测上游 /v1/responses 能力。
// 探测失败不影响账号创建响应。
h.scheduleOpenAIResponsesProbe(createdAccount)
h.scheduleGrokImportProbe(createdAccount)
response.Success(c, result.Data)
}
@@ -1297,6 +1305,10 @@ func (h *AccountHandler) ApplyOAuthCredentials(c *gin.Context) {
response.ErrorFrom(c, infraerrors.BadRequest("NOT_OAUTH", "cannot apply oauth credentials to non-OAuth account"))
return
}
if err := service.ValidateOpenAILongContextBillingExtra(existing.Platform, req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
updatedAccount, err := h.adminService.UpdateAccount(ctx, accountID, &service.UpdateAccountInput{
Type: req.Type,
@@ -1590,6 +1602,12 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
for _, item := range req.Accounts {
if err := service.ValidateOpenAILongContextBillingExtra(item.Platform, item.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
}
executeAdminIdempotentJSON(c, "admin.accounts.batch_create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
success := 0
@@ -1651,6 +1669,7 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
}
// OpenAI APIKey 账号异步探测 /v1/responses 能力。
h.scheduleOpenAIResponsesProbe(account)
h.scheduleGrokImportProbe(account)
success++
results = append(results, gin.H{
"name": item.Name,
@@ -92,7 +92,7 @@ func TestAccountHandlerListReturnsSchedulerScoresPerGroup(t *testing.T) {
}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=20&platform=openai", nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=20&platform=openai&include_scheduler_score=1", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
@@ -147,6 +147,43 @@ func TestAccountHandlerListReturnsSchedulerScoresPerGroup(t *testing.T) {
require.Greater(t, high.SchedulerScores[0].BaseScore, low.SchedulerScores[0].BaseScore)
}
func TestAccountHandlerListSkipsSchedulerScoresByDefault(t *testing.T) {
router, adminSvc := setupAccountListRouter()
now := time.Now().UTC()
adminSvc.accounts = []service.Account{
{
ID: 110,
Name: "openai-account",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 1,
CreatedAt: now,
UpdatedAt: now,
},
}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=20&platform=openai", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Zero(t, adminSvc.schedulerScoreFilterCalls)
require.Zero(t, adminSvc.openAISchedulerScorePoolCalls)
var payload struct {
Data struct {
Items []map[string]any `json:"items"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
require.Len(t, payload.Data.Items, 1)
require.NotContains(t, payload.Data.Items[0], "scheduler_score")
require.NotContains(t, payload.Data.Items[0], "scheduler_scores")
}
func TestAccountHandlerListKeepsSchedulerScoreScopedToFilter(t *testing.T) {
router, adminSvc := setupAccountListRouter()
now := time.Now().UTC()
@@ -188,7 +225,7 @@ func TestAccountHandlerListKeepsSchedulerScoreScopedToFilter(t *testing.T) {
adminSvc.openAISchedulerScorePoolAccounts = []service.Account{visibleAccount, hiddenGroupPeer}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=1&platform=openai", nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=1&platform=openai&include_scheduler_score=1", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
@@ -246,7 +283,7 @@ func TestAccountHandlerListSchedulerScoreIgnoresPagination(t *testing.T) {
adminSvc.accountSchedulerScoreFilterAccounts = []service.Account{visibleAccount, hiddenFilterPeer}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=1&platform=openai", nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?page=1&page_size=1&platform=openai&include_scheduler_score=1", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
@@ -0,0 +1,165 @@
package admin
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
func TestAccountAdminBoundariesRejectMalformedOpenAILongContextBillingValue(t *testing.T) {
const malformedExtra = `"extra":{"openai_long_context_billing_enabled":"true"}`
tests := []struct {
name string
method string
path string
body string
mount func(*gin.Engine, *AccountHandler)
setup func(*stubAdminService)
}{
{
name: "create",
method: http.MethodPost,
path: "/accounts",
body: `{"name":"account","platform":"openai","type":"apikey","credentials":{"api_key":"test"},` + malformedExtra + `}`,
mount: func(router *gin.Engine, handler *AccountHandler) { router.POST("/accounts", handler.Create) },
},
{
name: "update",
method: http.MethodPut,
path: "/accounts/1",
body: `{` + malformedExtra + `}`,
mount: func(router *gin.Engine, handler *AccountHandler) { router.PUT("/accounts/:id", handler.Update) },
setup: func(stub *stubAdminService) {
stub.updateAccountErr = infraerrors.BadRequest("OPENAI_LONG_CONTEXT_BILLING_INVALID", "invalid")
},
},
{
name: "bulk update",
method: http.MethodPost,
path: "/accounts/bulk-update",
body: `{"account_ids":[1],` + malformedExtra + `}`,
mount: func(router *gin.Engine, handler *AccountHandler) {
router.POST("/accounts/bulk-update", handler.BulkUpdate)
},
setup: func(stub *stubAdminService) {
stub.bulkUpdateAccountErr = infraerrors.BadRequest("OPENAI_LONG_CONTEXT_BILLING_INVALID", "invalid")
},
},
{
name: "batch create",
method: http.MethodPost,
path: "/accounts/batch",
body: `{"accounts":[{"name":"account","platform":"openai","type":"apikey","credentials":{"api_key":"test"},` + malformedExtra + `}]}`,
mount: func(router *gin.Engine, handler *AccountHandler) { router.POST("/accounts/batch", handler.BatchCreate) },
},
{
name: "Codex session import",
method: http.MethodPost,
path: "/accounts/import-codex-session",
body: `{"content":"token",` + malformedExtra + `}`,
mount: func(router *gin.Engine, handler *AccountHandler) {
router.POST("/accounts/import-codex-session", handler.ImportCodexSession)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gin.SetMode(gin.TestMode)
stub := newStubAdminService()
if tt.setup != nil {
tt.setup(stub)
}
handler := NewAccountHandler(stub, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
router := gin.New()
tt.mount(router, handler)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(tt.method, tt.path, bytes.NewBufferString(tt.body))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusBadRequest, recorder.Code)
var responseBody struct {
Reason string `json:"reason"`
}
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &responseBody))
require.Equal(t, "OPENAI_LONG_CONTEXT_BILLING_INVALID", responseBody.Reason)
})
}
}
func TestAccountCreateBoundaryDoesNotApplyOpenAIValidationToOtherPlatforms(t *testing.T) {
gin.SetMode(gin.TestMode)
handler := NewAccountHandler(newStubAdminService(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
router := gin.New()
router.POST("/accounts", handler.Create)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/accounts", bytes.NewBufferString(
`{"name":"account","platform":"anthropic","type":"apikey","credentials":{"api_key":"test"},"extra":{"openai_long_context_billing_enabled":"provider-owned"}}`,
))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
}
func TestApplyOAuthCredentialsRejectsMalformedOpenAILongContextBillingBeforeMutation(t *testing.T) {
gin.SetMode(gin.TestMode)
stub := newStubAdminService()
stub.getAccountResult = &service.Account{
ID: 1,
Platform: service.PlatformOpenAI,
Type: service.AccountTypeOAuth,
}
handler := NewAccountHandler(stub, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
router := gin.New()
router.POST("/accounts/:id/apply-oauth-credentials", handler.ApplyOAuthCredentials)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/accounts/1/apply-oauth-credentials", bytes.NewBufferString(
`{"type":"oauth","credentials":{"access_token":"new-token"},"extra":{"openai_long_context_billing_enabled":"true"}}`,
))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusBadRequest, recorder.Code)
var responseBody struct {
Reason string `json:"reason"`
}
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &responseBody))
require.Equal(t, "OPENAI_LONG_CONTEXT_BILLING_INVALID", responseBody.Reason)
require.Zero(t, stub.updateAccountCalls)
require.Zero(t, stub.updateAccountExtraCalls)
}
func TestOpenAIOAuthCodexPATBoundaryRejectsMalformedOpenAILongContextBillingValueBeforeTokenValidation(t *testing.T) {
gin.SetMode(gin.TestMode)
handler := NewOpenAIOAuthHandler(nil, newStubAdminService(), nil)
router := gin.New()
router.Use(gin.Recovery())
router.POST("/openai/create-from-codex-pat", handler.CreateAccountFromCodexPAT)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/openai/create-from-codex-pat", bytes.NewBufferString(
`{"access_token":"token","extra":{"openai_long_context_billing_enabled":1}}`,
))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusBadRequest, recorder.Code)
var responseBody struct {
Reason string `json:"reason"`
}
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &responseBody))
require.Equal(t, "OPENAI_LONG_CONTEXT_BILLING_INVALID", responseBody.Reason)
}
@@ -265,10 +265,12 @@ func TestOpenAIFastPolicySettingsFromDTO_NormalizesServiceTier(t *testing.T) {
ServiceTier: "PRIORITY",
Action: "filter",
Scope: "all",
UserIDs: []int64{42},
}},
}
out := openaiFastPolicySettingsFromDTO(in)
require.Equal(t, service.OpenAIFastTierPriority, out.Rules[0].ServiceTier)
require.Equal(t, []int64{42}, out.Rules[0].UserIDs)
})
t.Run("non-empty values pass through (lowercased)", func(t *testing.T) {
@@ -16,6 +16,8 @@ type stubAdminService struct {
accounts []service.Account
accountSchedulerScoreFilterAccounts []service.Account
openAISchedulerScorePoolAccounts []service.Account
schedulerScoreFilterCalls int
openAISchedulerScorePoolCalls int
proxies []service.Proxy
proxyCounts []service.ProxyWithAccountCount
redeems []service.RedeemCode
@@ -31,6 +33,9 @@ type stubAdminService struct {
createSparkShadowErr error
updateAccountErr error
bulkUpdateAccountErr error
getAccountResult *service.Account
updateAccountCalls int
updateAccountExtraCalls int
checkMixedErr error
lastMixedCheck struct {
accountID int64
@@ -351,6 +356,7 @@ func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int,
}
func (s *stubAdminService) ListAccountsForSchedulerScoreFilter(_ context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, error) {
s.schedulerScoreFilterCalls++
if s.accountSchedulerScoreFilterAccounts != nil {
return s.accountSchedulerScoreFilterAccounts, nil
}
@@ -358,6 +364,7 @@ func (s *stubAdminService) ListAccountsForSchedulerScoreFilter(_ context.Context
}
func (s *stubAdminService) ListOpenAISchedulableAccountsForSchedulerScore(_ context.Context, groupID *int64) ([]service.Account, error) {
s.openAISchedulerScorePoolCalls++
accounts := s.openAISchedulerScorePoolAccounts
if accounts == nil {
accounts = s.accounts
@@ -384,6 +391,9 @@ func (s *stubAdminService) ListOpenAISchedulableAccountsForSchedulerScore(_ cont
}
func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
if s.getAccountResult != nil {
return s.getAccountResult, nil
}
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
return &account, nil
}
@@ -409,6 +419,7 @@ func (s *stubAdminService) CreateAccount(ctx context.Context, input *service.Cre
}
func (s *stubAdminService) UpdateAccount(ctx context.Context, id int64, input *service.UpdateAccountInput) (*service.Account, error) {
s.updateAccountCalls++
if s.updateAccountErr != nil {
return nil, s.updateAccountErr
}
@@ -417,6 +428,7 @@ func (s *stubAdminService) UpdateAccount(ctx context.Context, id int64, input *s
}
func (s *stubAdminService) UpdateAccountExtra(ctx context.Context, id int64, updates map[string]any) error {
s.updateAccountExtraCalls++
return nil
}
@@ -37,11 +37,11 @@ func NewChannelMonitorHandler(monitorService *service.ChannelMonitorService) *Ch
type channelMonitorCreateRequest struct {
Name string `json:"name" binding:"required,max=100"`
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini"`
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini grok"`
APIMode string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
Endpoint string `json:"endpoint" binding:"required,max=500"`
APIKey string `json:"api_key" binding:"required,max=2000"`
PrimaryModel string `json:"primary_model" binding:"required,max=200"`
PrimaryModel string `json:"primary_model" binding:"max=200"`
ExtraModels []string `json:"extra_models"`
GroupName string `json:"group_name" binding:"max=100"`
Enabled *bool `json:"enabled"`
@@ -55,7 +55,7 @@ type channelMonitorCreateRequest struct {
type channelMonitorUpdateRequest struct {
Name *string `json:"name" binding:"omitempty,max=100"`
Provider *string `json:"provider" binding:"omitempty,oneof=openai anthropic gemini"`
Provider *string `json:"provider" binding:"omitempty,oneof=openai anthropic gemini grok"`
APIMode *string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
Endpoint *string `json:"endpoint" binding:"omitempty,max=500"`
APIKey *string `json:"api_key" binding:"omitempty,max=2000"`
@@ -26,7 +26,7 @@ func NewChannelMonitorRequestTemplateHandler(templateService *service.ChannelMon
type channelMonitorTemplateCreateRequest struct {
Name string `json:"name" binding:"required,max=100"`
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini"`
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini grok"`
APIMode string `json:"api_mode" binding:"omitempty,oneof=chat_completions responses"`
Description string `json:"description" binding:"max=500"`
ExtraHeaders map[string]string `json:"extra_headers"`
@@ -657,11 +657,14 @@ func (h *DashboardHandler) GetUserBreakdown(c *gin.Context) {
dim.AccountID = id
}
}
if v := c.Query("request_type"); v != "" {
if rt, err := strconv.ParseInt(v, 10, 16); err == nil {
rtVal := int16(rt)
dim.RequestType = &rtVal
if v := strings.TrimSpace(c.Query("request_type")); v != "" {
parsed, err := service.ParseUsageRequestType(v)
if err != nil {
response.BadRequest(c, err.Error())
return
}
rtVal := int16(parsed)
dim.RequestType = &rtVal
}
if v := c.Query("stream"); v != "" {
if s, err := strconv.ParseBool(v); err == nil {
@@ -675,6 +678,9 @@ func (h *DashboardHandler) GetUserBreakdown(c *gin.Context) {
}
}
// sort_by 由 repo 层 allowlist 校验;非法值静默回退默认排序(actual_cost)。
dim.SortBy = strings.TrimSpace(c.Query("sort_by"))
limit := 50
if v := c.Query("limit"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
@@ -59,7 +59,21 @@ func TestGetUserBreakdown_GroupIDFilter(t *testing.T) {
require.Equal(t, int64(42), repo.capturedDim.GroupID)
require.Empty(t, repo.capturedDim.Model)
require.Empty(t, repo.capturedDim.Endpoint)
require.Equal(t, 50, repo.capturedLimit) // default limit
require.Equal(t, 50, repo.capturedLimit) // default limit
require.Empty(t, repo.capturedDim.SortBy) // no sort_by => empty (repo falls back to default)
}
func TestGetUserBreakdown_SortBy(t *testing.T) {
repo := &userBreakdownRepoCapture{}
router := newUserBreakdownRouter(repo)
req := httptest.NewRequest(http.MethodGet,
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&sort_by=total_tokens", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Equal(t, "total_tokens", repo.capturedDim.SortBy)
}
func TestGetUserBreakdown_ModelFilter(t *testing.T) {
@@ -227,3 +241,42 @@ func TestGetUserBreakdown_NoFilters(t *testing.T) {
require.Empty(t, repo.capturedDim.Model)
require.Empty(t, repo.capturedDim.Endpoint)
}
func TestGetUserBreakdown_RequestTypeStringFilter(t *testing.T) {
cases := []struct {
name string
value string
want int16
}{
{"ws_v2", "ws_v2", int16(service.RequestTypeWSV2)},
{"stream", "stream", int16(service.RequestTypeStream)},
{"sync", "sync", int16(service.RequestTypeSync)},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
repo := &userBreakdownRepoCapture{}
router := newUserBreakdownRouter(repo)
req := httptest.NewRequest(http.MethodGet,
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&request_type="+tc.value, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.NotNil(t, repo.capturedDim.RequestType, "request_type=%s should set filter", tc.value)
require.Equal(t, tc.want, *repo.capturedDim.RequestType)
})
}
}
func TestGetUserBreakdown_InvalidRequestType(t *testing.T) {
repo := &userBreakdownRepoCapture{}
router := newUserBreakdownRouter(repo)
req := httptest.NewRequest(http.MethodGet,
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&request_type=bogus", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusBadRequest, w.Code)
}
@@ -0,0 +1,203 @@
package admin
import (
"context"
"log/slog"
"sync"
"time"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/service"
)
const (
grokImportProbeConcurrency = 3
grokImportProbeTimeout = 25 * time.Second
)
type grokUsageProber interface {
ProbeUsage(ctx context.Context, accountID int64) (*service.GrokQuotaProbeResult, error)
}
type grokImportProbeTask struct {
prober grokUsageProber
accountID int64
}
type grokImportProbeScheduler struct {
mu sync.Mutex
queue []grokImportProbeTask
concurrency int
workers int
maxWorkers int
timeout time.Duration
}
var defaultGrokImportProbeScheduler = newGrokImportProbeScheduler(
grokImportProbeConcurrency,
grokImportProbeTimeout,
)
func newGrokImportProbeScheduler(concurrency int, timeout time.Duration) *grokImportProbeScheduler {
if concurrency <= 0 {
concurrency = 1
}
if timeout <= 0 {
timeout = grokImportProbeTimeout
}
return &grokImportProbeScheduler{
concurrency: concurrency,
timeout: timeout,
}
}
func (s *grokImportProbeScheduler) schedule(prober grokUsageProber, account *service.Account) {
if s == nil || prober == nil || account == nil || account.ID <= 0 {
return
}
if account.Platform != service.PlatformGrok || account.Type != service.AccountTypeOAuth {
return
}
s.mu.Lock()
s.queue = append(s.queue, grokImportProbeTask{prober: prober, accountID: account.ID})
if s.workers < s.concurrency {
s.workers++
if s.workers > s.maxWorkers {
s.maxWorkers = s.workers
}
go s.worker()
}
s.mu.Unlock()
}
func (s *grokImportProbeScheduler) worker() {
for {
task, ok := s.nextTask()
if !ok {
return
}
s.run(task.prober, task.accountID)
}
}
func (s *grokImportProbeScheduler) nextTask() (grokImportProbeTask, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.queue) == 0 {
s.workers--
return grokImportProbeTask{}, false
}
task := s.queue[0]
s.queue[0] = grokImportProbeTask{}
s.queue = s.queue[1:]
if len(s.queue) == 0 {
s.queue = nil
}
return task, true
}
func (s *grokImportProbeScheduler) run(prober grokUsageProber, accountID int64) {
defer func() {
if recovered := recover(); recovered != nil {
slog.Error(
"grok_import_active_probe_panic",
"account_id", accountID,
"recovery_type", panicType(recovered),
)
}
}()
// Queue time is intentionally excluded: every imported account is probed,
// while this timeout only bounds the actual upstream probe execution.
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
defer cancel()
result, err := prober.ProbeUsage(ctx, accountID)
if err != nil {
slog.Warn(
"grok_import_active_probe_failed",
"account_id", accountID,
"status", infraerrors.Code(err),
"reason", infraerrors.Reason(err),
)
return
}
if result == nil {
slog.Warn(
"grok_import_active_probe_failed",
"account_id", accountID,
"reason", "empty_result",
)
return
}
slog.Info(
"grok_import_active_probe_completed",
"account_id", accountID,
"model", result.Model,
"status", result.StatusCode,
"headers_observed", result.HeadersObserved,
)
}
func panicType(value any) string {
switch value.(type) {
case string:
return "string"
case error:
return "error"
default:
return "unknown"
}
}
func (h *AccountHandler) scheduleGrokImportProbe(account *service.Account) {
if h == nil {
return
}
defaultGrokImportProbeScheduler.schedule(h.grokImportProber, account)
}
func (h *GrokOAuthHandler) scheduleGrokImportProbe(account *service.Account) {
if h == nil {
return
}
defaultGrokImportProbeScheduler.schedule(h.importProber, account)
}
// ProvideAccountHandler injects the Grok active prober for production while
// keeping NewAccountHandler convenient for focused unit tests.
func ProvideAccountHandler(
adminService service.AdminService,
oauthService *service.OAuthService,
openaiOAuthService *service.OpenAIOAuthService,
geminiOAuthService *service.GeminiOAuthService,
antigravityOAuthService *service.AntigravityOAuthService,
rateLimitService *service.RateLimitService,
accountUsageService *service.AccountUsageService,
accountTestService *service.AccountTestService,
concurrencyService *service.ConcurrencyService,
crsSyncService *service.CRSSyncService,
sessionLimitCache service.SessionLimitCache,
rpmCache service.RPMCache,
tokenCacheInvalidator service.TokenCacheInvalidator,
grokQuotaService *service.GrokQuotaService,
) *AccountHandler {
handler := NewAccountHandler(
adminService,
oauthService,
openaiOAuthService,
geminiOAuthService,
antigravityOAuthService,
rateLimitService,
accountUsageService,
accountTestService,
concurrencyService,
crsSyncService,
sessionLimitCache,
rpmCache,
tokenCacheInvalidator,
)
handler.grokImportProber = grokQuotaService
return handler
}
@@ -0,0 +1,119 @@
//go:build unit
package admin
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
type grokImportAdminService struct {
*stubAdminService
mu sync.Mutex
nextID int64
}
func newGrokImportAdminService() *grokImportAdminService {
return &grokImportAdminService{
stubAdminService: newStubAdminService(),
nextID: 500,
}
}
func (s *grokImportAdminService) CreateAccount(_ context.Context, input *service.CreateAccountInput) (*service.Account, error) {
s.mu.Lock()
s.nextID++
id := s.nextID
s.mu.Unlock()
return &service.Account{
ID: id,
Name: input.Name,
Platform: input.Platform,
Type: input.Type,
Credentials: input.Credentials,
Extra: input.Extra,
ProxyID: input.ProxyID,
Concurrency: input.Concurrency,
Status: service.StatusActive,
Schedulable: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
}
type grokImportOAuthClientStub struct{}
func (grokImportOAuthClientStub) ExchangeCode(context.Context, string, string, string, string, string) (*xai.TokenResponse, error) {
return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
}
func (grokImportOAuthClientStub) RefreshToken(context.Context, string, string, string) (*xai.TokenResponse, error) {
return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
}
func (grokImportOAuthClientStub) ConvertSSOToBuild(context.Context, string, string) (*xai.TokenResponse, error) {
return &xai.TokenResponse{AccessToken: "access-token", RefreshToken: "refresh-token", ExpiresIn: 3600}, nil
}
func TestGrokSSOBatchImportKeepsCreatedAccountsWhenOneAutomaticProbeFails(t *testing.T) {
gin.SetMode(gin.TestMode)
adminService := newGrokImportAdminService()
oauthService := service.NewGrokOAuthService(nil, grokImportOAuthClientStub{})
defer oauthService.Stop()
prober := newGrokImportProbeStub(3)
prober.failures[502] = infraerrors.New(502, "GROK_TEST_PROBE_FAILED", "sensitive-upstream-body")
handler := NewGrokOAuthHandler(oauthService, adminService, nil)
handler.importProber = prober
router := gin.New()
router.POST("/api/v1/admin/grok/sso-to-oauth", handler.CreateAccountsFromSSO)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(
http.MethodPost,
"/api/v1/admin/grok/sso-to-oauth",
strings.NewReader(`{"sso_tokens":["sso-one","sso-two","sso-three"]}`),
)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"created"`)
require.NotContains(t, recorder.Body.String(), `GROK_TEST_PROBE_FAILED`)
for i := 0; i < 3; i++ {
awaitGrokProbeSignal(t, prober.done)
}
calls, _, _ := prober.snapshot()
require.Equal(t, map[int64]int{501: 1, 502: 1, 503: 1}, calls)
}
func TestAccountCreateWithoutAutomaticGrokProbeServiceStillSucceeds(t *testing.T) {
gin.SetMode(gin.TestMode)
handler := NewAccountHandler(
newGrokImportAdminService(),
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
)
router := gin.New()
router.POST("/api/v1/admin/accounts", handler.Create)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(
http.MethodPost,
"/api/v1/admin/accounts",
strings.NewReader(`{"name":"grok-rt","platform":"grok","type":"oauth","credentials":{"refresh_token":"secret"}}`),
)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
}
@@ -0,0 +1,232 @@
//go:build unit
package admin
import (
"bytes"
"context"
"log/slog"
"sync"
"testing"
"time"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/stretchr/testify/require"
)
type grokImportProbeStub struct {
mu sync.Mutex
calls map[int64]int
failures map[int64]error
active int
maxActive int
deadlineSeen bool
block <-chan struct{}
started chan int64
done chan int64
}
func newGrokImportProbeStub(buffer int) *grokImportProbeStub {
return &grokImportProbeStub{
calls: make(map[int64]int),
failures: make(map[int64]error),
started: make(chan int64, buffer),
done: make(chan int64, buffer),
}
}
func (s *grokImportProbeStub) ProbeUsage(ctx context.Context, accountID int64) (*service.GrokQuotaProbeResult, error) {
_, deadlineSeen := ctx.Deadline()
s.mu.Lock()
s.calls[accountID]++
s.active++
if s.active > s.maxActive {
s.maxActive = s.active
}
s.deadlineSeen = s.deadlineSeen || deadlineSeen
s.mu.Unlock()
s.started <- accountID
var ctxErr error
if s.block != nil {
select {
case <-s.block:
case <-ctx.Done():
ctxErr = ctx.Err()
}
}
s.mu.Lock()
s.active--
failure := s.failures[accountID]
s.mu.Unlock()
s.done <- accountID
if ctxErr != nil {
return nil, ctxErr
}
if failure != nil {
return nil, failure
}
return &service.GrokQuotaProbeResult{
Source: "active_probe",
Model: "grok-4.5",
StatusCode: 200,
ResetSupported: false,
}, nil
}
func (s *grokImportProbeStub) snapshot() (map[int64]int, int, bool) {
s.mu.Lock()
defer s.mu.Unlock()
calls := make(map[int64]int, len(s.calls))
for id, count := range s.calls {
calls[id] = count
}
return calls, s.maxActive, s.deadlineSeen
}
type grokImportProbeSchedulerTestSnapshot struct {
queued int
workers int
maxWorkers int
}
func snapshotGrokImportProbeScheduler(s *grokImportProbeScheduler) grokImportProbeSchedulerTestSnapshot {
if s == nil {
return grokImportProbeSchedulerTestSnapshot{}
}
s.mu.Lock()
defer s.mu.Unlock()
return grokImportProbeSchedulerTestSnapshot{
queued: len(s.queue),
workers: s.workers,
maxWorkers: s.maxWorkers,
}
}
func newGrokOAuthImportAccount(id int64) *service.Account {
return &service.Account{
ID: id,
Platform: service.PlatformGrok,
Type: service.AccountTypeOAuth,
}
}
func awaitGrokProbeSignal(t *testing.T, signals <-chan int64) int64 {
t.Helper()
select {
case id := <-signals:
return id
case <-time.After(time.Second):
t.Fatal("timed out waiting for Grok import probe")
return 0
}
}
func TestGrokImportProbeSchedulerProbesSingleAccountOnce(t *testing.T) {
scheduler := newGrokImportProbeScheduler(1, time.Second)
prober := newGrokImportProbeStub(1)
scheduler.schedule(prober, newGrokOAuthImportAccount(101))
require.Equal(t, int64(101), awaitGrokProbeSignal(t, prober.done))
calls, maxActive, deadlineSeen := prober.snapshot()
require.Equal(t, map[int64]int{101: 1}, calls)
require.Equal(t, 1, maxActive)
require.True(t, deadlineSeen)
require.Eventually(t, func() bool {
snapshot := snapshotGrokImportProbeScheduler(scheduler)
return snapshot.queued == 0 && snapshot.workers == 0
}, time.Second, 10*time.Millisecond)
}
func TestGrokImportProbeSchedulerQueuesBatchWithoutPerTaskGoroutines(t *testing.T) {
const taskCount = 100
release := make(chan struct{})
scheduler := newGrokImportProbeScheduler(3, time.Second)
prober := newGrokImportProbeStub(taskCount)
prober.block = release
prober.failures[150] = infraerrors.New(502, "GROK_TEST_PROBE_FAILED", "sensitive-upstream-body")
for id := int64(101); id < 101+taskCount; id++ {
scheduler.schedule(prober, newGrokOAuthImportAccount(id))
}
for i := 0; i < 3; i++ {
awaitGrokProbeSignal(t, prober.started)
}
snapshot := snapshotGrokImportProbeScheduler(scheduler)
require.Equal(t, 97, snapshot.queued)
require.Equal(t, 3, snapshot.workers)
require.Equal(t, 3, snapshot.maxWorkers)
select {
case id := <-prober.started:
t.Fatalf("probe %d started before a concurrency slot was released", id)
case <-time.After(75 * time.Millisecond):
}
close(release)
for i := 0; i < taskCount; i++ {
awaitGrokProbeSignal(t, prober.done)
}
calls, maxActive, _ := prober.snapshot()
require.Len(t, calls, taskCount)
for id := int64(101); id < 101+taskCount; id++ {
require.Equal(t, 1, calls[id])
}
require.Equal(t, 3, maxActive)
require.Eventually(t, func() bool {
snapshot = snapshotGrokImportProbeScheduler(scheduler)
return snapshot.queued == 0 && snapshot.workers == 0
}, time.Second, 10*time.Millisecond)
require.Equal(t, 3, snapshot.maxWorkers)
}
func TestGrokImportProbeSchedulerTimeoutCancelsProbe(t *testing.T) {
neverRelease := make(chan struct{})
scheduler := newGrokImportProbeScheduler(1, 20*time.Millisecond)
prober := newGrokImportProbeStub(1)
prober.block = neverRelease
scheduler.schedule(prober, newGrokOAuthImportAccount(201))
require.Equal(t, int64(201), awaitGrokProbeSignal(t, prober.done))
calls, _, _ := prober.snapshot()
require.Equal(t, 1, calls[201])
}
func TestGrokImportProbeSchedulerSkipsMissingServiceAndNonGrokAccounts(t *testing.T) {
scheduler := newGrokImportProbeScheduler(1, time.Second)
prober := newGrokImportProbeStub(1)
scheduler.schedule(nil, newGrokOAuthImportAccount(301))
scheduler.schedule(prober, &service.Account{ID: 302, Platform: service.PlatformOpenAI, Type: service.AccountTypeOAuth})
scheduler.schedule(prober, &service.Account{ID: 303, Platform: service.PlatformGrok, Type: service.AccountTypeAPIKey})
select {
case id := <-prober.started:
t.Fatalf("unexpected probe for account %d", id)
case <-time.After(50 * time.Millisecond):
}
calls, _, _ := prober.snapshot()
require.Empty(t, calls)
}
func TestGrokImportProbeFailureLogDoesNotIncludeErrorMessage(t *testing.T) {
var logs bytes.Buffer
previousLogger := slog.Default()
slog.SetDefault(slog.New(slog.NewTextHandler(&logs, nil)))
defer slog.SetDefault(previousLogger)
scheduler := newGrokImportProbeScheduler(1, time.Second)
prober := newGrokImportProbeStub(1)
prober.failures[401] = infraerrors.New(502, "GROK_TEST_PROBE_FAILED", "refresh-token-secret")
scheduler.schedule(prober, newGrokOAuthImportAccount(401))
awaitGrokProbeSignal(t, prober.done)
require.Eventually(t, func() bool {
return bytes.Contains(logs.Bytes(), []byte("grok_import_active_probe_failed"))
}, time.Second, 10*time.Millisecond)
require.Contains(t, logs.String(), "GROK_TEST_PROBE_FAILED")
require.NotContains(t, logs.String(), "refresh-token-secret")
}
@@ -1,20 +1,28 @@
package admin
import (
"context"
"fmt"
"log/slog"
"strconv"
"strings"
"sync"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
const grokSSOImportConcurrency = 3
type GrokOAuthHandler struct {
grokOAuthService *service.GrokOAuthService
adminService service.AdminService
quotaService *service.GrokQuotaService
importProber grokUsageProber
}
func NewGrokOAuthHandler(
@@ -26,6 +34,7 @@ func NewGrokOAuthHandler(
grokOAuthService: grokOAuthService,
adminService: adminService,
quotaService: quotaService,
importProber: quotaService,
}
}
@@ -202,9 +211,243 @@ func (h *GrokOAuthHandler) CreateAccountFromOAuth(c *gin.Context) {
response.ErrorFrom(c, err)
return
}
h.scheduleGrokImportProbe(account)
response.Success(c, dto.AccountFromService(account))
}
type GrokSSOToOAuthRequest struct {
SSOTokens []string `json:"sso_tokens"`
SSOToken string `json:"sso_token"`
Name string `json:"name"`
Notes *string `json:"notes"`
ProxyID *int64 `json:"proxy_id"`
GroupIDs []int64 `json:"group_ids"`
Credentials map[string]any `json:"credentials"`
Extra map[string]any `json:"extra"`
Concurrency int `json:"concurrency"`
LoadFactor *int `json:"load_factor"`
Priority int `json:"priority"`
RateMultiplier *float64 `json:"rate_multiplier"`
ExpiresAt *int64 `json:"expires_at"`
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
}
type GrokSSOToOAuthItemResult struct {
Index int `json:"index"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Account *dto.Account `json:"account,omitempty"`
Error string `json:"error,omitempty"`
}
type GrokSSOToOAuthResponse struct {
Created []GrokSSOToOAuthItemResult `json:"created"`
Failed []GrokSSOToOAuthItemResult `json:"failed"`
}
type grokSSOImportJob struct {
index int
token string
}
type grokSSOImportWorkerResult struct {
created bool
item GrokSSOToOAuthItemResult
}
func (h *GrokOAuthHandler) CreateAccountsFromSSO(c *gin.Context) {
var req GrokSSOToOAuthRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
tokens := normalizeSSOImportTokens(req.SSOTokens, req.SSOToken)
if len(tokens) == 0 {
response.BadRequest(c, "sso_tokens is required")
return
}
ctx := c.Request.Context()
workerCount := grokSSOImportConcurrency
if len(tokens) < workerCount {
workerCount = len(tokens)
}
jobs := make(chan grokSSOImportJob)
items := make([]grokSSOImportWorkerResult, len(tokens))
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
items[job.index] = h.safeCreateAccountFromSSOToken(ctx, req, job.token, job.index+1, len(tokens))
}
}()
}
for i, token := range tokens {
jobs <- grokSSOImportJob{index: i, token: token}
}
close(jobs)
wg.Wait()
result := GrokSSOToOAuthResponse{
Created: make([]GrokSSOToOAuthItemResult, 0, len(tokens)),
Failed: make([]GrokSSOToOAuthItemResult, 0),
}
for _, item := range items {
if item.created {
result.Created = append(result.Created, item.item)
} else {
result.Failed = append(result.Failed, item.item)
}
}
response.Success(c, result)
}
func (h *GrokOAuthHandler) safeCreateAccountFromSSOToken(ctx context.Context, req GrokSSOToOAuthRequest, token string, index, total int) (result grokSSOImportWorkerResult) {
defer func() {
if recovered := recover(); recovered != nil {
slog.Error("grok_sso_import_worker_panic", "index", index, "recover", recovered)
result = grokSSOImportWorkerResult{
item: GrokSSOToOAuthItemResult{
Index: index,
Error: fmt.Sprintf("internal worker panic: %v", recovered),
},
}
}
}()
return h.createAccountFromSSOToken(ctx, req, token, index, total)
}
func (h *GrokOAuthHandler) createAccountFromSSOToken(ctx context.Context, req GrokSSOToOAuthRequest, token string, index, total int) grokSSOImportWorkerResult {
tokenInfo, err := h.grokOAuthService.ConvertFromSSO(ctx, token, req.ProxyID)
if err != nil {
return grokSSOImportWorkerResult{item: GrokSSOToOAuthItemResult{Index: index, Error: grokSSOImportErrorMessage(err)}}
}
credentials := h.grokOAuthService.BuildAccountCredentials(tokenInfo)
credentials = service.MergeCredentials(cloneGrokSSOMap(req.Credentials), credentials)
name := grokSSOImportAccountName(req.Name, tokenInfo, index, total)
expiresAt, autoPauseOnExpired := grokSSOImportExpiry(req.ExpiresAt, req.AutoPauseOnExpired, tokenInfo)
account, err := h.adminService.CreateAccount(ctx, &service.CreateAccountInput{
Name: name,
Notes: req.Notes,
Platform: service.PlatformGrok,
Type: service.AccountTypeOAuth,
Credentials: credentials,
Extra: cloneGrokSSOMap(req.Extra),
ProxyID: req.ProxyID,
Concurrency: req.Concurrency,
LoadFactor: req.LoadFactor,
Priority: req.Priority,
RateMultiplier: req.RateMultiplier,
GroupIDs: append([]int64(nil), req.GroupIDs...),
ExpiresAt: expiresAt,
AutoPauseOnExpired: autoPauseOnExpired,
})
if err != nil {
return grokSSOImportWorkerResult{item: GrokSSOToOAuthItemResult{Index: index, Name: name, Email: tokenInfo.Email, Error: grokSSOImportErrorMessage(err)}}
}
h.scheduleGrokImportProbe(account)
return grokSSOImportWorkerResult{
created: true,
item: GrokSSOToOAuthItemResult{
Index: index,
Name: name,
Email: tokenInfo.Email,
Account: dto.AccountFromService(account),
},
}
}
func grokSSOImportExpiry(requestExpiresAt *int64, requestAutoPause *bool, tokenInfo *service.GrokTokenInfo) (*int64, *bool) {
if tokenInfo == nil || strings.TrimSpace(tokenInfo.RefreshToken) != "" || tokenInfo.ExpiresAt <= 0 {
return requestExpiresAt, requestAutoPause
}
expiresAt := tokenInfo.ExpiresAt
if requestExpiresAt != nil && *requestExpiresAt > 0 && *requestExpiresAt < expiresAt {
expiresAt = *requestExpiresAt
}
autoPause := true
return &expiresAt, &autoPause
}
func cloneGrokSSOMap(source map[string]any) map[string]any {
if source == nil {
return nil
}
clone := make(map[string]any, len(source))
for key, value := range source {
clone[key] = cloneGrokSSOValue(value)
}
return clone
}
func cloneGrokSSOValue(value any) any {
switch v := value.(type) {
case map[string]any:
return cloneGrokSSOMap(v)
case []any:
clone := make([]any, len(v))
for i, item := range v {
clone[i] = cloneGrokSSOValue(item)
}
return clone
default:
return value
}
}
func normalizeSSOImportTokens(tokens []string, single string) []string {
items := make([]string, 0, len(tokens)+1)
if strings.TrimSpace(single) != "" {
items = append(items, single)
}
items = append(items, tokens...)
seen := make(map[string]struct{}, len(items))
result := make([]string, 0, len(items))
for _, item := range items {
parts := strings.Split(strings.NewReplacer(",", "\n", "\r", "\n").Replace(item), "\n")
for _, token := range parts {
if token = xai.NormalizeSSOToken(token); token == "" {
continue
}
if _, ok := seen[token]; ok {
continue
}
seen[token] = struct{}{}
result = append(result, token)
}
}
return result
}
func grokSSOImportAccountName(base string, tokenInfo *service.GrokTokenInfo, index, total int) string {
base = strings.TrimSpace(base)
if base == "" && tokenInfo != nil {
base = strings.TrimSpace(tokenInfo.Email)
}
if base == "" {
base = "Grok OAuth Account"
}
if total > 1 {
return base + " #" + strconv.Itoa(index)
}
return base
}
func grokSSOImportErrorMessage(err error) string {
status := infraerrors.FromError(err)
if status == nil {
return ""
}
if status.Reason != "" {
return status.Reason + ": " + status.Message
}
return status.Message
}
func (h *GrokOAuthHandler) QueryQuota(c *gin.Context) {
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
@@ -215,7 +458,7 @@ func (h *GrokOAuthHandler) QueryQuota(c *gin.Context) {
response.BadRequest(c, "grok quota service is not enabled")
return
}
result, err := h.quotaService.ProbeUsage(c.Request.Context(), accountID)
result, err := h.quotaService.QueryQuota(c.Request.Context(), accountID)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -8,6 +8,7 @@ import (
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
@@ -41,17 +42,35 @@ func (r *grokQuotaHandlerAccountRepo) UpdateExtra(_ context.Context, id int64, u
}
type grokQuotaHandlerUpstream struct {
resp *http.Response
lastReq *http.Request
lastBody []byte
mu sync.Mutex
requests []*http.Request
bodies [][]byte
}
func (u *grokQuotaHandlerUpstream) Do(req *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
u.lastReq = req
var body []byte
if req.Body != nil {
u.lastBody, _ = io.ReadAll(req.Body)
body, _ = io.ReadAll(req.Body)
}
return u.resp, nil
u.mu.Lock()
u.requests = append(u.requests, req)
u.bodies = append(u.bodies, body)
u.mu.Unlock()
if req.URL.Path == "/v1/responses" {
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{
"X-Ratelimit-Limit-Requests": []string{"10"},
"X-Ratelimit-Remaining-Requests": []string{"8"},
},
Body: io.NopCloser(strings.NewReader(`{"id":"resp_probe"}`)),
}, nil
}
payload := `{"config":{"billingPeriodStart":"2026-07-01T00:00:00Z","billingPeriodEnd":"2026-08-01T00:00:00Z"}}`
if req.URL.RawQuery == "format=credits" {
payload = `{"config":{"currentPeriod":{"type":"WEEKLY","start":"2026-07-09T03:25:00Z","end":"2026-07-16T03:25:00Z"}}}`
}
return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(payload))}, nil
}
func (u *grokQuotaHandlerUpstream) DoWithTLS(
@@ -77,14 +96,7 @@ func TestGrokOAuthHandlerQueryQuotaProbesUpstream(t *testing.T) {
"expires_at": time.Now().Add(time.Hour).UTC().Format(time.RFC3339),
},
}}
upstream := &grokQuotaHandlerUpstream{resp: &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{
"X-Ratelimit-Limit-Requests": []string{"10"},
"X-Ratelimit-Remaining-Requests": []string{"8"},
},
Body: io.NopCloser(strings.NewReader(`{"id":"resp_probe"}`)),
}}
upstream := &grokQuotaHandlerUpstream{}
quotaService := service.NewGrokQuotaService(repo, nil, service.NewGrokTokenProvider(repo, nil), upstream)
handler := NewGrokOAuthHandler(nil, nil, quotaService)
@@ -95,12 +107,23 @@ func TestGrokOAuthHandlerQueryQuotaProbesUpstream(t *testing.T) {
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Contains(t, rec.Body.String(), `"source":"active_probe"`)
require.Contains(t, rec.Body.String(), `"source":"hybrid_probe"`)
require.Contains(t, rec.Body.String(), `"billing":`)
require.Contains(t, rec.Body.String(), `"snapshot":`)
require.Contains(t, rec.Body.String(), `"headers_observed":true`)
require.NotContains(t, rec.Body.String(), "access-token")
require.Equal(t, xai.DefaultBaseURL+"/responses", upstream.lastReq.URL.String())
require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization"))
require.Contains(t, string(upstream.lastBody), `"store":false`)
upstream.mu.Lock()
requests := append([]*http.Request(nil), upstream.requests...)
bodies := append([][]byte(nil), upstream.bodies...)
upstream.mu.Unlock()
require.Len(t, requests, 3)
for i, upstreamReq := range requests {
require.Equal(t, "Bearer access-token", upstreamReq.Header.Get("Authorization"))
if upstreamReq.URL.String() == xai.DefaultCLIBaseURL+"/responses" {
require.Contains(t, string(bodies[i]), `"model":"grok-4.5"`)
require.Contains(t, string(bodies[i]), `"store":false`)
}
}
require.NotNil(t, repo.updates[42])
}
@@ -145,3 +168,51 @@ func TestGrokOAuthHandlerRuntimeSanityDoesNotExposeSecrets(t *testing.T) {
require.NotContains(t, rec.Body.String(), "secret")
require.NotContains(t, rec.Body.String(), "client-secret-like-value")
}
func TestGrokSSOImportExpiryUsesTokenExpiryWithoutRefreshToken(t *testing.T) {
tokenExpiry := time.Now().Add(6 * time.Hour).Unix()
expiresAt, autoPause := grokSSOImportExpiry(nil, nil, &service.GrokTokenInfo{
ExpiresAt: tokenExpiry,
})
require.NotNil(t, expiresAt)
require.Equal(t, tokenExpiry, *expiresAt)
require.NotNil(t, autoPause)
require.True(t, *autoPause)
}
func TestGrokSSOImportExpiryUsesEarlierRequestedExpiryWithoutRefreshToken(t *testing.T) {
requestedExpiry := time.Now().Add(2 * time.Hour).Unix()
tokenExpiry := time.Now().Add(6 * time.Hour).Unix()
requestedAutoPause := false
expiresAt, autoPause := grokSSOImportExpiry(&requestedExpiry, &requestedAutoPause, &service.GrokTokenInfo{
ExpiresAt: tokenExpiry,
})
require.NotNil(t, expiresAt)
require.Equal(t, requestedExpiry, *expiresAt)
require.NotNil(t, autoPause)
require.True(t, *autoPause)
}
func TestGrokSSOImportExpiryPreservesRequestSettingsWithRefreshToken(t *testing.T) {
requestedExpiry := time.Now().Add(2 * time.Hour).Unix()
requestedAutoPause := false
expiresAt, autoPause := grokSSOImportExpiry(&requestedExpiry, &requestedAutoPause, &service.GrokTokenInfo{
RefreshToken: "refresh-token",
ExpiresAt: time.Now().Add(6 * time.Hour).Unix(),
})
require.Same(t, &requestedExpiry, expiresAt)
require.Same(t, &requestedAutoPause, autoPause)
}
func TestGrokSSOImportWorkerRecoversPanic(t *testing.T) {
h := &GrokOAuthHandler{}
result := h.safeCreateAccountFromSSOToken(context.Background(), GrokSSOToOAuthRequest{}, "token", 2, 3)
// Without a service, createAccountFromSSOToken would panic on nil service access.
// Recovery must convert that into a failed item and keep the worker alive.
require.False(t, result.created)
require.Equal(t, 2, result.item.Index)
require.Contains(t, result.item.Error, "internal worker panic")
}
@@ -93,8 +93,13 @@ type CreateGroupRequest struct {
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
AllowImageGeneration bool `json:"allow_image_generation"`
AllowBatchImageGeneration bool `json:"allow_batch_image_generation"`
ImageRateIndependent bool `json:"image_rate_independent"`
ImageRateMultiplier *float64 `json:"image_rate_multiplier"`
BatchImageDiscountMultiplier *float64 `json:"batch_image_discount_multiplier"`
BatchImageHoldMultiplier *float64 `json:"batch_image_hold_multiplier"`
VideoRateIndependent bool `json:"video_rate_independent"`
VideoRateMultiplier *float64 `json:"video_rate_multiplier"`
PeakRateEnabled bool `json:"peak_rate_enabled"`
PeakStart string `json:"peak_start"`
PeakEnd string `json:"peak_end"`
@@ -102,6 +107,10 @@ type CreateGroupRequest struct {
ImagePrice1K *float64 `json:"image_price_1k"`
ImagePrice2K *float64 `json:"image_price_2k"`
ImagePrice4K *float64 `json:"image_price_4k"`
VideoPrice480P *float64 `json:"video_price_480p"`
VideoPrice720P *float64 `json:"video_price_720p"`
VideoPrice1080P *float64 `json:"video_price_1080p"`
WebSearchPricePerCall *float64 `json:"web_search_price_per_call"`
ClaudeCodeOnly bool `json:"claude_code_only"`
FallbackGroupID *int64 `json:"fallback_group_id"`
FallbackGroupIDOnInvalidRequest *int64 `json:"fallback_group_id_on_invalid_request"`
@@ -138,8 +147,13 @@ type UpdateGroupRequest struct {
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
AllowImageGeneration *bool `json:"allow_image_generation"`
AllowBatchImageGeneration *bool `json:"allow_batch_image_generation"`
ImageRateIndependent *bool `json:"image_rate_independent"`
ImageRateMultiplier *float64 `json:"image_rate_multiplier"`
BatchImageDiscountMultiplier *float64 `json:"batch_image_discount_multiplier"`
BatchImageHoldMultiplier *float64 `json:"batch_image_hold_multiplier"`
VideoRateIndependent *bool `json:"video_rate_independent"`
VideoRateMultiplier *float64 `json:"video_rate_multiplier"`
PeakRateEnabled *bool `json:"peak_rate_enabled"`
PeakStart *string `json:"peak_start"`
PeakEnd *string `json:"peak_end"`
@@ -147,6 +161,10 @@ type UpdateGroupRequest struct {
ImagePrice1K *float64 `json:"image_price_1k"`
ImagePrice2K *float64 `json:"image_price_2k"`
ImagePrice4K *float64 `json:"image_price_4k"`
VideoPrice480P *float64 `json:"video_price_480p"`
VideoPrice720P *float64 `json:"video_price_720p"`
VideoPrice1080P *float64 `json:"video_price_1080p"`
WebSearchPricePerCall *float64 `json:"web_search_price_per_call"`
ClaudeCodeOnly *bool `json:"claude_code_only"`
FallbackGroupID *int64 `json:"fallback_group_id"`
FallbackGroupIDOnInvalidRequest *int64 `json:"fallback_group_id_on_invalid_request"`
@@ -301,8 +319,13 @@ func (h *GroupHandler) Create(c *gin.Context) {
WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(),
MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(),
AllowImageGeneration: req.AllowImageGeneration,
AllowBatchImageGeneration: req.AllowBatchImageGeneration,
ImageRateIndependent: req.ImageRateIndependent,
ImageRateMultiplier: req.ImageRateMultiplier,
BatchImageDiscountMultiplier: req.BatchImageDiscountMultiplier,
BatchImageHoldMultiplier: req.BatchImageHoldMultiplier,
VideoRateIndependent: req.VideoRateIndependent,
VideoRateMultiplier: req.VideoRateMultiplier,
PeakRateEnabled: req.PeakRateEnabled,
PeakStart: req.PeakStart,
PeakEnd: req.PeakEnd,
@@ -310,6 +333,10 @@ func (h *GroupHandler) Create(c *gin.Context) {
ImagePrice1K: req.ImagePrice1K,
ImagePrice2K: req.ImagePrice2K,
ImagePrice4K: req.ImagePrice4K,
VideoPrice480P: req.VideoPrice480P,
VideoPrice720P: req.VideoPrice720P,
VideoPrice1080P: req.VideoPrice1080P,
WebSearchPricePerCall: req.WebSearchPricePerCall,
ClaudeCodeOnly: req.ClaudeCodeOnly,
FallbackGroupID: req.FallbackGroupID,
FallbackGroupIDOnInvalidRequest: req.FallbackGroupIDOnInvalidRequest,
@@ -361,8 +388,13 @@ func (h *GroupHandler) Update(c *gin.Context) {
WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(),
MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(),
AllowImageGeneration: req.AllowImageGeneration,
AllowBatchImageGeneration: req.AllowBatchImageGeneration,
ImageRateIndependent: req.ImageRateIndependent,
ImageRateMultiplier: req.ImageRateMultiplier,
BatchImageDiscountMultiplier: req.BatchImageDiscountMultiplier,
BatchImageHoldMultiplier: req.BatchImageHoldMultiplier,
VideoRateIndependent: req.VideoRateIndependent,
VideoRateMultiplier: req.VideoRateMultiplier,
PeakRateEnabled: req.PeakRateEnabled,
PeakStart: req.PeakStart,
PeakEnd: req.PeakEnd,
@@ -370,6 +402,10 @@ func (h *GroupHandler) Update(c *gin.Context) {
ImagePrice1K: req.ImagePrice1K,
ImagePrice2K: req.ImagePrice2K,
ImagePrice4K: req.ImagePrice4K,
VideoPrice480P: req.VideoPrice480P,
VideoPrice720P: req.VideoPrice720P,
VideoPrice1080P: req.VideoPrice1080P,
WebSearchPricePerCall: req.WebSearchPricePerCall,
ClaudeCodeOnly: req.ClaudeCodeOnly,
FallbackGroupID: req.FallbackGroupID,
FallbackGroupIDOnInvalidRequest: req.FallbackGroupIDOnInvalidRequest,
@@ -304,6 +304,10 @@ func (h *OpenAIOAuthHandler) CreateAccountFromCodexPAT(c *gin.Context) {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := service.ValidateOpenAILongContextBillingExtra(service.PlatformOpenAI, req.Extra); err != nil {
response.ErrorFrom(c, err)
return
}
if req.Concurrency != nil && *req.Concurrency < 0 {
response.BadRequest(c, "concurrency must be >= 0")
return
@@ -15,6 +15,7 @@ import (
type opsSystemLogCleanupRequest struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Host string `json:"host"`
Level string `json:"level"`
Component string `json:"component"`
@@ -56,6 +57,7 @@ func (h *OpsHandler) ListSystemLogs(c *gin.Context) {
PageSize: pageSize,
StartTime: &start,
EndTime: &end,
Host: strings.TrimSpace(c.Query("host")),
Level: strings.TrimSpace(c.Query("level")),
Component: strings.TrimSpace(c.Query("component")),
RequestID: strings.TrimSpace(c.Query("request_id")),
@@ -153,6 +155,7 @@ func (h *OpsHandler) CleanupSystemLogs(c *gin.Context) {
filter := &service.OpsSystemLogCleanupFilter{
StartTime: start,
EndTime: end,
Host: strings.TrimSpace(req.Host),
Level: strings.TrimSpace(req.Level),
Component: strings.TrimSpace(req.Component),
RequestID: strings.TrimSpace(req.RequestID),
@@ -2,6 +2,7 @@ package admin
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -19,6 +20,26 @@ type responseEnvelope struct {
Data json.RawMessage `json:"data"`
}
type opsSystemLogCaptureRepo struct {
service.OpsRepository
listFilter *service.OpsSystemLogFilter
cleanupFilter *service.OpsSystemLogCleanupFilter
}
func (r *opsSystemLogCaptureRepo) ListSystemLogs(_ context.Context, filter *service.OpsSystemLogFilter) (*service.OpsSystemLogList, error) {
r.listFilter = filter
return &service.OpsSystemLogList{Logs: []*service.OpsSystemLog{}, Page: filter.Page, PageSize: filter.PageSize}, nil
}
func (r *opsSystemLogCaptureRepo) DeleteSystemLogs(_ context.Context, filter *service.OpsSystemLogCleanupFilter) (int64, error) {
r.cleanupFilter = filter
return 1, nil
}
func (r *opsSystemLogCaptureRepo) InsertSystemLogCleanupAudit(_ context.Context, _ *service.OpsSystemLogCleanupAudit) error {
return nil
}
func newOpsSystemLogTestRouter(handler *OpsHandler, withUser bool) *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
@@ -121,6 +142,23 @@ func TestOpsSystemLogHandler_ListSuccess(t *testing.T) {
}
}
func TestOpsSystemLogHandler_ListAcceptsHost(t *testing.T) {
repo := &opsSystemLogCaptureRepo{}
svc := service.NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
h := NewOpsHandler(svc)
r := newOpsSystemLogTestRouter(h, false)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/logs?host=api-node-1", nil)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status=%d, want 200", w.Code)
}
if repo.listFilter == nil || repo.listFilter.Host != "api-node-1" {
t.Fatalf("host filter = %+v, want api-node-1", repo.listFilter)
}
}
func TestOpsSystemLogHandler_CleanupUnauthorized(t *testing.T) {
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
h := NewOpsHandler(svc)
@@ -205,6 +243,24 @@ func TestOpsSystemLogHandler_CleanupAcceptsAPIKeyID(t *testing.T) {
}
}
func TestOpsSystemLogHandler_CleanupAcceptsHost(t *testing.T) {
repo := &opsSystemLogCaptureRepo{}
svc := service.NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
h := NewOpsHandler(svc)
r := newOpsSystemLogTestRouter(h, true)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"host":"api-node-1"}`))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status=%d, want 200", w.Code)
}
if repo.cleanupFilter == nil || repo.cleanupFilter.Host != "api-node-1" {
t.Fatalf("host filter = %+v, want api-node-1", repo.cleanupFilter)
}
}
func TestOpsSystemLogHandler_CleanupInvalidAPIKeyID(t *testing.T) {
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
h := NewOpsHandler(svc)
@@ -16,6 +16,7 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
servermiddleware "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
@@ -323,7 +324,7 @@ func (h *OpsHandler) QPSWSHandler(c *gin.Context) {
// If realtime monitoring is disabled, prefer a successful WS upgrade followed by a clean close
// with a deterministic close code. This prevents clients from spinning on 404/1006 reconnect loops.
if !h.opsService.IsRealtimeMonitoringEnabled(c.Request.Context()) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
conn, err := upgrader.Upgrade(c.Writer, c.Request, servermiddleware.ServerTimingResponseHeader(c))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "ops realtime monitoring is disabled"})
return
@@ -358,7 +359,7 @@ func (h *OpsHandler) QPSWSHandler(c *gin.Context) {
defer releaseOpsWSIPSlot(clientIP)
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
conn, err := upgrader.Upgrade(c.Writer, c.Request, servermiddleware.ServerTimingResponseHeader(c))
if err != nil {
logger.LegacyPrintf("handler.admin.ops_ws", "[OpsWS] upgrade failed: %v", err)
return
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,758 @@
package admin
import (
"log/slog"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) {
if before == nil || after == nil {
return
}
changed := diffSettings(before, after, beforeAuthSourceDefaults, afterAuthSourceDefaults, req)
if len(changed) == 0 {
return
}
subject, _ := middleware.GetAuthSubjectFromContext(c)
role, _ := middleware.GetUserRoleFromContext(c)
slog.Info("settings updated",
"audit", true,
"user_id", subject.UserID,
"role", role,
"changed", changed,
)
}
func diffSettings(before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) []string {
changed := make([]string, 0, 20)
if before.RegistrationEnabled != after.RegistrationEnabled {
changed = append(changed, "registration_enabled")
}
if before.EmailVerifyEnabled != after.EmailVerifyEnabled {
changed = append(changed, "email_verify_enabled")
}
if !equalStringSlice(before.RegistrationEmailSuffixWhitelist, after.RegistrationEmailSuffixWhitelist) {
changed = append(changed, "registration_email_suffix_whitelist")
}
if before.PromoCodeEnabled != after.PromoCodeEnabled {
changed = append(changed, "promo_code_enabled")
}
if before.InvitationCodeEnabled != after.InvitationCodeEnabled {
changed = append(changed, "invitation_code_enabled")
}
if before.PasswordResetEnabled != after.PasswordResetEnabled {
changed = append(changed, "password_reset_enabled")
}
if before.FrontendURL != after.FrontendURL {
changed = append(changed, "frontend_url")
}
if before.TotpEnabled != after.TotpEnabled {
changed = append(changed, "totp_enabled")
}
if before.LoginAgreementEnabled != after.LoginAgreementEnabled {
changed = append(changed, "login_agreement_enabled")
}
if before.LoginAgreementMode != after.LoginAgreementMode {
changed = append(changed, "login_agreement_mode")
}
if before.LoginAgreementUpdatedAt != after.LoginAgreementUpdatedAt {
changed = append(changed, "login_agreement_updated_at")
}
if !equalLoginAgreementDocuments(before.LoginAgreementDocuments, after.LoginAgreementDocuments) {
changed = append(changed, "login_agreement_documents")
}
if before.SMTPHost != after.SMTPHost {
changed = append(changed, "smtp_host")
}
if before.SMTPPort != after.SMTPPort {
changed = append(changed, "smtp_port")
}
if before.SMTPUsername != after.SMTPUsername {
changed = append(changed, "smtp_username")
}
if req.SMTPPassword != "" {
changed = append(changed, "smtp_password")
}
if before.SMTPFrom != after.SMTPFrom {
changed = append(changed, "smtp_from_email")
}
if before.SMTPFromName != after.SMTPFromName {
changed = append(changed, "smtp_from_name")
}
if before.SMTPUseTLS != after.SMTPUseTLS {
changed = append(changed, "smtp_use_tls")
}
if before.TurnstileEnabled != after.TurnstileEnabled {
changed = append(changed, "turnstile_enabled")
}
if before.TurnstileSiteKey != after.TurnstileSiteKey {
changed = append(changed, "turnstile_site_key")
}
if req.TurnstileSecretKey != "" {
changed = append(changed, "turnstile_secret_key")
}
if before.APIKeyACLTrustForwardedIP != after.APIKeyACLTrustForwardedIP {
changed = append(changed, "api_key_acl_trust_forwarded_ip")
}
if before.LinuxDoConnectEnabled != after.LinuxDoConnectEnabled {
changed = append(changed, "linuxdo_connect_enabled")
}
if before.LinuxDoConnectClientID != after.LinuxDoConnectClientID {
changed = append(changed, "linuxdo_connect_client_id")
}
if req.LinuxDoConnectClientSecret != "" {
changed = append(changed, "linuxdo_connect_client_secret")
}
if before.LinuxDoConnectRedirectURL != after.LinuxDoConnectRedirectURL {
changed = append(changed, "linuxdo_connect_redirect_url")
}
if before.DingTalkConnectEnabled != after.DingTalkConnectEnabled {
changed = append(changed, "dingtalk_connect_enabled")
}
if before.DingTalkConnectClientID != after.DingTalkConnectClientID {
changed = append(changed, "dingtalk_connect_client_id")
}
if req.DingTalkConnectClientSecret != "" {
changed = append(changed, "dingtalk_connect_client_secret")
}
if before.DingTalkConnectRedirectURL != after.DingTalkConnectRedirectURL {
changed = append(changed, "dingtalk_connect_redirect_url")
}
if before.DingTalkConnectCorpRestrictionPolicy != after.DingTalkConnectCorpRestrictionPolicy {
changed = append(changed, "dingtalk_connect_corp_restriction_policy")
}
if before.DingTalkConnectInternalCorpID != after.DingTalkConnectInternalCorpID {
changed = append(changed, "dingtalk_connect_internal_corp_id")
}
if before.DingTalkConnectBypassRegistration != after.DingTalkConnectBypassRegistration {
changed = append(changed, "dingtalk_connect_bypass_registration")
}
if before.DingTalkConnectSyncCorpEmail != after.DingTalkConnectSyncCorpEmail {
changed = append(changed, "dingtalk_connect_sync_corp_email")
}
if before.DingTalkConnectSyncDisplayName != after.DingTalkConnectSyncDisplayName {
changed = append(changed, "dingtalk_connect_sync_display_name")
}
if before.DingTalkConnectSyncDept != after.DingTalkConnectSyncDept {
changed = append(changed, "dingtalk_connect_sync_dept")
}
if before.DingTalkConnectSyncCorpEmailAttrKey != after.DingTalkConnectSyncCorpEmailAttrKey {
changed = append(changed, "dingtalk_connect_sync_corp_email_attr_key")
}
if before.DingTalkConnectSyncDisplayNameAttrKey != after.DingTalkConnectSyncDisplayNameAttrKey {
changed = append(changed, "dingtalk_connect_sync_display_name_attr_key")
}
if before.DingTalkConnectSyncDeptAttrKey != after.DingTalkConnectSyncDeptAttrKey {
changed = append(changed, "dingtalk_connect_sync_dept_attr_key")
}
if before.WeChatConnectEnabled != after.WeChatConnectEnabled {
changed = append(changed, "wechat_connect_enabled")
}
if before.WeChatConnectAppID != after.WeChatConnectAppID {
changed = append(changed, "wechat_connect_app_id")
}
if req.WeChatConnectAppSecret != "" {
changed = append(changed, "wechat_connect_app_secret")
}
if before.WeChatConnectOpenAppID != after.WeChatConnectOpenAppID {
changed = append(changed, "wechat_connect_open_app_id")
}
if req.WeChatConnectOpenAppSecret != "" {
changed = append(changed, "wechat_connect_open_app_secret")
}
if before.WeChatConnectMPAppID != after.WeChatConnectMPAppID {
changed = append(changed, "wechat_connect_mp_app_id")
}
if req.WeChatConnectMPAppSecret != "" {
changed = append(changed, "wechat_connect_mp_app_secret")
}
if before.WeChatConnectMobileAppID != after.WeChatConnectMobileAppID {
changed = append(changed, "wechat_connect_mobile_app_id")
}
if req.WeChatConnectMobileAppSecret != "" {
changed = append(changed, "wechat_connect_mobile_app_secret")
}
if before.WeChatConnectOpenEnabled != after.WeChatConnectOpenEnabled {
changed = append(changed, "wechat_connect_open_enabled")
}
if before.WeChatConnectMPEnabled != after.WeChatConnectMPEnabled {
changed = append(changed, "wechat_connect_mp_enabled")
}
if before.WeChatConnectMobileEnabled != after.WeChatConnectMobileEnabled {
changed = append(changed, "wechat_connect_mobile_enabled")
}
if before.WeChatConnectMode != after.WeChatConnectMode {
changed = append(changed, "wechat_connect_mode")
}
if before.WeChatConnectScopes != after.WeChatConnectScopes {
changed = append(changed, "wechat_connect_scopes")
}
if before.WeChatConnectRedirectURL != after.WeChatConnectRedirectURL {
changed = append(changed, "wechat_connect_redirect_url")
}
if before.WeChatConnectFrontendRedirectURL != after.WeChatConnectFrontendRedirectURL {
changed = append(changed, "wechat_connect_frontend_redirect_url")
}
if before.OIDCConnectEnabled != after.OIDCConnectEnabled {
changed = append(changed, "oidc_connect_enabled")
}
if before.OIDCConnectProviderName != after.OIDCConnectProviderName {
changed = append(changed, "oidc_connect_provider_name")
}
if before.OIDCConnectClientID != after.OIDCConnectClientID {
changed = append(changed, "oidc_connect_client_id")
}
if req.OIDCConnectClientSecret != "" {
changed = append(changed, "oidc_connect_client_secret")
}
if before.OIDCConnectIssuerURL != after.OIDCConnectIssuerURL {
changed = append(changed, "oidc_connect_issuer_url")
}
if before.OIDCConnectDiscoveryURL != after.OIDCConnectDiscoveryURL {
changed = append(changed, "oidc_connect_discovery_url")
}
if before.OIDCConnectAuthorizeURL != after.OIDCConnectAuthorizeURL {
changed = append(changed, "oidc_connect_authorize_url")
}
if before.OIDCConnectTokenURL != after.OIDCConnectTokenURL {
changed = append(changed, "oidc_connect_token_url")
}
if before.OIDCConnectUserInfoURL != after.OIDCConnectUserInfoURL {
changed = append(changed, "oidc_connect_userinfo_url")
}
if before.OIDCConnectJWKSURL != after.OIDCConnectJWKSURL {
changed = append(changed, "oidc_connect_jwks_url")
}
if before.OIDCConnectScopes != after.OIDCConnectScopes {
changed = append(changed, "oidc_connect_scopes")
}
if before.OIDCConnectRedirectURL != after.OIDCConnectRedirectURL {
changed = append(changed, "oidc_connect_redirect_url")
}
if before.OIDCConnectFrontendRedirectURL != after.OIDCConnectFrontendRedirectURL {
changed = append(changed, "oidc_connect_frontend_redirect_url")
}
if before.OIDCConnectTokenAuthMethod != after.OIDCConnectTokenAuthMethod {
changed = append(changed, "oidc_connect_token_auth_method")
}
if before.OIDCConnectUsePKCE != after.OIDCConnectUsePKCE {
changed = append(changed, "oidc_connect_use_pkce")
}
if before.OIDCConnectValidateIDToken != after.OIDCConnectValidateIDToken {
changed = append(changed, "oidc_connect_validate_id_token")
}
if before.OIDCConnectAllowedSigningAlgs != after.OIDCConnectAllowedSigningAlgs {
changed = append(changed, "oidc_connect_allowed_signing_algs")
}
if before.OIDCConnectClockSkewSeconds != after.OIDCConnectClockSkewSeconds {
changed = append(changed, "oidc_connect_clock_skew_seconds")
}
if before.OIDCConnectRequireEmailVerified != after.OIDCConnectRequireEmailVerified {
changed = append(changed, "oidc_connect_require_email_verified")
}
if before.OIDCConnectUserInfoEmailPath != after.OIDCConnectUserInfoEmailPath {
changed = append(changed, "oidc_connect_userinfo_email_path")
}
if before.OIDCConnectUserInfoIDPath != after.OIDCConnectUserInfoIDPath {
changed = append(changed, "oidc_connect_userinfo_id_path")
}
if before.OIDCConnectUserInfoUsernamePath != after.OIDCConnectUserInfoUsernamePath {
changed = append(changed, "oidc_connect_userinfo_username_path")
}
if before.SiteName != after.SiteName {
changed = append(changed, "site_name")
}
if before.SiteLogo != after.SiteLogo {
changed = append(changed, "site_logo")
}
if before.SiteSubtitle != after.SiteSubtitle {
changed = append(changed, "site_subtitle")
}
if before.APIBaseURL != after.APIBaseURL {
changed = append(changed, "api_base_url")
}
if before.ContactInfo != after.ContactInfo {
changed = append(changed, "contact_info")
}
if before.DocURL != after.DocURL {
changed = append(changed, "doc_url")
}
if before.HomeContent != after.HomeContent {
changed = append(changed, "home_content")
}
if before.HideCcsImportButton != after.HideCcsImportButton {
changed = append(changed, "hide_ccs_import_button")
}
if before.DefaultConcurrency != after.DefaultConcurrency {
changed = append(changed, "default_concurrency")
}
if before.DefaultBalance != after.DefaultBalance {
changed = append(changed, "default_balance")
}
if before.AffiliateRebateRate != after.AffiliateRebateRate {
changed = append(changed, "affiliate_rebate_rate")
}
if before.AffiliateRebateFreezeHours != after.AffiliateRebateFreezeHours {
changed = append(changed, "affiliate_rebate_freeze_hours")
}
if before.AffiliateRebateDurationDays != after.AffiliateRebateDurationDays {
changed = append(changed, "affiliate_rebate_duration_days")
}
if before.AffiliateRebatePerInviteeCap != after.AffiliateRebatePerInviteeCap {
changed = append(changed, "affiliate_rebate_per_invitee_cap")
}
if !equalDefaultSubscriptions(before.DefaultSubscriptions, after.DefaultSubscriptions) {
changed = append(changed, "default_subscriptions")
}
if before.EnableModelFallback != after.EnableModelFallback {
changed = append(changed, "enable_model_fallback")
}
if before.FallbackModelAnthropic != after.FallbackModelAnthropic {
changed = append(changed, "fallback_model_anthropic")
}
if before.FallbackModelOpenAI != after.FallbackModelOpenAI {
changed = append(changed, "fallback_model_openai")
}
if before.FallbackModelGemini != after.FallbackModelGemini {
changed = append(changed, "fallback_model_gemini")
}
if before.FallbackModelAntigravity != after.FallbackModelAntigravity {
changed = append(changed, "fallback_model_antigravity")
}
if before.EnableIdentityPatch != after.EnableIdentityPatch {
changed = append(changed, "enable_identity_patch")
}
if before.IdentityPatchPrompt != after.IdentityPatchPrompt {
changed = append(changed, "identity_patch_prompt")
}
if before.OpsMonitoringEnabled != after.OpsMonitoringEnabled {
changed = append(changed, "ops_monitoring_enabled")
}
if before.OpsRealtimeMonitoringEnabled != after.OpsRealtimeMonitoringEnabled {
changed = append(changed, "ops_realtime_monitoring_enabled")
}
if before.OpsQueryModeDefault != after.OpsQueryModeDefault {
changed = append(changed, "ops_query_mode_default")
}
if before.OpsMetricsIntervalSeconds != after.OpsMetricsIntervalSeconds {
changed = append(changed, "ops_metrics_interval_seconds")
}
if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
changed = append(changed, "min_claude_code_version")
}
if before.MaxClaudeCodeVersion != after.MaxClaudeCodeVersion {
changed = append(changed, "max_claude_code_version")
}
if before.MinCodexVersion != after.MinCodexVersion {
changed = append(changed, "min_codex_version")
}
if before.MaxCodexVersion != after.MaxCodexVersion {
changed = append(changed, "max_codex_version")
}
if before.CodexCLIOnlyAllowAppServerClients != after.CodexCLIOnlyAllowAppServerClients {
changed = append(changed, "codex_cli_only_allow_app_server_clients")
}
if before.CodexCLIOnlyEngineFingerprintSignals != after.CodexCLIOnlyEngineFingerprintSignals {
changed = append(changed, "codex_cli_only_engine_fingerprint_signals")
}
if before.CodexCLIOnlyBlacklist != after.CodexCLIOnlyBlacklist {
changed = append(changed, "codex_cli_only_blacklist")
}
if before.CodexCLIOnlyWhitelist != after.CodexCLIOnlyWhitelist {
changed = append(changed, "codex_cli_only_whitelist")
}
if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
changed = append(changed, "allow_ungrouped_key_scheduling")
}
if before.BackendModeEnabled != after.BackendModeEnabled {
changed = append(changed, "backend_mode_enabled")
}
if before.PurchaseSubscriptionEnabled != after.PurchaseSubscriptionEnabled {
changed = append(changed, "purchase_subscription_enabled")
}
if before.PurchaseSubscriptionURL != after.PurchaseSubscriptionURL {
changed = append(changed, "purchase_subscription_url")
}
if before.TableDefaultPageSize != after.TableDefaultPageSize {
changed = append(changed, "table_default_page_size")
}
if !equalIntSlice(before.TablePageSizeOptions, after.TablePageSizeOptions) {
changed = append(changed, "table_page_size_options")
}
if before.CustomMenuItems != after.CustomMenuItems {
changed = append(changed, "custom_menu_items")
}
if before.CustomEndpoints != after.CustomEndpoints {
changed = append(changed, "custom_endpoints")
}
if before.EnableFingerprintUnification != after.EnableFingerprintUnification {
changed = append(changed, "enable_fingerprint_unification")
}
if before.EnableMetadataPassthrough != after.EnableMetadataPassthrough {
changed = append(changed, "enable_metadata_passthrough")
}
if before.EnableCCHSigning != after.EnableCCHSigning {
changed = append(changed, "enable_cch_signing")
}
if before.EnableClaudeOAuthSystemPromptInjection != after.EnableClaudeOAuthSystemPromptInjection {
changed = append(changed, "enable_claude_oauth_system_prompt_injection")
}
if before.ClaudeOAuthSystemPrompt != after.ClaudeOAuthSystemPrompt {
changed = append(changed, "claude_oauth_system_prompt")
}
if before.ClaudeOAuthSystemPromptBlocks != after.ClaudeOAuthSystemPromptBlocks {
changed = append(changed, "claude_oauth_system_prompt_blocks")
}
if before.EnableAnthropicCacheTTL1hInjection != after.EnableAnthropicCacheTTL1hInjection {
changed = append(changed, "enable_anthropic_cache_ttl_1h_injection")
}
if before.RewriteMessageCacheControl != after.RewriteMessageCacheControl {
changed = append(changed, "rewrite_message_cache_control")
}
if before.EnableClientDatelineNormalization != after.EnableClientDatelineNormalization {
changed = append(changed, "enable_client_dateline_normalization")
}
if before.AntigravityUserAgentVersion != after.AntigravityUserAgentVersion {
changed = append(changed, "antigravity_user_agent_version")
}
if before.OpenAICodexUserAgent != after.OpenAICodexUserAgent {
changed = append(changed, "openai_codex_user_agent")
}
if before.PaymentVisibleMethodAlipaySource != after.PaymentVisibleMethodAlipaySource {
changed = append(changed, "payment_visible_method_alipay_source")
}
if before.PaymentVisibleMethodWxpaySource != after.PaymentVisibleMethodWxpaySource {
changed = append(changed, "payment_visible_method_wxpay_source")
}
if before.PaymentVisibleMethodAlipayEnabled != after.PaymentVisibleMethodAlipayEnabled {
changed = append(changed, "payment_visible_method_alipay_enabled")
}
if before.PaymentVisibleMethodWxpayEnabled != after.PaymentVisibleMethodWxpayEnabled {
changed = append(changed, "payment_visible_method_wxpay_enabled")
}
if before.OpenAIAdvancedSchedulerEnabled != after.OpenAIAdvancedSchedulerEnabled {
changed = append(changed, "openai_advanced_scheduler_enabled")
}
if before.OpenAIAdvancedSchedulerStickyWeightedEnabled != after.OpenAIAdvancedSchedulerStickyWeightedEnabled {
changed = append(changed, "openai_advanced_scheduler_sticky_weighted_enabled")
}
if before.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled != after.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled {
changed = append(changed, "openai_advanced_scheduler_subscription_priority_enabled")
}
if before.OpenAIAdvancedSchedulerLBTopK != after.OpenAIAdvancedSchedulerLBTopK {
changed = append(changed, "openai_advanced_scheduler_lb_top_k")
}
if before.OpenAIAdvancedSchedulerWeightPriority != after.OpenAIAdvancedSchedulerWeightPriority {
changed = append(changed, "openai_advanced_scheduler_weight_priority")
}
if before.OpenAIAdvancedSchedulerWeightLoad != after.OpenAIAdvancedSchedulerWeightLoad {
changed = append(changed, "openai_advanced_scheduler_weight_load")
}
if before.OpenAIAdvancedSchedulerWeightQueue != after.OpenAIAdvancedSchedulerWeightQueue {
changed = append(changed, "openai_advanced_scheduler_weight_queue")
}
if before.OpenAIAdvancedSchedulerWeightErrorRate != after.OpenAIAdvancedSchedulerWeightErrorRate {
changed = append(changed, "openai_advanced_scheduler_weight_error_rate")
}
if before.OpenAIAdvancedSchedulerWeightTTFT != after.OpenAIAdvancedSchedulerWeightTTFT {
changed = append(changed, "openai_advanced_scheduler_weight_ttft")
}
if before.OpenAIAdvancedSchedulerWeightReset != after.OpenAIAdvancedSchedulerWeightReset {
changed = append(changed, "openai_advanced_scheduler_weight_reset")
}
if before.OpenAIAdvancedSchedulerWeightQuotaHeadroom != after.OpenAIAdvancedSchedulerWeightQuotaHeadroom {
changed = append(changed, "openai_advanced_scheduler_weight_quota_headroom")
}
if before.OpenAIAdvancedSchedulerWeightPreviousResponse != after.OpenAIAdvancedSchedulerWeightPreviousResponse {
changed = append(changed, "openai_advanced_scheduler_weight_previous_response")
}
if before.OpenAIAdvancedSchedulerWeightSessionSticky != after.OpenAIAdvancedSchedulerWeightSessionSticky {
changed = append(changed, "openai_advanced_scheduler_weight_session_sticky")
}
// 余额、订阅到期与账号限额通知
if before.BalanceLowNotifyEnabled != after.BalanceLowNotifyEnabled {
changed = append(changed, "balance_low_notify_enabled")
}
if before.BalanceLowNotifyThreshold != after.BalanceLowNotifyThreshold {
changed = append(changed, "balance_low_notify_threshold")
}
if before.BalanceLowNotifyRechargeURL != after.BalanceLowNotifyRechargeURL {
changed = append(changed, "balance_low_notify_recharge_url")
}
if before.SubscriptionExpiryNotifyEnabled != after.SubscriptionExpiryNotifyEnabled {
changed = append(changed, "subscription_expiry_notify_enabled")
}
if before.AccountQuotaNotifyEnabled != after.AccountQuotaNotifyEnabled {
changed = append(changed, "account_quota_notify_enabled")
}
if !equalNotifyEmailEntries(before.AccountQuotaNotifyEmails, after.AccountQuotaNotifyEmails) {
changed = append(changed, "account_quota_notify_emails")
}
if before.ChannelMonitorEnabled != after.ChannelMonitorEnabled {
changed = append(changed, "channel_monitor_enabled")
}
if before.ChannelMonitorDefaultIntervalSeconds != after.ChannelMonitorDefaultIntervalSeconds {
changed = append(changed, "channel_monitor_default_interval_seconds")
}
if before.AvailableChannelsEnabled != after.AvailableChannelsEnabled {
changed = append(changed, "available_channels_enabled")
}
if before.AffiliateEnabled != after.AffiliateEnabled {
changed = append(changed, "affiliate_enabled")
}
if before.RiskControlEnabled != after.RiskControlEnabled {
changed = append(changed, "risk_control_enabled")
}
if before.CyberSessionBlockEnabled != after.CyberSessionBlockEnabled {
changed = append(changed, "cyber_session_block_enabled")
}
if before.CyberSessionBlockTTLSeconds != after.CyberSessionBlockTTLSeconds {
changed = append(changed, "cyber_session_block_ttl_seconds")
}
// Default platform quotas(JSON map,整体比较)
if !equalPlatformQuotaSettings(before.DefaultPlatformQuotas, after.DefaultPlatformQuotas) {
changed = append(changed, service.SettingKeyDefaultPlatformQuotas)
}
changed = appendAuthSourceDefaultChanges(changed, beforeAuthSourceDefaults, afterAuthSourceDefaults)
return changed
}
func appendAuthSourceDefaultChanges(changed []string, before *service.AuthSourceDefaultSettings, after *service.AuthSourceDefaultSettings) []string {
if before == nil {
before = &service.AuthSourceDefaultSettings{}
}
if after == nil {
after = &service.AuthSourceDefaultSettings{}
}
type providerDefaultGrantField struct {
name string
before service.ProviderDefaultGrantSettings
after service.ProviderDefaultGrantSettings
}
fields := []providerDefaultGrantField{
{name: "email", before: before.Email, after: after.Email},
{name: "linuxdo", before: before.LinuxDo, after: after.LinuxDo},
{name: "oidc", before: before.OIDC, after: after.OIDC},
{name: "wechat", before: before.WeChat, after: after.WeChat},
{name: "github", before: before.GitHub, after: after.GitHub},
{name: "google", before: before.Google, after: after.Google},
{name: "dingtalk", before: before.DingTalk, after: after.DingTalk},
}
for _, field := range fields {
if field.before.Balance != field.after.Balance {
changed = append(changed, "auth_source_default_"+field.name+"_balance")
}
if field.before.Concurrency != field.after.Concurrency {
changed = append(changed, "auth_source_default_"+field.name+"_concurrency")
}
if !equalDefaultSubscriptions(field.before.Subscriptions, field.after.Subscriptions) {
changed = append(changed, "auth_source_default_"+field.name+"_subscriptions")
}
if field.before.GrantOnSignup != field.after.GrantOnSignup {
changed = append(changed, "auth_source_default_"+field.name+"_grant_on_signup")
}
if field.before.GrantOnFirstBind != field.after.GrantOnFirstBind {
changed = append(changed, "auth_source_default_"+field.name+"_grant_on_first_bind")
}
// Platform quotas diff:整体替换语义,发单个 JSON key。
if !equalPlatformQuotaSettings(field.before.PlatformQuotas, field.after.PlatformQuotas) {
changed = append(changed, service.SettingKeyAuthSourcePlatformQuotas(field.name))
}
}
if before.ForceEmailOnThirdPartySignup != after.ForceEmailOnThirdPartySignup {
changed = append(changed, "force_email_on_third_party_signup")
}
return changed
}
func normalizeDefaultSubscriptions(input []dto.DefaultSubscriptionSetting) []dto.DefaultSubscriptionSetting {
if len(input) == 0 {
return nil
}
normalized := make([]dto.DefaultSubscriptionSetting, 0, len(input))
for _, item := range input {
if item.GroupID <= 0 || item.ValidityDays <= 0 {
continue
}
if item.ValidityDays > service.MaxValidityDays {
item.ValidityDays = service.MaxValidityDays
}
normalized = append(normalized, item)
}
return normalized
}
func normalizeOptionalDefaultSubscriptions(input *[]dto.DefaultSubscriptionSetting) *[]dto.DefaultSubscriptionSetting {
if input == nil {
return nil
}
normalized := normalizeDefaultSubscriptions(*input)
return &normalized
}
func float64ValueOrDefault(value *float64, fallback float64) float64 {
if value == nil {
return fallback
}
return *value
}
func intValueOrDefault(value *int, fallback int) int {
if value == nil {
return fallback
}
return *value
}
func boolValueOrDefault(value *bool, fallback bool) bool {
if value == nil {
return fallback
}
return *value
}
func defaultSubscriptionsValueOrDefault(input *[]dto.DefaultSubscriptionSetting, fallback []service.DefaultSubscriptionSetting) []service.DefaultSubscriptionSetting {
if input == nil {
return fallback
}
result := make([]service.DefaultSubscriptionSetting, 0, len(*input))
for _, item := range *input {
result = append(result, service.DefaultSubscriptionSetting{
GroupID: item.GroupID,
ValidityDays: item.ValidityDays,
})
}
return result
}
// platformQuotasValueOrDefault 处理 auth-source platform quota 的 nil 语义:
// nil = 请求未包含该字段(保留 fallback),non-nil(含 empty map)= 整体覆盖。
// 注意:JSON null 与字段省略等价——两者均反序列化为 nil map,因此都保留旧值;
// 若要清空某 source 的所有 quota 配置,须显式发空对象 {}。
func platformQuotasValueOrDefault(value, fallback map[string]*service.DefaultPlatformQuotaSetting) map[string]*service.DefaultPlatformQuotaSetting {
if value == nil {
return fallback
}
return value
}
func equalStringSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].GroupID != b[i].GroupID || a[i].ValidityDays != b[i].ValidityDays {
return false
}
}
return true
}
func equalLoginAgreementDocuments(a, b []service.LoginAgreementDocument) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].ID != b[i].ID || a[i].Title != b[i].Title || a[i].ContentMD != b[i].ContentMD {
return false
}
}
return true
}
func equalIntSlice(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func equalNotifyEmailEntries(a, b []service.NotifyEmailEntry) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].Email != b[i].Email || a[i].Verified != b[i].Verified || a[i].Disabled != b[i].Disabled {
return false
}
}
return true
}
// equalNullableFloat compares two *float64 values treating nil as a distinct case.
func equalNullableFloat(a, b *float64) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}
// slotOf returns the *float64 for the given window from a DefaultPlatformQuotaSetting.
func slotOf(s *service.DefaultPlatformQuotaSetting, win string) *float64 {
if s == nil {
return nil
}
switch win {
case "daily":
return s.DailyLimitUSD
case "weekly":
return s.WeeklyLimitUSD
case "monthly":
return s.MonthlyLimitUSD
}
return nil
}
// equalPlatformQuotaSettings reports whether two platform-quota maps are identical across all allowed slots.
func equalPlatformQuotaSettings(before, after map[string]*service.DefaultPlatformQuotaSetting) bool {
for _, platform := range service.AllowedQuotaPlatforms {
b := before[platform]
a := after[platform]
if !equalNullableFloat(slotOf(b, "daily"), slotOf(a, "daily")) {
return false
}
if !equalNullableFloat(slotOf(b, "weekly"), slotOf(a, "weekly")) {
return false
}
if !equalNullableFloat(slotOf(b, "monthly"), slotOf(a, "monthly")) {
return false
}
}
return true
}
func stringSetting(value *string, fallback string) string {
if value == nil {
return fallback
}
return *value
}

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