Compare commits

..
248 Commits
Author SHA1 Message Date
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
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
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
superman2003 7050070aa3 fix(grok): route cacheable chat requests via responses 2026-07-12 11:50:56 +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
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
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
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
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
507 changed files with 129019 additions and 58571 deletions
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
- name: Unit tests
working-directory: backend
run: make test-unit
@@ -60,7 +60,7 @@ jobs:
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.26.4'
go version | grep -q 'go1.26.5'
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
+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: |
+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
+63 -10
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
@@ -619,19 +637,19 @@ 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`
- 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/{request_id}`, and `/videos/{request_id}`. Generation requests require the group image-generation permission.
- Media models: `grok-imagine`, `grok-imagine-image-quality`, `grok-imagine-image`, `grok-imagine-edit`, `grok-imagine-video`, and `grok-imagine-video-1.5`
- Out of scope for this provider: TTS, transcription, browser automation, cookies, and Grok web scraping
@@ -647,9 +665,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 +677,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.
---
+18
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>
+18
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>
## 概要
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.145
0.1.151
+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
+25 -2
View File
@@ -96,6 +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)
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)
@@ -259,9 +267,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService)
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
availableChannelHandler := handler.NewAvailableChannelHandler(channelService, apiKeyService, settingService)
batchImageHandler := handler.NewBatchImageHandler(batchImagePublicService, batchImageDownloadService, batchImageCleanupService)
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, availableChannelHandler, idempotencyCoordinator, idempotencyCleanupService)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, availableChannelHandler, batchImageHandler, idempotencyCoordinator, idempotencyCleanupService)
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
@@ -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
+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:
+223 -16
View File
@@ -435,6 +435,188 @@ var (
},
},
}
// BatchImageEventsColumns holds the columns for the "batch_image_events" table.
BatchImageEventsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "job_id", Type: field.TypeString, Size: 64},
{Name: "event_type", Type: field.TypeString, Size: 64},
{Name: "payload", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "event_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageEventsTable holds the schema information for the "batch_image_events" table.
BatchImageEventsTable = &schema.Table{
Name: "batch_image_events",
Columns: BatchImageEventsColumns,
PrimaryKey: []*schema.Column{BatchImageEventsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimageevent_job_id_created_at",
Unique: false,
Columns: []*schema.Column{BatchImageEventsColumns[1], BatchImageEventsColumns[5]},
},
{
Name: "batchimageevent_event_type",
Unique: false,
Columns: []*schema.Column{BatchImageEventsColumns[2]},
},
{
Name: "batchimageevent_job_id_event_hash",
Unique: true,
Columns: []*schema.Column{BatchImageEventsColumns[1], BatchImageEventsColumns[4]},
Annotation: &entsql.IndexAnnotation{
Where: "event_hash IS NOT NULL AND event_hash <> ''",
},
},
},
}
// BatchImageItemsColumns holds the columns for the "batch_image_items" table.
BatchImageItemsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "job_id", Type: field.TypeString, Size: 64},
{Name: "custom_id", Type: field.TypeString, Size: 255},
{Name: "status", Type: field.TypeString, Size: 32},
{Name: "request_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "prompt_preview", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "provider_source_object", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "source_line_number", Type: field.TypeInt, Nullable: true},
{Name: "source_byte_offset", Type: field.TypeInt64, Nullable: true},
{Name: "source_byte_length", Type: field.TypeInt64, Nullable: true},
{Name: "mime_type", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "file_extension", Type: field.TypeString, Nullable: true, Size: 32},
{Name: "image_count", Type: field.TypeInt, Default: 0},
{Name: "error_code", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "billed_amount", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "indexed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageItemsTable holds the schema information for the "batch_image_items" table.
BatchImageItemsTable = &schema.Table{
Name: "batch_image_items",
Columns: BatchImageItemsColumns,
PrimaryKey: []*schema.Column{BatchImageItemsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimageitem_job_id_custom_id",
Unique: true,
Columns: []*schema.Column{BatchImageItemsColumns[1], BatchImageItemsColumns[2]},
},
{
Name: "batchimageitem_job_id_status",
Unique: false,
Columns: []*schema.Column{BatchImageItemsColumns[1], BatchImageItemsColumns[3]},
},
{
Name: "batchimageitem_provider_source_object",
Unique: false,
Columns: []*schema.Column{BatchImageItemsColumns[6]},
},
},
}
// BatchImageJobsColumns holds the columns for the "batch_image_jobs" table.
BatchImageJobsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "batch_id", Type: field.TypeString, Size: 64},
{Name: "user_id", Type: field.TypeInt64},
{Name: "api_key_id", Type: field.TypeInt64, Nullable: true},
{Name: "account_id", Type: field.TypeInt64, Nullable: true},
{Name: "provider", Type: field.TypeString, Size: 32},
{Name: "model", Type: field.TypeString, Size: 128},
{Name: "task_name", Type: field.TypeString, Size: 255, Default: ""},
{Name: "status", Type: field.TypeString, Size: 32, Default: "created"},
{Name: "provider_job_name", Type: field.TypeString, Nullable: true, Size: 512},
{Name: "provider_input_ref", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "provider_output_ref", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "gcs_input_uri", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "gcs_output_uri", Type: field.TypeString, Nullable: true, Size: 1024},
{Name: "item_count", Type: field.TypeInt},
{Name: "success_count", Type: field.TypeInt, Default: 0},
{Name: "fail_count", Type: field.TypeInt, Default: 0},
{Name: "cancelled_count", Type: field.TypeInt, Default: 0},
{Name: "estimated_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "hold_amount", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "actual_cost", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "currency", Type: field.TypeString, Size: 16, Default: "USD"},
{Name: "hold_id", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "idempotency_key", Type: field.TypeString, Nullable: true, Size: 255},
{Name: "request_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "manifest_hash", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "retry_count", Type: field.TypeInt, Default: 0},
{Name: "version", Type: field.TypeInt, Default: 0},
{Name: "output_expires_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "input_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "output_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "downloaded_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "user_deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "last_error_code", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "last_error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "submitted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "started_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "finished_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "settled_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// BatchImageJobsTable holds the schema information for the "batch_image_jobs" table.
BatchImageJobsTable = &schema.Table{
Name: "batch_image_jobs",
Columns: BatchImageJobsColumns,
PrimaryKey: []*schema.Column{BatchImageJobsColumns[0]},
Indexes: []*schema.Index{
{
Name: "batchimagejob_batch_id",
Unique: true,
Columns: []*schema.Column{BatchImageJobsColumns[1]},
},
{
Name: "batchimagejob_user_id_created_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[2], BatchImageJobsColumns[35]},
},
{
Name: "batchimagejob_status",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[8]},
},
{
Name: "batchimagejob_provider_status",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[5], BatchImageJobsColumns[8]},
},
{
Name: "batchimagejob_idempotency_key",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[23]},
Annotation: &entsql.IndexAnnotation{
Where: "idempotency_key IS NOT NULL AND idempotency_key <> ''",
},
},
{
Name: "batchimagejob_manifest_hash",
Unique: true,
Columns: []*schema.Column{BatchImageJobsColumns[25]},
Annotation: &entsql.IndexAnnotation{
Where: "manifest_hash IS NOT NULL AND manifest_hash <> ''",
},
},
{
Name: "batchimagejob_output_expires_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[28]},
},
{
Name: "batchimagejob_downloaded_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[31]},
},
{
Name: "batchimagejob_user_deleted_at",
Unique: false,
Columns: []*schema.Column{BatchImageJobsColumns[32]},
},
},
}
// ChannelMonitorsColumns holds the columns for the "channel_monitors" table.
ChannelMonitorsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -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]},
},
},
}
@@ -1382,6 +1573,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 +1592,31 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "usage_logs_api_keys_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[40]},
RefColumns: []*schema.Column{APIKeysColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_accounts_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[41]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_groups_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[42]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "usage_logs_users_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[43]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_user_subscriptions_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[44]},
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
OnDelete: schema.SetNull,
},
@@ -1431,32 +1625,32 @@ var (
{
Name: "usagelog_user_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40]},
Columns: []*schema.Column{UsageLogsColumns[43]},
},
{
Name: "usagelog_api_key_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37]},
Columns: []*schema.Column{UsageLogsColumns[40]},
},
{
Name: "usagelog_account_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[38]},
Columns: []*schema.Column{UsageLogsColumns[41]},
},
{
Name: "usagelog_group_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39]},
Columns: []*schema.Column{UsageLogsColumns[42]},
},
{
Name: "usagelog_subscription_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[41]},
Columns: []*schema.Column{UsageLogsColumns[44]},
},
{
Name: "usagelog_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[39]},
},
{
Name: "usagelog_model",
@@ -1476,17 +1670,17 @@ var (
{
Name: "usagelog_user_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[40], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[43], UsageLogsColumns[39]},
},
{
Name: "usagelog_api_key_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[37], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[40], UsageLogsColumns[39]},
},
{
Name: "usagelog_group_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[39], UsageLogsColumns[36]},
Columns: []*schema.Column{UsageLogsColumns[42], UsageLogsColumns[39]},
},
},
}
@@ -1500,6 +1694,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 +1721,7 @@ var (
{
Name: "user_status",
Unique: false,
Columns: []*schema.Column{UsersColumns[9]},
Columns: []*schema.Column{UsersColumns[10]},
},
{
Name: "user_deleted_at",
@@ -1799,6 +1994,9 @@ var (
AnnouncementReadsTable,
AuthIdentitiesTable,
AuthIdentityChannelsTable,
BatchImageEventsTable,
BatchImageItemsTable,
BatchImageJobsTable,
ChannelMonitorsTable,
ChannelMonitorDailyRollupsTable,
ChannelMonitorHistoriesTable,
@@ -1862,6 +2060,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",
+6630 -3
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)
+228 -27
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()
@@ -1787,12 +1976,20 @@ func init() {
usagelogDescImageSizeSource := usagelogFields[37].Descriptor()
// usagelog.ImageSizeSourceValidator is a validator for the "image_size_source" field. It is called by the builders before save.
usagelog.ImageSizeSourceValidator = usagelogDescImageSizeSource.Validators[0].(func(string) error)
// usagelogDescVideoCount is the schema descriptor for video_count field.
usagelogDescVideoCount := usagelogFields[39].Descriptor()
// usagelog.DefaultVideoCount holds the default value on creation for the video_count field.
usagelog.DefaultVideoCount = usagelogDescVideoCount.Default.(int)
// usagelogDescVideoResolution is the schema descriptor for video_resolution field.
usagelogDescVideoResolution := usagelogFields[40].Descriptor()
// usagelog.VideoResolutionValidator is a validator for the "video_resolution" field. It is called by the builders before save.
usagelog.VideoResolutionValidator = usagelogDescVideoResolution.Validators[0].(func(string) error)
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
usagelogDescCacheTTLOverridden := usagelogFields[39].Descriptor()
usagelogDescCacheTTLOverridden := usagelogFields[42].Descriptor()
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
usagelog.DefaultCacheTTLOverridden = usagelogDescCacheTTLOverridden.Default.(bool)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt := usagelogFields[40].Descriptor()
usagelogDescCreatedAt := usagelogFields[43].Descriptor()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
userMixin := schema.User{}.Mixin()
@@ -1860,54 +2057,58 @@ func init() {
userDescBalance := userFields[3].Descriptor()
// user.DefaultBalance holds the default value on creation for the balance field.
user.DefaultBalance = userDescBalance.Default.(float64)
// userDescFrozenBalance is the schema descriptor for frozen_balance field.
userDescFrozenBalance := userFields[4].Descriptor()
// user.DefaultFrozenBalance holds the default value on creation for the frozen_balance field.
user.DefaultFrozenBalance = userDescFrozenBalance.Default.(float64)
// userDescConcurrency is the schema descriptor for concurrency field.
userDescConcurrency := userFields[4].Descriptor()
userDescConcurrency := userFields[5].Descriptor()
// user.DefaultConcurrency holds the default value on creation for the concurrency field.
user.DefaultConcurrency = userDescConcurrency.Default.(int)
// userDescStatus is the schema descriptor for status field.
userDescStatus := userFields[5].Descriptor()
userDescStatus := userFields[6].Descriptor()
// user.DefaultStatus holds the default value on creation for the status field.
user.DefaultStatus = userDescStatus.Default.(string)
// user.StatusValidator is a validator for the "status" field. It is called by the builders before save.
user.StatusValidator = userDescStatus.Validators[0].(func(string) error)
// userDescUsername is the schema descriptor for username field.
userDescUsername := userFields[6].Descriptor()
userDescUsername := userFields[7].Descriptor()
// user.DefaultUsername holds the default value on creation for the username field.
user.DefaultUsername = userDescUsername.Default.(string)
// user.UsernameValidator is a validator for the "username" field. It is called by the builders before save.
user.UsernameValidator = userDescUsername.Validators[0].(func(string) error)
// userDescNotes is the schema descriptor for notes field.
userDescNotes := userFields[7].Descriptor()
userDescNotes := userFields[8].Descriptor()
// user.DefaultNotes holds the default value on creation for the notes field.
user.DefaultNotes = userDescNotes.Default.(string)
// userDescTotpEnabled is the schema descriptor for totp_enabled field.
userDescTotpEnabled := userFields[9].Descriptor()
userDescTotpEnabled := userFields[10].Descriptor()
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
user.DefaultTotpEnabled = userDescTotpEnabled.Default.(bool)
// userDescSignupSource is the schema descriptor for signup_source field.
userDescSignupSource := userFields[11].Descriptor()
userDescSignupSource := userFields[12].Descriptor()
// user.DefaultSignupSource holds the default value on creation for the signup_source field.
user.DefaultSignupSource = userDescSignupSource.Default.(string)
// user.SignupSourceValidator is a validator for the "signup_source" field. It is called by the builders before save.
user.SignupSourceValidator = userDescSignupSource.Validators[0].(func(string) error)
// userDescBalanceNotifyEnabled is the schema descriptor for balance_notify_enabled field.
userDescBalanceNotifyEnabled := userFields[14].Descriptor()
userDescBalanceNotifyEnabled := userFields[15].Descriptor()
// user.DefaultBalanceNotifyEnabled holds the default value on creation for the balance_notify_enabled field.
user.DefaultBalanceNotifyEnabled = userDescBalanceNotifyEnabled.Default.(bool)
// userDescBalanceNotifyThresholdType is the schema descriptor for balance_notify_threshold_type field.
userDescBalanceNotifyThresholdType := userFields[15].Descriptor()
userDescBalanceNotifyThresholdType := userFields[16].Descriptor()
// user.DefaultBalanceNotifyThresholdType holds the default value on creation for the balance_notify_threshold_type field.
user.DefaultBalanceNotifyThresholdType = userDescBalanceNotifyThresholdType.Default.(string)
// userDescBalanceNotifyExtraEmails is the schema descriptor for balance_notify_extra_emails field.
userDescBalanceNotifyExtraEmails := userFields[17].Descriptor()
userDescBalanceNotifyExtraEmails := userFields[18].Descriptor()
// user.DefaultBalanceNotifyExtraEmails holds the default value on creation for the balance_notify_extra_emails field.
user.DefaultBalanceNotifyExtraEmails = userDescBalanceNotifyExtraEmails.Default.(string)
// userDescTotalRecharged is the schema descriptor for total_recharged field.
userDescTotalRecharged := userFields[18].Descriptor()
userDescTotalRecharged := userFields[19].Descriptor()
// user.DefaultTotalRecharged holds the default value on creation for the total_recharged field.
user.DefaultTotalRecharged = userDescTotalRecharged.Default.(float64)
// userDescRpmLimit is the schema descriptor for rpm_limit field.
userDescRpmLimit := userFields[19].Descriptor()
userDescRpmLimit := userFields[20].Descriptor()
// user.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
user.DefaultRpmLimit = userDescRpmLimit.Default.(int)
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
+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"),
}
}
+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").
+14
View File
@@ -149,6 +149,20 @@ func (UsageLog) Fields() []ent.Field {
field.JSON("image_size_breakdown", map[string]int{}).
Optional().
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
// 视频生成字段(Grok 视频按秒计费;billing_mode 走 token/其他模式时这些列仍标记视频用量)
field.Int("video_count").
Default(0).
Comment("视频生成数量;>0 表示本行是视频生成用量"),
field.String("video_resolution").
MaxLen(10).
Optional().
Nillable().
Comment("计费用视频分辨率 480p/720p/1080p"),
field.Int("video_duration_seconds").
Optional().
Nillable().
Comment("提交时请求的视频时长(秒),按秒计费的乘数"),
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
field.Bool("cache_ttl_overridden").
Default(false),
+3
View File
@@ -49,6 +49,9 @@ func (User) Fields() []ent.Field {
field.Float("balance").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0),
field.Float("frozen_balance").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0),
field.Int("concurrency").
Default(5),
field.String("status").
+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)
+41 -2
View File
@@ -101,6 +101,12 @@ type UsageLog struct {
ImageSizeSource *string `json:"image_size_source,omitempty"`
// ImageSizeBreakdown holds the value of the "image_size_breakdown" field.
ImageSizeBreakdown map[string]int `json:"image_size_breakdown,omitempty"`
// 视频生成数量;>0 表示本行是视频生成用量
VideoCount int `json:"video_count,omitempty"`
// 计费用视频分辨率 480p/720p/1080p
VideoResolution *string `json:"video_resolution,omitempty"`
// 提交时请求的视频时长(秒),按秒计费的乘数
VideoDurationSeconds *int `json:"video_duration_seconds,omitempty"`
// CacheTTLOverridden holds the value of the "cache_ttl_overridden" field.
CacheTTLOverridden bool `json:"cache_ttl_overridden,omitempty"`
// CreatedAt holds the value of the "created_at" field.
@@ -194,9 +200,9 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullBool)
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier, usagelog.FieldAccountRateMultiplier:
values[i] = new(sql.NullFloat64)
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldChannelID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount:
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldChannelID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount, usagelog.FieldVideoCount, usagelog.FieldVideoDurationSeconds:
values[i] = new(sql.NullInt64)
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldRequestedModel, usagelog.FieldUpstreamModel, usagelog.FieldModelMappingChain, usagelog.FieldBillingTier, usagelog.FieldBillingMode, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldImageInputSize, usagelog.FieldImageOutputSize, usagelog.FieldImageSizeSource:
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldRequestedModel, usagelog.FieldUpstreamModel, usagelog.FieldModelMappingChain, usagelog.FieldBillingTier, usagelog.FieldBillingMode, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldImageInputSize, usagelog.FieldImageOutputSize, usagelog.FieldImageSizeSource, usagelog.FieldVideoResolution:
values[i] = new(sql.NullString)
case usagelog.FieldCreatedAt:
values[i] = new(sql.NullTime)
@@ -474,6 +480,26 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
return fmt.Errorf("unmarshal field image_size_breakdown: %w", err)
}
}
case usagelog.FieldVideoCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field video_count", values[i])
} else if value.Valid {
_m.VideoCount = int(value.Int64)
}
case usagelog.FieldVideoResolution:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field video_resolution", values[i])
} else if value.Valid {
_m.VideoResolution = new(string)
*_m.VideoResolution = value.String
}
case usagelog.FieldVideoDurationSeconds:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field video_duration_seconds", values[i])
} else if value.Valid {
_m.VideoDurationSeconds = new(int)
*_m.VideoDurationSeconds = int(value.Int64)
}
case usagelog.FieldCacheTTLOverridden:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field cache_ttl_overridden", values[i])
@@ -698,6 +724,19 @@ func (_m *UsageLog) String() string {
builder.WriteString("image_size_breakdown=")
builder.WriteString(fmt.Sprintf("%v", _m.ImageSizeBreakdown))
builder.WriteString(", ")
builder.WriteString("video_count=")
builder.WriteString(fmt.Sprintf("%v", _m.VideoCount))
builder.WriteString(", ")
if v := _m.VideoResolution; v != nil {
builder.WriteString("video_resolution=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.VideoDurationSeconds; v != nil {
builder.WriteString("video_duration_seconds=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("cache_ttl_overridden=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheTTLOverridden))
builder.WriteString(", ")
+28
View File
@@ -92,6 +92,12 @@ const (
FieldImageSizeSource = "image_size_source"
// FieldImageSizeBreakdown holds the string denoting the image_size_breakdown field in the database.
FieldImageSizeBreakdown = "image_size_breakdown"
// FieldVideoCount holds the string denoting the video_count field in the database.
FieldVideoCount = "video_count"
// FieldVideoResolution holds the string denoting the video_resolution field in the database.
FieldVideoResolution = "video_resolution"
// FieldVideoDurationSeconds holds the string denoting the video_duration_seconds field in the database.
FieldVideoDurationSeconds = "video_duration_seconds"
// FieldCacheTTLOverridden holds the string denoting the cache_ttl_overridden field in the database.
FieldCacheTTLOverridden = "cache_ttl_overridden"
// FieldCreatedAt holds the string denoting the created_at field in the database.
@@ -187,6 +193,9 @@ var Columns = []string{
FieldImageOutputSize,
FieldImageSizeSource,
FieldImageSizeBreakdown,
FieldVideoCount,
FieldVideoResolution,
FieldVideoDurationSeconds,
FieldCacheTTLOverridden,
FieldCreatedAt,
}
@@ -260,6 +269,10 @@ var (
ImageOutputSizeValidator func(string) error
// ImageSizeSourceValidator is a validator for the "image_size_source" field. It is called by the builders before save.
ImageSizeSourceValidator func(string) error
// DefaultVideoCount holds the default value on creation for the "video_count" field.
DefaultVideoCount int
// VideoResolutionValidator is a validator for the "video_resolution" field. It is called by the builders before save.
VideoResolutionValidator func(string) error
// DefaultCacheTTLOverridden holds the default value on creation for the "cache_ttl_overridden" field.
DefaultCacheTTLOverridden bool
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
@@ -464,6 +477,21 @@ func ByImageSizeSource(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldImageSizeSource, opts...).ToFunc()
}
// ByVideoCount orders the results by the video_count field.
func ByVideoCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoCount, opts...).ToFunc()
}
// ByVideoResolution orders the results by the video_resolution field.
func ByVideoResolution(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoResolution, opts...).ToFunc()
}
// ByVideoDurationSeconds orders the results by the video_duration_seconds field.
func ByVideoDurationSeconds(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVideoDurationSeconds, opts...).ToFunc()
}
// ByCacheTTLOverridden orders the results by the cache_ttl_overridden field.
func ByCacheTTLOverridden(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCacheTTLOverridden, opts...).ToFunc()
+180
View File
@@ -245,6 +245,21 @@ func ImageSizeSource(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldImageSizeSource, v))
}
// VideoCount applies equality check predicate on the "video_count" field. It's identical to VideoCountEQ.
func VideoCount(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoCount, v))
}
// VideoResolution applies equality check predicate on the "video_resolution" field. It's identical to VideoResolutionEQ.
func VideoResolution(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoResolution, v))
}
// VideoDurationSeconds applies equality check predicate on the "video_duration_seconds" field. It's identical to VideoDurationSecondsEQ.
func VideoDurationSeconds(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoDurationSeconds, v))
}
// CacheTTLOverridden applies equality check predicate on the "cache_ttl_overridden" field. It's identical to CacheTTLOverriddenEQ.
func CacheTTLOverridden(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
@@ -2150,6 +2165,171 @@ func ImageSizeBreakdownNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldImageSizeBreakdown))
}
// VideoCountEQ applies the EQ predicate on the "video_count" field.
func VideoCountEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoCount, v))
}
// VideoCountNEQ applies the NEQ predicate on the "video_count" field.
func VideoCountNEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoCount, v))
}
// VideoCountIn applies the In predicate on the "video_count" field.
func VideoCountIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoCount, vs...))
}
// VideoCountNotIn applies the NotIn predicate on the "video_count" field.
func VideoCountNotIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoCount, vs...))
}
// VideoCountGT applies the GT predicate on the "video_count" field.
func VideoCountGT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoCount, v))
}
// VideoCountGTE applies the GTE predicate on the "video_count" field.
func VideoCountGTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoCount, v))
}
// VideoCountLT applies the LT predicate on the "video_count" field.
func VideoCountLT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoCount, v))
}
// VideoCountLTE applies the LTE predicate on the "video_count" field.
func VideoCountLTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoCount, v))
}
// VideoResolutionEQ applies the EQ predicate on the "video_resolution" field.
func VideoResolutionEQ(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoResolution, v))
}
// VideoResolutionNEQ applies the NEQ predicate on the "video_resolution" field.
func VideoResolutionNEQ(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoResolution, v))
}
// VideoResolutionIn applies the In predicate on the "video_resolution" field.
func VideoResolutionIn(vs ...string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoResolution, vs...))
}
// VideoResolutionNotIn applies the NotIn predicate on the "video_resolution" field.
func VideoResolutionNotIn(vs ...string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoResolution, vs...))
}
// VideoResolutionGT applies the GT predicate on the "video_resolution" field.
func VideoResolutionGT(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoResolution, v))
}
// VideoResolutionGTE applies the GTE predicate on the "video_resolution" field.
func VideoResolutionGTE(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoResolution, v))
}
// VideoResolutionLT applies the LT predicate on the "video_resolution" field.
func VideoResolutionLT(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoResolution, v))
}
// VideoResolutionLTE applies the LTE predicate on the "video_resolution" field.
func VideoResolutionLTE(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoResolution, v))
}
// VideoResolutionContains applies the Contains predicate on the "video_resolution" field.
func VideoResolutionContains(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldContains(FieldVideoResolution, v))
}
// VideoResolutionHasPrefix applies the HasPrefix predicate on the "video_resolution" field.
func VideoResolutionHasPrefix(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldHasPrefix(FieldVideoResolution, v))
}
// VideoResolutionHasSuffix applies the HasSuffix predicate on the "video_resolution" field.
func VideoResolutionHasSuffix(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldHasSuffix(FieldVideoResolution, v))
}
// VideoResolutionIsNil applies the IsNil predicate on the "video_resolution" field.
func VideoResolutionIsNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldIsNull(FieldVideoResolution))
}
// VideoResolutionNotNil applies the NotNil predicate on the "video_resolution" field.
func VideoResolutionNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldVideoResolution))
}
// VideoResolutionEqualFold applies the EqualFold predicate on the "video_resolution" field.
func VideoResolutionEqualFold(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEqualFold(FieldVideoResolution, v))
}
// VideoResolutionContainsFold applies the ContainsFold predicate on the "video_resolution" field.
func VideoResolutionContainsFold(v string) predicate.UsageLog {
return predicate.UsageLog(sql.FieldContainsFold(FieldVideoResolution, v))
}
// VideoDurationSecondsEQ applies the EQ predicate on the "video_duration_seconds" field.
func VideoDurationSecondsEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsNEQ applies the NEQ predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNEQ(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNEQ(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsIn applies the In predicate on the "video_duration_seconds" field.
func VideoDurationSecondsIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldIn(FieldVideoDurationSeconds, vs...))
}
// VideoDurationSecondsNotIn applies the NotIn predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNotIn(vs ...int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotIn(FieldVideoDurationSeconds, vs...))
}
// VideoDurationSecondsGT applies the GT predicate on the "video_duration_seconds" field.
func VideoDurationSecondsGT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGT(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsGTE applies the GTE predicate on the "video_duration_seconds" field.
func VideoDurationSecondsGTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldGTE(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsLT applies the LT predicate on the "video_duration_seconds" field.
func VideoDurationSecondsLT(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLT(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsLTE applies the LTE predicate on the "video_duration_seconds" field.
func VideoDurationSecondsLTE(v int) predicate.UsageLog {
return predicate.UsageLog(sql.FieldLTE(FieldVideoDurationSeconds, v))
}
// VideoDurationSecondsIsNil applies the IsNil predicate on the "video_duration_seconds" field.
func VideoDurationSecondsIsNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldIsNull(FieldVideoDurationSeconds))
}
// VideoDurationSecondsNotNil applies the NotNil predicate on the "video_duration_seconds" field.
func VideoDurationSecondsNotNil() predicate.UsageLog {
return predicate.UsageLog(sql.FieldNotNull(FieldVideoDurationSeconds))
}
// CacheTTLOverriddenEQ applies the EQ predicate on the "cache_ttl_overridden" field.
func CacheTTLOverriddenEQ(v bool) predicate.UsageLog {
return predicate.UsageLog(sql.FieldEQ(FieldCacheTTLOverridden, v))
+266
View File
@@ -525,6 +525,48 @@ func (_c *UsageLogCreate) SetImageSizeBreakdown(v map[string]int) *UsageLogCreat
return _c
}
// SetVideoCount sets the "video_count" field.
func (_c *UsageLogCreate) SetVideoCount(v int) *UsageLogCreate {
_c.mutation.SetVideoCount(v)
return _c
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoCount(v *int) *UsageLogCreate {
if v != nil {
_c.SetVideoCount(*v)
}
return _c
}
// SetVideoResolution sets the "video_resolution" field.
func (_c *UsageLogCreate) SetVideoResolution(v string) *UsageLogCreate {
_c.mutation.SetVideoResolution(v)
return _c
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoResolution(v *string) *UsageLogCreate {
if v != nil {
_c.SetVideoResolution(*v)
}
return _c
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_c *UsageLogCreate) SetVideoDurationSeconds(v int) *UsageLogCreate {
_c.mutation.SetVideoDurationSeconds(v)
return _c
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_c *UsageLogCreate) SetNillableVideoDurationSeconds(v *int) *UsageLogCreate {
if v != nil {
_c.SetVideoDurationSeconds(*v)
}
return _c
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_c *UsageLogCreate) SetCacheTTLOverridden(v bool) *UsageLogCreate {
_c.mutation.SetCacheTTLOverridden(v)
@@ -677,6 +719,10 @@ func (_c *UsageLogCreate) defaults() {
v := usagelog.DefaultImageCount
_c.mutation.SetImageCount(v)
}
if _, ok := _c.mutation.VideoCount(); !ok {
v := usagelog.DefaultVideoCount
_c.mutation.SetVideoCount(v)
}
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
v := usagelog.DefaultCacheTTLOverridden
_c.mutation.SetCacheTTLOverridden(v)
@@ -817,6 +863,14 @@ func (_c *UsageLogCreate) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if _, ok := _c.mutation.VideoCount(); !ok {
return &ValidationError{Name: "video_count", err: errors.New(`ent: missing required field "UsageLog.video_count"`)}
}
if v, ok := _c.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _, ok := _c.mutation.CacheTTLOverridden(); !ok {
return &ValidationError{Name: "cache_ttl_overridden", err: errors.New(`ent: missing required field "UsageLog.cache_ttl_overridden"`)}
}
@@ -995,6 +1049,18 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
_spec.SetField(usagelog.FieldImageSizeBreakdown, field.TypeJSON, value)
_node.ImageSizeBreakdown = value
}
if value, ok := _c.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
_node.VideoCount = value
}
if value, ok := _c.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
_node.VideoResolution = &value
}
if value, ok := _c.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
_node.VideoDurationSeconds = &value
}
if value, ok := _c.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
_node.CacheTTLOverridden = value
@@ -1830,6 +1896,66 @@ func (u *UsageLogUpsert) ClearImageSizeBreakdown() *UsageLogUpsert {
return u
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsert) SetVideoCount(v int) *UsageLogUpsert {
u.Set(usagelog.FieldVideoCount, v)
return u
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoCount() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoCount)
return u
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsert) AddVideoCount(v int) *UsageLogUpsert {
u.Add(usagelog.FieldVideoCount, v)
return u
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsert) SetVideoResolution(v string) *UsageLogUpsert {
u.Set(usagelog.FieldVideoResolution, v)
return u
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoResolution() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoResolution)
return u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsert) ClearVideoResolution() *UsageLogUpsert {
u.SetNull(usagelog.FieldVideoResolution)
return u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsert) SetVideoDurationSeconds(v int) *UsageLogUpsert {
u.Set(usagelog.FieldVideoDurationSeconds, v)
return u
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsert) UpdateVideoDurationSeconds() *UsageLogUpsert {
u.SetExcluded(usagelog.FieldVideoDurationSeconds)
return u
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsert) AddVideoDurationSeconds(v int) *UsageLogUpsert {
u.Add(usagelog.FieldVideoDurationSeconds, v)
return u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsert) ClearVideoDurationSeconds() *UsageLogUpsert {
u.SetNull(usagelog.FieldVideoDurationSeconds)
return u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsert) SetCacheTTLOverridden(v bool) *UsageLogUpsert {
u.Set(usagelog.FieldCacheTTLOverridden, v)
@@ -2692,6 +2818,76 @@ func (u *UsageLogUpsertOne) ClearImageSizeBreakdown() *UsageLogUpsertOne {
})
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsertOne) SetVideoCount(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoCount(v)
})
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsertOne) AddVideoCount(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoCount(v)
})
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoCount() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoCount()
})
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsertOne) SetVideoResolution(v string) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoResolution(v)
})
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoResolution() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoResolution()
})
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsertOne) ClearVideoResolution() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoResolution()
})
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) SetVideoDurationSeconds(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoDurationSeconds(v)
})
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) AddVideoDurationSeconds(v int) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoDurationSeconds(v)
})
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsertOne) UpdateVideoDurationSeconds() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoDurationSeconds()
})
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsertOne) ClearVideoDurationSeconds() *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoDurationSeconds()
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsertOne) SetCacheTTLOverridden(v bool) *UsageLogUpsertOne {
return u.Update(func(s *UsageLogUpsert) {
@@ -3722,6 +3918,76 @@ func (u *UsageLogUpsertBulk) ClearImageSizeBreakdown() *UsageLogUpsertBulk {
})
}
// SetVideoCount sets the "video_count" field.
func (u *UsageLogUpsertBulk) SetVideoCount(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoCount(v)
})
}
// AddVideoCount adds v to the "video_count" field.
func (u *UsageLogUpsertBulk) AddVideoCount(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoCount(v)
})
}
// UpdateVideoCount sets the "video_count" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoCount() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoCount()
})
}
// SetVideoResolution sets the "video_resolution" field.
func (u *UsageLogUpsertBulk) SetVideoResolution(v string) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoResolution(v)
})
}
// UpdateVideoResolution sets the "video_resolution" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoResolution() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoResolution()
})
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (u *UsageLogUpsertBulk) ClearVideoResolution() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoResolution()
})
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) SetVideoDurationSeconds(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.SetVideoDurationSeconds(v)
})
}
// AddVideoDurationSeconds adds v to the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) AddVideoDurationSeconds(v int) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.AddVideoDurationSeconds(v)
})
}
// UpdateVideoDurationSeconds sets the "video_duration_seconds" field to the value that was provided on create.
func (u *UsageLogUpsertBulk) UpdateVideoDurationSeconds() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.UpdateVideoDurationSeconds()
})
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (u *UsageLogUpsertBulk) ClearVideoDurationSeconds() *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
s.ClearVideoDurationSeconds()
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (u *UsageLogUpsertBulk) SetCacheTTLOverridden(v bool) *UsageLogUpsertBulk {
return u.Update(func(s *UsageLogUpsert) {
+188
View File
@@ -811,6 +811,74 @@ func (_u *UsageLogUpdate) ClearImageSizeBreakdown() *UsageLogUpdate {
return _u
}
// SetVideoCount sets the "video_count" field.
func (_u *UsageLogUpdate) SetVideoCount(v int) *UsageLogUpdate {
_u.mutation.ResetVideoCount()
_u.mutation.SetVideoCount(v)
return _u
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoCount(v *int) *UsageLogUpdate {
if v != nil {
_u.SetVideoCount(*v)
}
return _u
}
// AddVideoCount adds value to the "video_count" field.
func (_u *UsageLogUpdate) AddVideoCount(v int) *UsageLogUpdate {
_u.mutation.AddVideoCount(v)
return _u
}
// SetVideoResolution sets the "video_resolution" field.
func (_u *UsageLogUpdate) SetVideoResolution(v string) *UsageLogUpdate {
_u.mutation.SetVideoResolution(v)
return _u
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoResolution(v *string) *UsageLogUpdate {
if v != nil {
_u.SetVideoResolution(*v)
}
return _u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (_u *UsageLogUpdate) ClearVideoResolution() *UsageLogUpdate {
_u.mutation.ClearVideoResolution()
return _u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_u *UsageLogUpdate) SetVideoDurationSeconds(v int) *UsageLogUpdate {
_u.mutation.ResetVideoDurationSeconds()
_u.mutation.SetVideoDurationSeconds(v)
return _u
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_u *UsageLogUpdate) SetNillableVideoDurationSeconds(v *int) *UsageLogUpdate {
if v != nil {
_u.SetVideoDurationSeconds(*v)
}
return _u
}
// AddVideoDurationSeconds adds value to the "video_duration_seconds" field.
func (_u *UsageLogUpdate) AddVideoDurationSeconds(v int) *UsageLogUpdate {
_u.mutation.AddVideoDurationSeconds(v)
return _u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (_u *UsageLogUpdate) ClearVideoDurationSeconds() *UsageLogUpdate {
_u.mutation.ClearVideoDurationSeconds()
return _u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_u *UsageLogUpdate) SetCacheTTLOverridden(v bool) *UsageLogUpdate {
_u.mutation.SetCacheTTLOverridden(v)
@@ -979,6 +1047,11 @@ func (_u *UsageLogUpdate) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if v, ok := _u.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "UsageLog.user"`)
}
@@ -1210,6 +1283,27 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.ImageSizeBreakdownCleared() {
_spec.ClearField(usagelog.FieldImageSizeBreakdown, field.TypeJSON)
}
if value, ok := _u.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoCount(); ok {
_spec.AddField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
}
if _u.mutation.VideoResolutionCleared() {
_spec.ClearField(usagelog.FieldVideoResolution, field.TypeString)
}
if value, ok := _u.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoDurationSeconds(); ok {
_spec.AddField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if _u.mutation.VideoDurationSecondsCleared() {
_spec.ClearField(usagelog.FieldVideoDurationSeconds, field.TypeInt)
}
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
}
@@ -2157,6 +2251,74 @@ func (_u *UsageLogUpdateOne) ClearImageSizeBreakdown() *UsageLogUpdateOne {
return _u
}
// SetVideoCount sets the "video_count" field.
func (_u *UsageLogUpdateOne) SetVideoCount(v int) *UsageLogUpdateOne {
_u.mutation.ResetVideoCount()
_u.mutation.SetVideoCount(v)
return _u
}
// SetNillableVideoCount sets the "video_count" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoCount(v *int) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoCount(*v)
}
return _u
}
// AddVideoCount adds value to the "video_count" field.
func (_u *UsageLogUpdateOne) AddVideoCount(v int) *UsageLogUpdateOne {
_u.mutation.AddVideoCount(v)
return _u
}
// SetVideoResolution sets the "video_resolution" field.
func (_u *UsageLogUpdateOne) SetVideoResolution(v string) *UsageLogUpdateOne {
_u.mutation.SetVideoResolution(v)
return _u
}
// SetNillableVideoResolution sets the "video_resolution" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoResolution(v *string) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoResolution(*v)
}
return _u
}
// ClearVideoResolution clears the value of the "video_resolution" field.
func (_u *UsageLogUpdateOne) ClearVideoResolution() *UsageLogUpdateOne {
_u.mutation.ClearVideoResolution()
return _u
}
// SetVideoDurationSeconds sets the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) SetVideoDurationSeconds(v int) *UsageLogUpdateOne {
_u.mutation.ResetVideoDurationSeconds()
_u.mutation.SetVideoDurationSeconds(v)
return _u
}
// SetNillableVideoDurationSeconds sets the "video_duration_seconds" field if the given value is not nil.
func (_u *UsageLogUpdateOne) SetNillableVideoDurationSeconds(v *int) *UsageLogUpdateOne {
if v != nil {
_u.SetVideoDurationSeconds(*v)
}
return _u
}
// AddVideoDurationSeconds adds value to the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) AddVideoDurationSeconds(v int) *UsageLogUpdateOne {
_u.mutation.AddVideoDurationSeconds(v)
return _u
}
// ClearVideoDurationSeconds clears the value of the "video_duration_seconds" field.
func (_u *UsageLogUpdateOne) ClearVideoDurationSeconds() *UsageLogUpdateOne {
_u.mutation.ClearVideoDurationSeconds()
return _u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (_u *UsageLogUpdateOne) SetCacheTTLOverridden(v bool) *UsageLogUpdateOne {
_u.mutation.SetCacheTTLOverridden(v)
@@ -2338,6 +2500,11 @@ func (_u *UsageLogUpdateOne) check() error {
return &ValidationError{Name: "image_size_source", err: fmt.Errorf(`ent: validator failed for field "UsageLog.image_size_source": %w`, err)}
}
}
if v, ok := _u.mutation.VideoResolution(); ok {
if err := usagelog.VideoResolutionValidator(v); err != nil {
return &ValidationError{Name: "video_resolution", err: fmt.Errorf(`ent: validator failed for field "UsageLog.video_resolution": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "UsageLog.user"`)
}
@@ -2586,6 +2753,27 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
if _u.mutation.ImageSizeBreakdownCleared() {
_spec.ClearField(usagelog.FieldImageSizeBreakdown, field.TypeJSON)
}
if value, ok := _u.mutation.VideoCount(); ok {
_spec.SetField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoCount(); ok {
_spec.AddField(usagelog.FieldVideoCount, field.TypeInt, value)
}
if value, ok := _u.mutation.VideoResolution(); ok {
_spec.SetField(usagelog.FieldVideoResolution, field.TypeString, value)
}
if _u.mutation.VideoResolutionCleared() {
_spec.ClearField(usagelog.FieldVideoResolution, field.TypeString)
}
if value, ok := _u.mutation.VideoDurationSeconds(); ok {
_spec.SetField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedVideoDurationSeconds(); ok {
_spec.AddField(usagelog.FieldVideoDurationSeconds, field.TypeInt, value)
}
if _u.mutation.VideoDurationSecondsCleared() {
_spec.ClearField(usagelog.FieldVideoDurationSeconds, field.TypeInt)
}
if value, ok := _u.mutation.CacheTTLOverridden(); ok {
_spec.SetField(usagelog.FieldCacheTTLOverridden, field.TypeBool, value)
}
+12 -1
View File
@@ -31,6 +31,8 @@ type User struct {
Role string `json:"role,omitempty"`
// Balance holds the value of the "balance" field.
Balance float64 `json:"balance,omitempty"`
// FrozenBalance holds the value of the "frozen_balance" field.
FrozenBalance float64 `json:"frozen_balance,omitempty"`
// Concurrency holds the value of the "concurrency" field.
Concurrency int `json:"concurrency,omitempty"`
// Status holds the value of the "status" field.
@@ -237,7 +239,7 @@ func (*User) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case user.FieldTotpEnabled, user.FieldBalanceNotifyEnabled:
values[i] = new(sql.NullBool)
case user.FieldBalance, user.FieldBalanceNotifyThreshold, user.FieldTotalRecharged:
case user.FieldBalance, user.FieldFrozenBalance, user.FieldBalanceNotifyThreshold, user.FieldTotalRecharged:
values[i] = new(sql.NullFloat64)
case user.FieldID, user.FieldConcurrency, user.FieldRpmLimit:
values[i] = new(sql.NullInt64)
@@ -309,6 +311,12 @@ func (_m *User) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Balance = value.Float64
}
case user.FieldFrozenBalance:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field frozen_balance", values[i])
} else if value.Valid {
_m.FrozenBalance = value.Float64
}
case user.FieldConcurrency:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field concurrency", values[i])
@@ -539,6 +547,9 @@ func (_m *User) String() string {
builder.WriteString("balance=")
builder.WriteString(fmt.Sprintf("%v", _m.Balance))
builder.WriteString(", ")
builder.WriteString("frozen_balance=")
builder.WriteString(fmt.Sprintf("%v", _m.FrozenBalance))
builder.WriteString(", ")
builder.WriteString("concurrency=")
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
builder.WriteString(", ")
+10
View File
@@ -29,6 +29,8 @@ const (
FieldRole = "role"
// FieldBalance holds the string denoting the balance field in the database.
FieldBalance = "balance"
// FieldFrozenBalance holds the string denoting the frozen_balance field in the database.
FieldFrozenBalance = "frozen_balance"
// FieldConcurrency holds the string denoting the concurrency field in the database.
FieldConcurrency = "concurrency"
// FieldStatus holds the string denoting the status field in the database.
@@ -199,6 +201,7 @@ var Columns = []string{
FieldPasswordHash,
FieldRole,
FieldBalance,
FieldFrozenBalance,
FieldConcurrency,
FieldStatus,
FieldUsername,
@@ -257,6 +260,8 @@ var (
RoleValidator func(string) error
// DefaultBalance holds the default value on creation for the "balance" field.
DefaultBalance float64
// DefaultFrozenBalance holds the default value on creation for the "frozen_balance" field.
DefaultFrozenBalance float64
// DefaultConcurrency holds the default value on creation for the "concurrency" field.
DefaultConcurrency int
// DefaultStatus holds the default value on creation for the "status" field.
@@ -330,6 +335,11 @@ func ByBalance(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBalance, opts...).ToFunc()
}
// ByFrozenBalance orders the results by the frozen_balance field.
func ByFrozenBalance(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFrozenBalance, opts...).ToFunc()
}
// ByConcurrency orders the results by the concurrency field.
func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
+45
View File
@@ -90,6 +90,11 @@ func Balance(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldBalance, v))
}
// FrozenBalance applies equality check predicate on the "frozen_balance" field. It's identical to FrozenBalanceEQ.
func FrozenBalance(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldFrozenBalance, v))
}
// Concurrency applies equality check predicate on the "concurrency" field. It's identical to ConcurrencyEQ.
func Concurrency(v int) predicate.User {
return predicate.User(sql.FieldEQ(FieldConcurrency, v))
@@ -535,6 +540,46 @@ func BalanceLTE(v float64) predicate.User {
return predicate.User(sql.FieldLTE(FieldBalance, v))
}
// FrozenBalanceEQ applies the EQ predicate on the "frozen_balance" field.
func FrozenBalanceEQ(v float64) predicate.User {
return predicate.User(sql.FieldEQ(FieldFrozenBalance, v))
}
// FrozenBalanceNEQ applies the NEQ predicate on the "frozen_balance" field.
func FrozenBalanceNEQ(v float64) predicate.User {
return predicate.User(sql.FieldNEQ(FieldFrozenBalance, v))
}
// FrozenBalanceIn applies the In predicate on the "frozen_balance" field.
func FrozenBalanceIn(vs ...float64) predicate.User {
return predicate.User(sql.FieldIn(FieldFrozenBalance, vs...))
}
// FrozenBalanceNotIn applies the NotIn predicate on the "frozen_balance" field.
func FrozenBalanceNotIn(vs ...float64) predicate.User {
return predicate.User(sql.FieldNotIn(FieldFrozenBalance, vs...))
}
// FrozenBalanceGT applies the GT predicate on the "frozen_balance" field.
func FrozenBalanceGT(v float64) predicate.User {
return predicate.User(sql.FieldGT(FieldFrozenBalance, v))
}
// FrozenBalanceGTE applies the GTE predicate on the "frozen_balance" field.
func FrozenBalanceGTE(v float64) predicate.User {
return predicate.User(sql.FieldGTE(FieldFrozenBalance, v))
}
// FrozenBalanceLT applies the LT predicate on the "frozen_balance" field.
func FrozenBalanceLT(v float64) predicate.User {
return predicate.User(sql.FieldLT(FieldFrozenBalance, v))
}
// FrozenBalanceLTE applies the LTE predicate on the "frozen_balance" field.
func FrozenBalanceLTE(v float64) predicate.User {
return predicate.User(sql.FieldLTE(FieldFrozenBalance, v))
}
// ConcurrencyEQ applies the EQ predicate on the "concurrency" field.
func ConcurrencyEQ(v int) predicate.User {
return predicate.User(sql.FieldEQ(FieldConcurrency, v))
+85
View File
@@ -116,6 +116,20 @@ func (_c *UserCreate) SetNillableBalance(v *float64) *UserCreate {
return _c
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_c *UserCreate) SetFrozenBalance(v float64) *UserCreate {
_c.mutation.SetFrozenBalance(v)
return _c
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_c *UserCreate) SetNillableFrozenBalance(v *float64) *UserCreate {
if v != nil {
_c.SetFrozenBalance(*v)
}
return _c
}
// SetConcurrency sets the "concurrency" field.
func (_c *UserCreate) SetConcurrency(v int) *UserCreate {
_c.mutation.SetConcurrency(v)
@@ -594,6 +608,10 @@ func (_c *UserCreate) defaults() error {
v := user.DefaultBalance
_c.mutation.SetBalance(v)
}
if _, ok := _c.mutation.FrozenBalance(); !ok {
v := user.DefaultFrozenBalance
_c.mutation.SetFrozenBalance(v)
}
if _, ok := _c.mutation.Concurrency(); !ok {
v := user.DefaultConcurrency
_c.mutation.SetConcurrency(v)
@@ -676,6 +694,9 @@ func (_c *UserCreate) check() error {
if _, ok := _c.mutation.Balance(); !ok {
return &ValidationError{Name: "balance", err: errors.New(`ent: missing required field "User.balance"`)}
}
if _, ok := _c.mutation.FrozenBalance(); !ok {
return &ValidationError{Name: "frozen_balance", err: errors.New(`ent: missing required field "User.frozen_balance"`)}
}
if _, ok := _c.mutation.Concurrency(); !ok {
return &ValidationError{Name: "concurrency", err: errors.New(`ent: missing required field "User.concurrency"`)}
}
@@ -779,6 +800,10 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
_spec.SetField(user.FieldBalance, field.TypeFloat64, value)
_node.Balance = value
}
if value, ok := _c.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
_node.FrozenBalance = value
}
if value, ok := _c.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
_node.Concurrency = value
@@ -1191,6 +1216,24 @@ func (u *UserUpsert) AddBalance(v float64) *UserUpsert {
return u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsert) SetFrozenBalance(v float64) *UserUpsert {
u.Set(user.FieldFrozenBalance, v)
return u
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsert) UpdateFrozenBalance() *UserUpsert {
u.SetExcluded(user.FieldFrozenBalance)
return u
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsert) AddFrozenBalance(v float64) *UserUpsert {
u.Add(user.FieldFrozenBalance, v)
return u
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsert) SetConcurrency(v int) *UserUpsert {
u.Set(user.FieldConcurrency, v)
@@ -1580,6 +1623,27 @@ func (u *UserUpsertOne) UpdateBalance() *UserUpsertOne {
})
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsertOne) SetFrozenBalance(v float64) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.SetFrozenBalance(v)
})
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsertOne) AddFrozenBalance(v float64) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.AddFrozenBalance(v)
})
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsertOne) UpdateFrozenBalance() *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
s.UpdateFrozenBalance()
})
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsertOne) SetConcurrency(v int) *UserUpsertOne {
return u.Update(func(s *UserUpsert) {
@@ -2176,6 +2240,27 @@ func (u *UserUpsertBulk) UpdateBalance() *UserUpsertBulk {
})
}
// SetFrozenBalance sets the "frozen_balance" field.
func (u *UserUpsertBulk) SetFrozenBalance(v float64) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.SetFrozenBalance(v)
})
}
// AddFrozenBalance adds v to the "frozen_balance" field.
func (u *UserUpsertBulk) AddFrozenBalance(v float64) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.AddFrozenBalance(v)
})
}
// UpdateFrozenBalance sets the "frozen_balance" field to the value that was provided on create.
func (u *UserUpsertBulk) UpdateFrozenBalance() *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
s.UpdateFrozenBalance()
})
}
// SetConcurrency sets the "concurrency" field.
func (u *UserUpsertBulk) SetConcurrency(v int) *UserUpsertBulk {
return u.Update(func(s *UserUpsert) {
+54
View File
@@ -129,6 +129,27 @@ func (_u *UserUpdate) AddBalance(v float64) *UserUpdate {
return _u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_u *UserUpdate) SetFrozenBalance(v float64) *UserUpdate {
_u.mutation.ResetFrozenBalance()
_u.mutation.SetFrozenBalance(v)
return _u
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_u *UserUpdate) SetNillableFrozenBalance(v *float64) *UserUpdate {
if v != nil {
_u.SetFrozenBalance(*v)
}
return _u
}
// AddFrozenBalance adds value to the "frozen_balance" field.
func (_u *UserUpdate) AddFrozenBalance(v float64) *UserUpdate {
_u.mutation.AddFrozenBalance(v)
return _u
}
// SetConcurrency sets the "concurrency" field.
func (_u *UserUpdate) SetConcurrency(v int) *UserUpdate {
_u.mutation.ResetConcurrency()
@@ -997,6 +1018,12 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedBalance(); ok {
_spec.AddField(user.FieldBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedFrozenBalance(); ok {
_spec.AddField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
}
@@ -1778,6 +1805,27 @@ func (_u *UserUpdateOne) AddBalance(v float64) *UserUpdateOne {
return _u
}
// SetFrozenBalance sets the "frozen_balance" field.
func (_u *UserUpdateOne) SetFrozenBalance(v float64) *UserUpdateOne {
_u.mutation.ResetFrozenBalance()
_u.mutation.SetFrozenBalance(v)
return _u
}
// SetNillableFrozenBalance sets the "frozen_balance" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillableFrozenBalance(v *float64) *UserUpdateOne {
if v != nil {
_u.SetFrozenBalance(*v)
}
return _u
}
// AddFrozenBalance adds value to the "frozen_balance" field.
func (_u *UserUpdateOne) AddFrozenBalance(v float64) *UserUpdateOne {
_u.mutation.AddFrozenBalance(v)
return _u
}
// SetConcurrency sets the "concurrency" field.
func (_u *UserUpdateOne) SetConcurrency(v int) *UserUpdateOne {
_u.mutation.ResetConcurrency()
@@ -2676,6 +2724,12 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.AddedBalance(); ok {
_spec.AddField(user.FieldBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.FrozenBalance(); ok {
_spec.SetField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedFrozenBalance(); ok {
_spec.AddField(user.FieldFrozenBalance, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Concurrency(); ok {
_spec.SetField(user.FieldConcurrency, field.TypeInt, value)
}
+14 -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=
+154 -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"`
@@ -1732,6 +1783,53 @@ func setDefaults() {
viper.SetDefault("redis.min_idle_conns", 128)
viper.SetDefault("redis.enable_tls", false)
// Batch Image queue
viper.SetDefault("batch_image.enabled", false)
viper.SetDefault("batch_image.max_items_per_job_default", 200)
viper.SetDefault("batch_image.max_items_per_job_trial", 50)
viper.SetDefault("batch_image.max_output_images_per_job", 200)
viper.SetDefault("batch_image.max_output_images_per_item", 4)
viper.SetDefault("batch_image.max_prompt_chars_per_item", 8000)
viper.SetDefault("batch_image.max_reference_images_per_job", 1000)
viper.SetDefault("batch_image.max_reference_inline_bytes_per_job", 134217728)
viper.SetDefault("batch_image.default_response_mime_type", "image/png")
viper.SetDefault("batch_image.default_image_size", "1K")
viper.SetDefault("batch_image.max_download_items_zip", 200)
viper.SetDefault("batch_image.max_download_bytes_per_request", 536870912)
viper.SetDefault("batch_image.max_download_duration_seconds", 600)
viper.SetDefault("batch_image.max_download_concurrency_per_user", 1)
viper.SetDefault("batch_image.input_retention_after_terminal_hours", 24)
viper.SetDefault("batch_image.output_retention_after_terminal_hours", 72)
viper.SetDefault("batch_image.output_retention_max_days", 7)
viper.SetDefault("batch_image.cleanup_interval_minutes", 30)
viper.SetDefault("batch_image.cleanup_batch_size", 100)
viper.SetDefault("batch_image.queue_enabled", false)
viper.SetDefault("batch_image.queue_ready_key", "batch_image:queue:ready")
viper.SetDefault("batch_image.queue_delayed_key", "batch_image:queue:delayed")
viper.SetDefault("batch_image.queue_active_key", "batch_image:queue:active")
viper.SetDefault("batch_image.inflight_key_prefix", "batch_image:queue:inflight:")
viper.SetDefault("batch_image.lock_key_prefix", "batch_image:queue:lock:")
viper.SetDefault("batch_image.idempotency_key_prefix", "batch_image:queue:idem:")
viper.SetDefault("batch_image.inflight_ttl_seconds", 604800)
viper.SetDefault("batch_image.job_lock_ttl_seconds", 300)
viper.SetDefault("batch_image.default_requeue_delay_seconds", 30)
viper.SetDefault("batch_image.error_retry_delay_seconds", 60)
viper.SetDefault("batch_image.lock_conflict_delay_seconds", 5)
viper.SetDefault("batch_image.stale_active_after_seconds", 600)
viper.SetDefault("batch_image.delayed_mover_interval_seconds", 5)
viper.SetDefault("batch_image.recovery_interval_seconds", 300)
viper.SetDefault("batch_image.delayed_move_limit", 100)
viper.SetDefault("batch_image.recover_limit", 100)
viper.SetDefault("batch_image.vertex_enabled", false)
viper.SetDefault("batch_image.vertex_project_id", "")
viper.SetDefault("batch_image.vertex_location", "global")
viper.SetDefault("batch_image.vertex_managed_gcs_bucket", "")
viper.SetDefault("batch_image.vertex_managed_gcs_prefix", "batch-image/{env}/{batch_id}")
viper.SetDefault("batch_image.vertex_input_retention_hours", 24)
viper.SetDefault("batch_image.vertex_output_retention_hours", 72)
viper.SetDefault("batch_image.vertex_batch_prediction_base_url", "")
viper.SetDefault("batch_image.vertex_gcs_base_url", "")
// Ops (vNext)
viper.SetDefault("ops.enabled", true)
viper.SetDefault("ops.use_preaggregated_tables", true)
@@ -2333,6 +2431,61 @@ func (c *Config) Validate() error {
if c.Redis.MinIdleConns > c.Redis.PoolSize {
return fmt.Errorf("redis.min_idle_conns cannot exceed redis.pool_size")
}
if c.BatchImage.QueueEnabled {
if strings.TrimSpace(c.BatchImage.QueueReadyKey) == "" {
return fmt.Errorf("batch_image.queue_ready_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.QueueDelayedKey) == "" {
return fmt.Errorf("batch_image.queue_delayed_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.QueueActiveKey) == "" {
return fmt.Errorf("batch_image.queue_active_key must not be empty")
}
if strings.TrimSpace(c.BatchImage.InflightKeyPrefix) == "" {
return fmt.Errorf("batch_image.inflight_key_prefix must not be empty")
}
if strings.TrimSpace(c.BatchImage.LockKeyPrefix) == "" {
return fmt.Errorf("batch_image.lock_key_prefix must not be empty")
}
if c.BatchImage.InflightTTLSeconds <= 0 {
return fmt.Errorf("batch_image.inflight_ttl_seconds must be positive")
}
if c.BatchImage.JobLockTTLSeconds <= 0 {
return fmt.Errorf("batch_image.job_lock_ttl_seconds must be positive")
}
if c.BatchImage.StaleActiveAfterSeconds <= 0 {
return fmt.Errorf("batch_image.stale_active_after_seconds must be positive")
}
if c.BatchImage.DelayedMoveLimit <= 0 {
return fmt.Errorf("batch_image.delayed_move_limit must be positive")
}
if c.BatchImage.RecoverLimit <= 0 {
return fmt.Errorf("batch_image.recover_limit must be positive")
}
}
if c.BatchImage.VertexEnabled {
if strings.TrimSpace(c.BatchImage.VertexManagedGCSBucket) == "" {
return fmt.Errorf("batch_image.vertex_managed_gcs_bucket must not be empty when vertex is enabled")
}
if strings.Contains(c.BatchImage.VertexManagedGCSBucket, "://") {
return fmt.Errorf("batch_image.vertex_managed_gcs_bucket must be a bucket name, not a URI")
}
if strings.TrimSpace(c.BatchImage.VertexLocation) == "" {
return fmt.Errorf("batch_image.vertex_location must not be empty when vertex is enabled")
}
if strings.TrimSpace(c.BatchImage.VertexManagedGCSPrefix) == "" {
return fmt.Errorf("batch_image.vertex_managed_gcs_prefix must not be empty when vertex is enabled")
}
if !strings.Contains(c.BatchImage.VertexManagedGCSPrefix, "{batch_id}") {
return fmt.Errorf("batch_image.vertex_managed_gcs_prefix must contain {batch_id}")
}
if c.BatchImage.VertexInputRetentionHours <= 0 {
return fmt.Errorf("batch_image.vertex_input_retention_hours must be positive")
}
if c.BatchImage.VertexOutputRetentionHours <= 0 {
return fmt.Errorf("batch_image.vertex_output_retention_hours must be positive")
}
}
if c.Dashboard.Enabled {
if c.Dashboard.StatsFreshTTLSeconds <= 0 {
return fmt.Errorf("dashboard_cache.stats_fresh_ttl_seconds must be positive")
+8
View File
@@ -270,6 +270,14 @@ func TestLoadDefaultIdempotencyConfig(t *testing.T) {
}
}
func TestLoadDefaultBatchImageQueueDisabled(t *testing.T) {
resetViperWithJWTSecret(t)
cfg, err := Load()
require.NoError(t, err)
require.False(t, cfg.BatchImage.QueueEnabled)
}
func TestLoadIdempotencyConfigFromEnv(t *testing.T) {
resetViperWithJWTSecret(t)
t.Setenv("IDEMPOTENCY_OBSERVE_ONLY", "false")
@@ -485,6 +485,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 +522,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 +532,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)
}
@@ -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)
@@ -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
@@ -351,6 +353,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 +361,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
@@ -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)
}
@@ -98,7 +98,7 @@ func TestGrokOAuthHandlerQueryQuotaProbesUpstream(t *testing.T) {
require.Contains(t, rec.Body.String(), `"source":"active_probe"`)
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, xai.DefaultCLIBaseURL+"/responses", upstream.lastReq.URL.String())
require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization"))
require.Contains(t, string(upstream.lastBody), `"store":false`)
require.NotNil(t, repo.updates[42])
@@ -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,
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
}
@@ -0,0 +1,347 @@
package admin
import (
"html"
"strings"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// TestSMTPRequest 测试SMTP连接请求
type TestSMTPRequest struct {
SMTPHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPUseTLS bool `json:"smtp_use_tls"`
}
// TestSMTPConnection 测试SMTP连接
// POST /api/v1/admin/settings/test-smtp
func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
var req TestSMTPRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
var savedConfig *service.SMTPConfig
if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
savedConfig = cfg
}
if req.SMTPHost == "" && savedConfig != nil {
req.SMTPHost = savedConfig.Host
}
if req.SMTPPort <= 0 {
if savedConfig != nil && savedConfig.Port > 0 {
req.SMTPPort = savedConfig.Port
} else {
req.SMTPPort = 587
}
}
if req.SMTPUsername == "" && savedConfig != nil {
req.SMTPUsername = savedConfig.Username
}
password := strings.TrimSpace(req.SMTPPassword)
if password == "" && savedConfig != nil {
password = savedConfig.Password
}
if req.SMTPHost == "" {
response.BadRequest(c, "SMTP host is required")
return
}
config := &service.SMTPConfig{
Host: req.SMTPHost,
Port: req.SMTPPort,
Username: req.SMTPUsername,
Password: password,
UseTLS: req.SMTPUseTLS,
}
err := h.emailService.TestSMTPConnectionWithConfig(config)
if err != nil {
response.BadRequest(c, "SMTP connection test failed: "+err.Error())
return
}
response.Success(c, gin.H{"message": "SMTP connection successful"})
}
// SendTestEmailRequest 发送测试邮件请求
type SendTestEmailRequest struct {
Email string `json:"email" binding:"required,email"`
SMTPHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
SMTPFrom string `json:"smtp_from_email"`
SMTPFromName string `json:"smtp_from_name"`
SMTPUseTLS bool `json:"smtp_use_tls"`
}
// SendTestEmail 发送测试邮件
// POST /api/v1/admin/settings/send-test-email
func (h *SettingHandler) SendTestEmail(c *gin.Context) {
var req SendTestEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
req.SMTPFrom = strings.TrimSpace(req.SMTPFrom)
req.SMTPFromName = strings.TrimSpace(req.SMTPFromName)
var savedConfig *service.SMTPConfig
if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
savedConfig = cfg
}
if req.SMTPHost == "" && savedConfig != nil {
req.SMTPHost = savedConfig.Host
}
if req.SMTPPort <= 0 {
if savedConfig != nil && savedConfig.Port > 0 {
req.SMTPPort = savedConfig.Port
} else {
req.SMTPPort = 587
}
}
if req.SMTPUsername == "" && savedConfig != nil {
req.SMTPUsername = savedConfig.Username
}
password := strings.TrimSpace(req.SMTPPassword)
if password == "" && savedConfig != nil {
password = savedConfig.Password
}
if req.SMTPFrom == "" && savedConfig != nil {
req.SMTPFrom = savedConfig.From
}
if req.SMTPFromName == "" && savedConfig != nil {
req.SMTPFromName = savedConfig.FromName
}
if req.SMTPHost == "" {
response.BadRequest(c, "SMTP host is required")
return
}
config := &service.SMTPConfig{
Host: req.SMTPHost,
Port: req.SMTPPort,
Username: req.SMTPUsername,
Password: password,
From: req.SMTPFrom,
FromName: req.SMTPFromName,
UseTLS: req.SMTPUseTLS,
}
siteName := h.settingService.GetSiteName(c.Request.Context())
subject := "[" + siteName + "] Test Email"
body := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; }
.content { padding: 40px 30px; text-align: center; }
.success { color: #10b981; font-size: 48px; margin-bottom: 20px; }
.footer { background-color: #f8f9fa; padding: 20px; text-align: center; color: #999; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>` + html.EscapeString(siteName) + `</h1>
</div>
<div class="content">
<div class="success">✓</div>
<h2>Email Configuration Successful!</h2>
<p>This is a test email to verify your SMTP settings are working correctly.</p>
</div>
<div class="footer">
<p>This is an automated test message.</p>
</div>
</div>
</body>
</html>
`
if err := h.emailService.SendEmailWithConfig(config, req.Email, subject, body); err != nil {
response.BadRequest(c, "Failed to send test email: "+err.Error())
return
}
response.Success(c, gin.H{"message": "Test email sent successfully"})
}
// ListEmailTemplates returns all editable notification email templates.
// GET /api/v1/admin/settings/email-templates
func (h *SettingHandler) ListEmailTemplates(c *gin.Context) {
if h.notificationEmailService == nil {
response.InternalError(c, "notification email service is not configured")
return
}
events := h.notificationEmailService.ListEventInfos()
templates, err := h.notificationEmailService.ListTemplates(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.EmailTemplateListResponse{
Events: emailTemplateEventOptionsToDTO(events),
Locales: h.notificationEmailService.SupportedLocales(),
Templates: emailTemplateSummariesToDTO(templates),
Placeholders: emailTemplatePlaceholderUnion(events),
})
}
// GetEmailTemplate returns one editable notification email template.
// GET /api/v1/admin/settings/email-templates/:event/:locale
func (h *SettingHandler) GetEmailTemplate(c *gin.Context) {
if h.notificationEmailService == nil {
response.InternalError(c, "notification email service is not configured")
return
}
tmpl, err := h.notificationEmailService.GetTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"))
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.Success(c, emailTemplateDetailToDTO(tmpl))
}
// UpdateEmailTemplate saves an override for one event/locale template.
// PUT /api/v1/admin/settings/email-templates/:event/:locale
func (h *SettingHandler) UpdateEmailTemplate(c *gin.Context) {
if h.notificationEmailService == nil {
response.InternalError(c, "notification email service is not configured")
return
}
var req dto.UpdateEmailTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
tmpl, err := h.notificationEmailService.UpdateTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"), req.Subject, req.HTML)
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.Success(c, emailTemplateDetailToDTO(tmpl))
}
// RestoreOfficialEmailTemplate removes an override and returns the built-in template.
// POST /api/v1/admin/settings/email-templates/:event/:locale/restore-official
func (h *SettingHandler) RestoreOfficialEmailTemplate(c *gin.Context) {
if h.notificationEmailService == nil {
response.InternalError(c, "notification email service is not configured")
return
}
tmpl, err := h.notificationEmailService.RestoreOfficialTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"))
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.Success(c, emailTemplateDetailToDTO(tmpl))
}
// PreviewEmailTemplate renders a template with safe sample variables without saving it.
// POST /api/v1/admin/settings/email-templates/preview
func (h *SettingHandler) PreviewEmailTemplate(c *gin.Context) {
if h.notificationEmailService == nil {
response.InternalError(c, "notification email service is not configured")
return
}
var req dto.PreviewEmailTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
preview, err := h.notificationEmailService.PreviewTemplate(c.Request.Context(), service.NotificationEmailPreviewInput{
Event: req.Event,
Locale: req.Locale,
Subject: req.Subject,
HTML: req.HTML,
Variables: req.Variables,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.Success(c, dto.EmailTemplatePreviewResponse{Subject: preview.Subject, HTML: preview.HTML})
}
func emailTemplateEventOptionsToDTO(events []service.NotificationEmailEventInfo) []dto.EmailTemplateEventOption {
items := make([]dto.EmailTemplateEventOption, 0, len(events))
for _, event := range events {
items = append(items, dto.EmailTemplateEventOption{
Value: event.Event,
Label: event.Label,
Description: event.Description,
Category: event.Category,
Optional: event.Optional,
})
}
return items
}
func emailTemplateSummariesToDTO(templates []service.NotificationEmailTemplate) []dto.EmailTemplateSummary {
items := make([]dto.EmailTemplateSummary, 0, len(templates))
for _, tmpl := range templates {
items = append(items, dto.EmailTemplateSummary{
Event: tmpl.Event,
Locale: tmpl.Locale,
Subject: tmpl.Subject,
IsCustom: tmpl.IsCustom,
UpdatedAt: emailTemplateUpdatedAt(tmpl),
})
}
return items
}
func emailTemplateDetailToDTO(tmpl service.NotificationEmailTemplate) dto.EmailTemplateDetail {
return dto.EmailTemplateDetail{
Event: tmpl.Event,
Locale: tmpl.Locale,
Subject: tmpl.Subject,
HTML: tmpl.HTML,
IsCustom: tmpl.IsCustom,
UpdatedAt: emailTemplateUpdatedAt(tmpl),
Placeholders: tmpl.Placeholders,
}
}
func emailTemplateUpdatedAt(tmpl service.NotificationEmailTemplate) string {
if tmpl.UpdatedAt == nil {
return ""
}
return tmpl.UpdatedAt.Format("2006-01-02T15:04:05Z07:00")
}
func emailTemplatePlaceholderUnion(events []service.NotificationEmailEventInfo) []string {
seen := make(map[string]struct{})
placeholders := make([]string, 0)
for _, event := range events {
for _, placeholder := range event.Placeholders {
if _, ok := seen[placeholder]; ok {
continue
}
seen[placeholder] = struct{}{}
placeholders = append(placeholders, placeholder)
}
}
return placeholders
}
@@ -0,0 +1,445 @@
package admin
import (
"strings"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// GetAdminAPIKey 获取管理员 API Key 状态
// GET /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) {
maskedKey, exists, err := h.settingService.GetAdminAPIKeyStatus(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{
"exists": exists,
"masked_key": maskedKey,
})
}
// RegenerateAdminAPIKey 生成/重新生成管理员 API Key
// POST /api/v1/admin/settings/admin-api-key/regenerate
func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) {
key, err := h.settingService.GenerateAdminAPIKey(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{
"key": key, // 完整 key 只在生成时返回一次
})
}
// DeleteAdminAPIKey 删除管理员 API Key
// DELETE /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) {
if err := h.settingService.DeleteAdminAPIKey(c.Request.Context()); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "Admin API key deleted"})
}
// GetOverloadCooldownSettings 获取529过载冷却配置
// GET /api/v1/admin/settings/overload-cooldown
func (h *SettingHandler) GetOverloadCooldownSettings(c *gin.Context) {
settings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.OverloadCooldownSettings{
Enabled: settings.Enabled,
CooldownMinutes: settings.CooldownMinutes,
})
}
// UpdateOverloadCooldownSettingsRequest 更新529过载冷却配置请求
type UpdateOverloadCooldownSettingsRequest struct {
Enabled bool `json:"enabled"`
CooldownMinutes int `json:"cooldown_minutes"`
}
// UpdateOverloadCooldownSettings 更新529过载冷却配置
// PUT /api/v1/admin/settings/overload-cooldown
func (h *SettingHandler) UpdateOverloadCooldownSettings(c *gin.Context) {
var req UpdateOverloadCooldownSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
settings := &service.OverloadCooldownSettings{
Enabled: req.Enabled,
CooldownMinutes: req.CooldownMinutes,
}
if err := h.settingService.SetOverloadCooldownSettings(c.Request.Context(), settings); err != nil {
response.BadRequest(c, err.Error())
return
}
updatedSettings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.OverloadCooldownSettings{
Enabled: updatedSettings.Enabled,
CooldownMinutes: updatedSettings.CooldownMinutes,
})
}
// GetRateLimit429CooldownSettings 获取429默认回避配置
// GET /api/v1/admin/settings/rate-limit-429-cooldown
func (h *SettingHandler) GetRateLimit429CooldownSettings(c *gin.Context) {
settings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.RateLimit429CooldownSettings{
Enabled: settings.Enabled,
CooldownSeconds: settings.CooldownSeconds,
})
}
// UpdateRateLimit429CooldownSettingsRequest 更新429默认回避配置请求
type UpdateRateLimit429CooldownSettingsRequest struct {
Enabled bool `json:"enabled"`
CooldownSeconds int `json:"cooldown_seconds"`
}
// UpdateRateLimit429CooldownSettings 更新429默认回避配置
// PUT /api/v1/admin/settings/rate-limit-429-cooldown
func (h *SettingHandler) UpdateRateLimit429CooldownSettings(c *gin.Context) {
var req UpdateRateLimit429CooldownSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
settings := &service.RateLimit429CooldownSettings{
Enabled: req.Enabled,
CooldownSeconds: req.CooldownSeconds,
}
if err := h.settingService.SetRateLimit429CooldownSettings(c.Request.Context(), settings); err != nil {
response.BadRequest(c, err.Error())
return
}
updatedSettings, err := h.settingService.GetRateLimit429CooldownSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.RateLimit429CooldownSettings{
Enabled: updatedSettings.Enabled,
CooldownSeconds: updatedSettings.CooldownSeconds,
})
}
// GetStreamTimeoutSettings 获取流超时处理配置
// GET /api/v1/admin/settings/stream-timeout
func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
settings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.StreamTimeoutSettings{
Enabled: settings.Enabled,
Action: settings.Action,
TempUnschedMinutes: settings.TempUnschedMinutes,
ThresholdCount: settings.ThresholdCount,
ThresholdWindowMinutes: settings.ThresholdWindowMinutes,
})
}
// GetRectifierSettings 获取请求整流器配置
// GET /api/v1/admin/settings/rectifier
func (h *SettingHandler) GetRectifierSettings(c *gin.Context) {
settings, err := h.settingService.GetRectifierSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
patterns := settings.APIKeySignaturePatterns
if patterns == nil {
patterns = []string{}
}
response.Success(c, dto.RectifierSettings{
Enabled: settings.Enabled,
ThinkingSignatureEnabled: settings.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: settings.ThinkingBudgetEnabled,
APIKeySignatureEnabled: settings.APIKeySignatureEnabled,
APIKeySignaturePatterns: patterns,
})
}
// UpdateRectifierSettingsRequest 更新整流器配置请求
type UpdateRectifierSettingsRequest struct {
Enabled bool `json:"enabled"`
ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
APIKeySignatureEnabled bool `json:"apikey_signature_enabled"`
APIKeySignaturePatterns []string `json:"apikey_signature_patterns"`
}
// UpdateRectifierSettings 更新请求整流器配置
// PUT /api/v1/admin/settings/rectifier
func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) {
var req UpdateRectifierSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
// 校验并清理自定义匹配关键词
const maxPatterns = 50
const maxPatternLen = 500
if len(req.APIKeySignaturePatterns) > maxPatterns {
response.BadRequest(c, "Too many signature patterns (max 50)")
return
}
var cleanedPatterns []string
for _, p := range req.APIKeySignaturePatterns {
p = strings.TrimSpace(p)
if p == "" {
continue
}
if len(p) > maxPatternLen {
response.BadRequest(c, "Signature pattern too long (max 500 characters)")
return
}
cleanedPatterns = append(cleanedPatterns, p)
}
settings := &service.RectifierSettings{
Enabled: req.Enabled,
ThinkingSignatureEnabled: req.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: req.ThinkingBudgetEnabled,
APIKeySignatureEnabled: req.APIKeySignatureEnabled,
APIKeySignaturePatterns: cleanedPatterns,
}
if err := h.settingService.SetRectifierSettings(c.Request.Context(), settings); err != nil {
response.BadRequest(c, err.Error())
return
}
// 重新获取设置返回
updatedSettings, err := h.settingService.GetRectifierSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
updatedPatterns := updatedSettings.APIKeySignaturePatterns
if updatedPatterns == nil {
updatedPatterns = []string{}
}
response.Success(c, dto.RectifierSettings{
Enabled: updatedSettings.Enabled,
ThinkingSignatureEnabled: updatedSettings.ThinkingSignatureEnabled,
ThinkingBudgetEnabled: updatedSettings.ThinkingBudgetEnabled,
APIKeySignatureEnabled: updatedSettings.APIKeySignatureEnabled,
APIKeySignaturePatterns: updatedPatterns,
})
}
// GetBetaPolicySettings 获取 Beta 策略配置
// GET /api/v1/admin/settings/beta-policy
func (h *SettingHandler) GetBetaPolicySettings(c *gin.Context) {
settings, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
rules := make([]dto.BetaPolicyRule, len(settings.Rules))
for i, r := range settings.Rules {
rules[i] = dto.BetaPolicyRule(r)
}
response.Success(c, dto.BetaPolicySettings{Rules: rules})
}
// UpdateBetaPolicySettingsRequest 更新 Beta 策略配置请求
type UpdateBetaPolicySettingsRequest struct {
Rules []dto.BetaPolicyRule `json:"rules"`
}
// UpdateBetaPolicySettings 更新 Beta 策略配置
// PUT /api/v1/admin/settings/beta-policy
func (h *SettingHandler) UpdateBetaPolicySettings(c *gin.Context) {
var req UpdateBetaPolicySettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
rules := make([]service.BetaPolicyRule, len(req.Rules))
for i, r := range req.Rules {
rules[i] = service.BetaPolicyRule(r)
}
settings := &service.BetaPolicySettings{Rules: rules}
if err := h.settingService.SetBetaPolicySettings(c.Request.Context(), settings); err != nil {
response.BadRequest(c, err.Error())
return
}
// Re-fetch to return updated settings
updated, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
outRules := make([]dto.BetaPolicyRule, len(updated.Rules))
for i, r := range updated.Rules {
outRules[i] = dto.BetaPolicyRule(r)
}
response.Success(c, dto.BetaPolicySettings{Rules: outRules})
}
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
type UpdateStreamTimeoutSettingsRequest struct {
Enabled bool `json:"enabled"`
Action string `json:"action"`
TempUnschedMinutes int `json:"temp_unsched_minutes"`
ThresholdCount int `json:"threshold_count"`
ThresholdWindowMinutes int `json:"threshold_window_minutes"`
}
// UpdateStreamTimeoutSettings 更新流超时处理配置
// PUT /api/v1/admin/settings/stream-timeout
func (h *SettingHandler) UpdateStreamTimeoutSettings(c *gin.Context) {
var req UpdateStreamTimeoutSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
settings := &service.StreamTimeoutSettings{
Enabled: req.Enabled,
Action: req.Action,
TempUnschedMinutes: req.TempUnschedMinutes,
ThresholdCount: req.ThresholdCount,
ThresholdWindowMinutes: req.ThresholdWindowMinutes,
}
if err := h.settingService.SetStreamTimeoutSettings(c.Request.Context(), settings); err != nil {
response.BadRequest(c, err.Error())
return
}
// 重新获取设置返回
updatedSettings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, dto.StreamTimeoutSettings{
Enabled: updatedSettings.Enabled,
Action: updatedSettings.Action,
TempUnschedMinutes: updatedSettings.TempUnschedMinutes,
ThresholdCount: updatedSettings.ThresholdCount,
ThresholdWindowMinutes: updatedSettings.ThresholdWindowMinutes,
})
}
// GetWebSearchEmulationConfig 获取 Web Search 模拟配置
// GET /api/v1/admin/settings/web-search-emulation
func (h *SettingHandler) GetWebSearchEmulationConfig(c *gin.Context) {
cfg, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), cfg))
}
// UpdateWebSearchEmulationConfig 更新 Web Search 模拟配置
// PUT /api/v1/admin/settings/web-search-emulation
func (h *SettingHandler) UpdateWebSearchEmulationConfig(c *gin.Context) {
var cfg service.WebSearchEmulationConfig
if err := c.ShouldBindJSON(&cfg); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := h.settingService.SaveWebSearchEmulationConfig(c.Request.Context(), &cfg); err != nil {
response.ErrorFrom(c, err)
return
}
// Re-read (with sanitized api keys) to return current state
updated, err := h.settingService.GetWebSearchEmulationConfig(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, service.PopulateWebSearchUsage(c.Request.Context(), updated))
}
// ResetWebSearchUsage 重置指定 provider 的配额用量
// POST /api/v1/admin/settings/web-search-emulation/reset-usage
func (h *SettingHandler) ResetWebSearchUsage(c *gin.Context) {
var req struct {
ProviderType string `json:"provider_type"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if req.ProviderType == "" {
response.BadRequest(c, "provider_type is required")
return
}
if err := service.ResetWebSearchUsage(c.Request.Context(), req.ProviderType); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, nil)
}
// TestWebSearchEmulation 测试 Web Search 搜索
// POST /api/v1/admin/settings/web-search-emulation/test
func (h *SettingHandler) TestWebSearchEmulation(c *gin.Context) {
var req struct {
Query string `json:"query"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if strings.TrimSpace(req.Query) == "" {
req.Query = "搜索今年世界大事件"
}
result, err := service.TestWebSearch(c.Request.Context(), req.Query)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, result)
}
File diff suppressed because it is too large Load Diff
@@ -26,6 +26,8 @@ type systemUpdateService interface {
CheckUpdate(ctx context.Context, force bool) (*service.UpdateInfo, error)
PerformUpdate(ctx context.Context) error
Rollback() error
ListRollbackVersions(ctx context.Context) ([]service.RollbackVersion, error)
RollbackToVersion(ctx context.Context, version string) error
}
// NewSystemHandler creates a new SystemHandler
@@ -102,11 +104,42 @@ func (h *SystemHandler) PerformUpdate(c *gin.Context) {
})
}
// Rollback restores the previous version
// GetRollbackVersions lists versions available for rollback
// GET /api/v1/admin/system/rollback-versions
func (h *SystemHandler) GetRollbackVersions(c *gin.Context) {
versions, err := h.updateSvc.ListRollbackVersions(c.Request.Context())
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, gin.H{
"versions": versions,
})
}
// Rollback restores a previous version.
// Without a body (or with an empty version) it restores the local .backup binary
// left by the last in-place update. With {"version": "x.y.z"} it downloads and
// installs that specific release (must be one of the recent rollback versions).
// POST /api/v1/admin/system/rollback
func (h *SystemHandler) Rollback(c *gin.Context) {
operationID := buildSystemOperationID(c, "rollback")
payload := gin.H{"operation_id": operationID}
var req struct {
Version string `json:"version"`
}
if c.Request.Body != nil && c.Request.ContentLength > 0 {
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "invalid request body")
return
}
}
targetVersion := strings.TrimSpace(req.Version)
operation := "rollback"
if targetVersion != "" {
operation = "rollback:" + targetVersion
}
operationID := buildSystemOperationID(c, operation)
payload := gin.H{"operation_id": operationID, "version": targetVersion}
executeAdminIdempotentJSON(c, "admin.system.rollback", payload, service.DefaultSystemOperationIdempotencyTTL(), func(ctx context.Context) (any, error) {
lock, release, err := h.acquireSystemLock(ctx, operationID)
if err != nil {
@@ -118,7 +151,12 @@ func (h *SystemHandler) Rollback(c *gin.Context) {
release(releaseReason, succeeded)
}()
if err := h.updateSvc.Rollback(); err != nil {
if targetVersion != "" {
err = h.updateSvc.RollbackToVersion(ctx, targetVersion)
} else {
err = h.updateSvc.Rollback()
}
if err != nil {
releaseReason = "SYSTEM_ROLLBACK_FAILED"
return nil, err
}
@@ -127,6 +165,7 @@ func (h *SystemHandler) Rollback(c *gin.Context) {
return gin.H{
"message": "Rollback completed. Please restart the service.",
"need_restart": true,
"version": targetVersion,
"operation_id": lock.OperationID(),
}, nil
})
@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
@@ -17,11 +18,18 @@ import (
)
type systemHandlerUpdateServiceStub struct {
performErr error
updateInfo *service.UpdateInfo
checkErr error
checkForces []bool
performCall int
performErr error
updateInfo *service.UpdateInfo
checkErr error
checkForces []bool
performCall int
rollbackCall int
rollbackToCall int
rollbackToVersions []string
rollbackToErr error
rollbackVersions []service.RollbackVersion
rollbackVersionsErr error
rollbackVersionsCall int
}
func (s *systemHandlerUpdateServiceStub) CheckUpdate(_ context.Context, force bool) (*service.UpdateInfo, error) {
@@ -35,9 +43,21 @@ func (s *systemHandlerUpdateServiceStub) PerformUpdate(context.Context) error {
}
func (s *systemHandlerUpdateServiceStub) Rollback() error {
s.rollbackCall++
return nil
}
func (s *systemHandlerUpdateServiceStub) ListRollbackVersions(context.Context) ([]service.RollbackVersion, error) {
s.rollbackVersionsCall++
return s.rollbackVersions, s.rollbackVersionsErr
}
func (s *systemHandlerUpdateServiceStub) RollbackToVersion(_ context.Context, version string) error {
s.rollbackToCall++
s.rollbackToVersions = append(s.rollbackToVersions, version)
return s.rollbackToErr
}
type systemUpdateResponseEnvelope struct {
Code int `json:"code"`
Message string `json:"message"`
@@ -71,6 +91,8 @@ func newSystemHandlerTestRouter(t *testing.T, updateSvc *systemHandlerUpdateServ
router := gin.New()
router.POST("/api/v1/admin/system/update", handler.PerformUpdate)
router.POST("/api/v1/admin/system/rollback", handler.Rollback)
router.GET("/api/v1/admin/system/rollback-versions", handler.GetRollbackVersions)
return router
}
@@ -142,3 +164,104 @@ func TestSystemHandlerPerformUpdateFailureStillReturnsInternalError(t *testing.T
require.Equal(t, http.StatusInternalServerError, body.Code)
require.Equal(t, "internal error", body.Message)
}
func TestSystemHandlerRollbackWithoutBodyUsesLegacyBackup(t *testing.T) {
updateSvc := &systemHandlerUpdateServiceStub{}
repo := newMemoryIdempotencyRepoStub()
router := newSystemHandlerTestRouter(t, updateSvc, repo)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/system/rollback", nil)
req.Header.Set("Idempotency-Key", "legacy-rollback")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, 1, updateSvc.rollbackCall)
require.Equal(t, 0, updateSvc.rollbackToCall)
requireSystemLockStatus(t, repo, service.IdempotencyStatusSucceeded)
}
func TestSystemHandlerRollbackWithVersionCallsRollbackToVersion(t *testing.T) {
updateSvc := &systemHandlerUpdateServiceStub{}
repo := newMemoryIdempotencyRepoStub()
router := newSystemHandlerTestRouter(t, updateSvc, repo)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/system/rollback",
strings.NewReader(`{"version":"0.1.146"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "rollback-to-146")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, 0, updateSvc.rollbackCall)
require.Equal(t, 1, updateSvc.rollbackToCall)
require.Equal(t, []string{"0.1.146"}, updateSvc.rollbackToVersions)
requireSystemLockStatus(t, repo, service.IdempotencyStatusSucceeded)
var body systemUpdateResponseEnvelope
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
require.Equal(t, 0, body.Code)
require.Equal(t, "Rollback completed. Please restart the service.", body.Data.Message)
}
func TestSystemHandlerRollbackWithDisallowedVersionReturnsBadRequest(t *testing.T) {
updateSvc := &systemHandlerUpdateServiceStub{
rollbackToErr: service.ErrRollbackVersionNotAllowed,
}
repo := newMemoryIdempotencyRepoStub()
router := newSystemHandlerTestRouter(t, updateSvc, repo)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/system/rollback",
strings.NewReader(`{"version":"9.9.9"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "rollback-to-bad")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusBadRequest, rec.Code)
require.Equal(t, 1, updateSvc.rollbackToCall)
}
func TestSystemHandlerGetRollbackVersions(t *testing.T) {
updateSvc := &systemHandlerUpdateServiceStub{
rollbackVersions: []service.RollbackVersion{
{Version: "0.1.146", PublishedAt: "2026-07-07T00:00:00Z", HTMLURL: "https://example.com/v0.1.146"},
{Version: "0.1.145", PublishedAt: "2026-07-06T00:00:00Z", HTMLURL: "https://example.com/v0.1.145"},
},
}
repo := newMemoryIdempotencyRepoStub()
router := newSystemHandlerTestRouter(t, updateSvc, repo)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/system/rollback-versions", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, 1, updateSvc.rollbackVersionsCall)
var body struct {
Code int `json:"code"`
Data struct {
Versions []service.RollbackVersion `json:"versions"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
require.Equal(t, 0, body.Code)
require.Len(t, body.Data.Versions, 2)
require.Equal(t, "0.1.146", body.Data.Versions[0].Version)
}
func TestSystemHandlerGetRollbackVersionsError(t *testing.T) {
updateSvc := &systemHandlerUpdateServiceStub{
rollbackVersionsErr: errors.New("github unavailable"),
}
repo := newMemoryIdempotencyRepoStub()
router := newSystemHandlerTestRouter(t, updateSvc, repo)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/system/rollback-versions", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusInternalServerError, rec.Code)
}
@@ -53,6 +53,7 @@ type CreateUserRequest struct {
Password string `json:"password" binding:"required,min=6"`
Username string `json:"username"`
Notes string `json:"notes"`
Role string `json:"role" binding:"omitempty,oneof=admin user"`
Balance *float64 `json:"balance"`
Concurrency int `json:"concurrency"`
RPMLimit int `json:"rpm_limit"`
@@ -66,6 +67,7 @@ type UpdateUserRequest struct {
Password string `json:"password" binding:"omitempty,min=6"`
Username *string `json:"username"`
Notes *string `json:"notes"`
Role string `json:"role" binding:"omitempty,oneof=admin user"`
Balance *float64 `json:"balance"`
Concurrency *int `json:"concurrency"`
RPMLimit *int `json:"rpm_limit"`
@@ -269,10 +271,12 @@ func (h *UserHandler) Create(c *gin.Context) {
Password: req.Password,
Username: req.Username,
Notes: req.Notes,
Role: req.Role,
Balance: req.Balance,
Concurrency: req.Concurrency,
RPMLimit: req.RPMLimit,
AllowedGroups: req.AllowedGroups,
ActorAdminID: getAdminIDFromContext(c),
})
if err != nil {
response.ErrorFrom(c, err)
@@ -297,18 +301,27 @@ func (h *UserHandler) Update(c *gin.Context) {
return
}
// 防锁死保护:管理员不能把自己降级为普通用户(单管理员场景下会失去后台访问权)。
// 与既有"不能禁用/删除 admin"保护一致。降级其他管理员仍然允许。
if req.Role == service.RoleUser && userID == getAdminIDFromContext(c) {
response.BadRequest(c, "cannot demote yourself from admin")
return
}
// 使用指针类型直接传递,nil 表示未提供该字段
user, err := h.adminService.UpdateUser(c.Request.Context(), userID, &service.UpdateUserInput{
Email: req.Email,
Password: req.Password,
Username: req.Username,
Notes: req.Notes,
Role: req.Role,
Balance: req.Balance,
Concurrency: req.Concurrency,
RPMLimit: req.RPMLimit,
Status: req.Status,
AllowedGroups: req.AllowedGroups,
GroupRates: req.GroupRates,
ActorAdminID: getAdminIDFromContext(c),
})
if err != nil {
response.ErrorFrom(c, err)
@@ -0,0 +1,270 @@
package handler
import (
"errors"
"io"
"net/http"
"strconv"
"strings"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type BatchImageHandler struct {
service *service.BatchImagePublicService
download *service.BatchImageDownloadService
cleanup *service.BatchImageCleanupService
}
func NewBatchImageHandler(service *service.BatchImagePublicService, download *service.BatchImageDownloadService, cleanup *service.BatchImageCleanupService) *BatchImageHandler {
return &BatchImageHandler{service: service, download: download, cleanup: cleanup}
}
func (h *BatchImageHandler) Submit(c *gin.Context) {
var req service.BatchImageSubmitRequest
if err := c.ShouldBindJSON(&req); err != nil {
batchImageError(c, service.ErrBatchImageInvalidItems)
return
}
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
got, err := h.service.Submit(c.Request.Context(), owner, req, c.GetHeader("Idempotency-Key"))
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) Get(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
got, err := h.service.Get(c.Request.Context(), owner, c.Param("id"))
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) List(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
limit, _ := strconv.Atoi(c.Query("limit"))
got, err := h.service.List(c.Request.Context(), owner, service.BatchImageJobsQuery{
Status: c.Query("status"),
TaskName: c.Query("task_name"),
Downloaded: c.Query("downloaded"),
From: c.Query("from"),
To: c.Query("to"),
Limit: limit,
Cursor: c.Query("cursor"),
})
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) Models(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
got, err := h.service.ListModels(c.Request.Context(), owner)
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) Items(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
limit, _ := strconv.Atoi(c.Query("limit"))
got, err := h.service.ListItems(c.Request.Context(), owner, c.Param("id"), service.BatchImageItemsQuery{
Status: c.Query("status"),
Limit: limit,
Cursor: c.Query("cursor"),
})
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) Cancel(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
got, err := h.service.Cancel(c.Request.Context(), owner, c.Param("id"))
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func (h *BatchImageHandler) ItemContent(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
imageIndex := 0
if raw := c.Query("image_index"); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil {
batchImageError(c, service.ErrBatchImageItemImageIndexOutOfRange)
return
}
imageIndex = parsed
}
stream, err := h.download.OpenItemContent(c.Request.Context(), owner, c.Param("id"), c.Param("custom_id"), imageIndex)
if err != nil {
batchImageError(c, err)
return
}
defer func() { _ = stream.Reader.Close() }()
c.Header("Content-Type", stream.ContentType)
c.Header("Content-Disposition", service.BatchImageContentDispositionAttachment(stream.Filename))
c.Header("Cache-Control", "private, max-age=300")
c.Header("X-Content-Type-Options", "nosniff")
if stream.ContentLength != nil && *stream.ContentLength >= 0 {
c.Header("Content-Length", strconv.FormatInt(*stream.ContentLength, 10))
}
c.Status(http.StatusOK)
if _, err := io.Copy(c.Writer, stream.Reader); err != nil {
return
}
h.markDownloadedBestEffort(c, owner)
}
// markDownloadedBestEffort 在响应体已写出后标记下载状态;
// 此时无法再向客户端返回错误,失败只能记日志(不能静默丢弃)。
func (h *BatchImageHandler) markDownloadedBestEffort(c *gin.Context, owner service.BatchImageOwner) {
if err := h.service.MarkDownloaded(c.Request.Context(), owner, c.Param("id")); err != nil {
logger.L().Warn("batch_image.mark_downloaded_failed",
zap.String("batch_id", c.Param("id")),
zap.Error(err),
)
}
}
func (h *BatchImageHandler) Download(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
maxItems, _ := strconv.Atoi(c.Query("max_items"))
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", service.BatchImageContentDispositionAttachment(c.Param("id")+".zip"))
c.Header("Cache-Control", "private, no-store")
c.Header("X-Content-Type-Options", "nosniff")
result, err := h.download.StreamZip(c.Request.Context(), owner, c.Param("id"), service.BatchImageZipOptions{
Status: c.Query("status"),
MaxItems: maxItems,
IncludeManifest: true,
}, c.Writer)
if err != nil {
if result == nil || !c.Writer.Written() {
batchImageError(c, err)
}
return
}
h.markDownloadedBestEffort(c, owner)
}
func (h *BatchImageHandler) DeleteRecord(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
if err := h.service.DeleteRecord(c.Request.Context(), owner, c.Param("id")); err != nil {
batchImageError(c, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *BatchImageHandler) DeleteOutputs(c *gin.Context) {
owner, ok := batchImageOwnerFromContext(c)
if !ok {
batchImageError(c, infraerrors.New(http.StatusUnauthorized, "API_KEY_REQUIRED", "API key is required"))
return
}
got, err := h.cleanup.DeleteOutputsForOwner(c.Request.Context(), owner, c.Param("id"))
if err != nil {
batchImageError(c, err)
return
}
c.JSON(http.StatusOK, got)
}
func batchImageOwnerFromContext(c *gin.Context) (service.BatchImageOwner, bool) {
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if !ok || apiKey == nil || apiKey.ID <= 0 || apiKey.UserID <= 0 {
return service.BatchImageOwner{}, false
}
return service.BatchImageOwner{
UserID: apiKey.UserID,
APIKeyID: apiKey.ID,
GroupID: apiKey.GroupID,
}, true
}
func batchImageError(c *gin.Context, err error) {
status := infraerrors.Code(err)
code := infraerrors.Reason(err)
message := infraerrors.Message(err)
if err == nil {
status = http.StatusInternalServerError
code = "INTERNAL_ERROR"
message = "internal error"
}
if status == 0 || (status == http.StatusInternalServerError && strings.TrimSpace(code) == "") {
status = http.StatusInternalServerError
code = "INTERNAL_ERROR"
message = "internal error"
}
if errors.Is(err, service.ErrBatchImageJobNotFound) {
status = http.StatusNotFound
code = "BATCH_IMAGE_NOT_FOUND"
message = "batch image job not found"
}
c.JSON(status, gin.H{
"error": gin.H{
"type": "invalid_request_error",
"code": code,
"message": message,
},
})
}
@@ -10,6 +10,7 @@ import (
func TestAPIKeyFromService_MapsLastUsedAt(t *testing.T) {
lastUsed := time.Now().UTC().Truncate(time.Second)
lastUsedIP := "203.0.113.10"
src := &service.APIKey{
ID: 1,
UserID: 2,
@@ -17,6 +18,7 @@ func TestAPIKeyFromService_MapsLastUsedAt(t *testing.T) {
Name: "Mapper",
Status: service.StatusActive,
LastUsedAt: &lastUsed,
LastUsedIP: &lastUsedIP,
CurrentConcurrency: 3,
}
@@ -24,6 +26,8 @@ func TestAPIKeyFromService_MapsLastUsedAt(t *testing.T) {
require.NotNil(t, out)
require.NotNil(t, out.LastUsedAt)
require.WithinDuration(t, lastUsed, *out.LastUsedAt, time.Second)
require.NotNil(t, out.LastUsedIP)
require.Equal(t, lastUsedIP, *out.LastUsedIP)
require.Equal(t, 3, out.CurrentConcurrency)
}
@@ -39,4 +43,5 @@ func TestAPIKeyFromService_MapsNilLastUsedAt(t *testing.T) {
out := APIKeyFromService(src)
require.NotNil(t, out)
require.Nil(t, out.LastUsedAt)
require.Nil(t, out.LastUsedIP)
}
+11
View File
@@ -18,6 +18,7 @@ func UserFromServiceShallow(u *service.User) *User {
Username: u.Username,
Role: u.Role,
Balance: u.Balance,
FrozenBalance: u.FrozenBalance,
Concurrency: u.Concurrency,
Status: u.Status,
AllowedGroups: u.AllowedGroups,
@@ -88,6 +89,7 @@ func APIKeyFromService(k *service.APIKey) *APIKey {
IPWhitelist: k.IPWhitelist,
IPBlacklist: k.IPBlacklist,
LastUsedAt: k.LastUsedAt,
LastUsedIP: k.LastUsedIP,
Quota: k.Quota,
QuotaUsed: k.QuotaUsed,
ExpiresAt: k.ExpiresAt,
@@ -180,8 +182,13 @@ func groupFromServiceBase(g *service.Group) Group {
WeeklyLimitUSD: g.WeeklyLimitUSD,
MonthlyLimitUSD: g.MonthlyLimitUSD,
AllowImageGeneration: g.AllowImageGeneration,
AllowBatchImageGeneration: g.AllowBatchImageGeneration,
ImageRateIndependent: g.ImageRateIndependent,
ImageRateMultiplier: g.ImageRateMultiplier,
BatchImageDiscountMultiplier: g.BatchImageDiscountMultiplier,
BatchImageHoldMultiplier: g.BatchImageHoldMultiplier,
VideoRateIndependent: g.VideoRateIndependent,
VideoRateMultiplier: g.VideoRateMultiplier,
PeakRateEnabled: g.PeakRateEnabled,
PeakStart: g.PeakStart,
PeakEnd: g.PeakEnd,
@@ -189,6 +196,10 @@ func groupFromServiceBase(g *service.Group) Group {
ImagePrice1K: g.ImagePrice1K,
ImagePrice2K: g.ImagePrice2K,
ImagePrice4K: g.ImagePrice4K,
VideoPrice480P: g.VideoPrice480P,
VideoPrice720P: g.VideoPrice720P,
VideoPrice1080P: g.VideoPrice1080P,
WebSearchPricePerCall: g.WebSearchPricePerCall,
ClaudeCodeOnly: g.ClaudeCodeOnly,
FallbackGroupID: g.FallbackGroupID,
FallbackGroupIDOnInvalidRequest: g.FallbackGroupIDOnInvalidRequest,
+1
View File
@@ -422,6 +422,7 @@ type OpenAIFastPolicyRule struct {
ServiceTier string `json:"service_tier"`
Action string `json:"action"`
Scope string `json:"scope"`
UserIDs []int64 `json:"user_ids,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
ModelWhitelist []string `json:"model_whitelist,omitempty"`
FallbackAction string `json:"fallback_action,omitempty"`
+15 -3
View File
@@ -14,6 +14,7 @@ type User struct {
Username string `json:"username"`
Role string `json:"role"`
Balance float64 `json:"balance"`
FrozenBalance float64 `json:"frozen_balance"`
Concurrency int `json:"concurrency"`
Status string `json:"status"`
AllowedGroups []int64 `json:"allowed_groups"`
@@ -58,6 +59,7 @@ type APIKey struct {
IPWhitelist []string `json:"ip_whitelist"`
IPBlacklist []string `json:"ip_blacklist"`
LastUsedAt *time.Time `json:"last_used_at"`
LastUsedIP *string `json:"last_used_ip"`
Quota float64 `json:"quota"` // Quota limit in USD (0 = unlimited)
QuotaUsed float64 `json:"quota_used"` // Used quota amount in USD
ExpiresAt *time.Time `json:"expires_at"` // Expiration time (nil = never expires)
@@ -99,9 +101,14 @@ type Group struct {
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
// 图片生成计费配置(仅 antigravity 平台使用)
AllowImageGeneration bool `json:"allow_image_generation"`
ImageRateIndependent bool `json:"image_rate_independent"`
ImageRateMultiplier float64 `json:"image_rate_multiplier"`
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"`
@@ -110,6 +117,11 @@ type Group 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"`
// Codex alpha/search 网页搜索单次价格(USD/次);null 表示使用默认价 0.01
WebSearchPricePerCall *float64 `json:"web_search_price_per_call"`
// Claude Code 客户端限制
ClaudeCodeOnly bool `json:"claude_code_only"`
+9 -3
View File
@@ -18,6 +18,7 @@ const (
EndpointMessages = "/v1/messages"
EndpointChatCompletions = "/v1/chat/completions"
EndpointEmbeddings = "/v1/embeddings"
EndpointAlphaSearch = "/v1/alpha/search"
EndpointResponses = "/v1/responses"
EndpointResponsesCompact = "/v1/responses/compact"
EndpointImagesGenerations = "/v1/images/generations"
@@ -75,6 +76,8 @@ func NormalizeInboundEndpoint(path string) string {
switch {
case strings.Contains(path, EndpointEmbeddings):
return EndpointEmbeddings
case strings.Contains(path, EndpointAlphaSearch) || isBareOrSubpathOf(strings.TrimRight(path, "/"), "/alpha/search") || isBareOrSubpathOf(strings.TrimRight(path, "/"), "/backend-api/codex/alpha/search"):
return EndpointAlphaSearch
case strings.Contains(path, EndpointChatCompletions):
return EndpointChatCompletions
case strings.Contains(path, EndpointMessages):
@@ -155,8 +158,11 @@ func isBareOrSubpathOf(path, root string) bool {
// account platform and the normalized inbound endpoint.
//
// Platform-specific rules:
// - OpenAI always forwards to /v1/responses (with optional subpath
// such as /v1/responses/compact preserved from the raw URL).
// - OpenAI and Grok text compatibility routes forward to /v1/responses
// (with optional subpath such as /v1/responses/compact preserved from
// the raw URL); native endpoints such as embeddings and alpha search
// retain their paths. Grok raw Chat requests override this through the
// forwarding result consumed by resolveOpenAIUpstreamEndpoint.
// - Anthropic → /v1/messages
// - Gemini → /v1beta/models
// - Antigravity → /v1/messages (Claude) or gemini (Gemini)
@@ -167,7 +173,7 @@ func DeriveUpstreamEndpoint(inbound, rawRequestPath, platform string) string {
switch platform {
case service.PlatformOpenAI, service.PlatformGrok:
if inbound == EndpointEmbeddings || inbound == EndpointImagesGenerations || inbound == EndpointImagesEdits || inbound == EndpointVideosGenerations || inbound == EndpointVideos {
if inbound == EndpointEmbeddings || inbound == EndpointAlphaSearch || inbound == EndpointImagesGenerations || inbound == EndpointImagesEdits || inbound == EndpointVideosGenerations || inbound == EndpointVideos {
return inbound
}
// OpenAI forwards everything to the Responses API.
+59
View File
@@ -25,6 +25,7 @@ func TestNormalizeInboundEndpoint(t *testing.T) {
{"/v1/messages", EndpointMessages},
{"/v1/chat/completions", EndpointChatCompletions},
{"/v1/embeddings", EndpointEmbeddings},
{"/v1/alpha/search", EndpointAlphaSearch},
{"/v1/responses", EndpointResponses},
{"/v1/responses/compact", EndpointResponsesCompact},
{"/v1/responses/compact/detail", EndpointResponsesCompact},
@@ -50,11 +51,13 @@ func TestNormalizeInboundEndpoint(t *testing.T) {
{"/responses", EndpointResponses},
{"/responses/compact", EndpointResponsesCompact},
{"/responses/compact/detail", EndpointResponsesCompact},
{"/alpha/search", EndpointAlphaSearch},
// Bare Codex direct alias route — root vs. compact.
{"/backend-api/codex/responses", EndpointResponses},
{"/backend-api/codex/responses/compact", EndpointResponsesCompact},
{"/backend-api/codex/responses/compact/detail", EndpointResponsesCompact},
{"/backend-api/codex/alpha/search", EndpointAlphaSearch},
// Must NOT generalize to arbitrary paths merely ending in
// "/responses" (or "/responses/compact") that are unrelated to
@@ -119,8 +122,11 @@ func TestDeriveUpstreamEndpoint(t *testing.T) {
{"openai from messages", EndpointMessages, "/v1/messages", service.PlatformOpenAI, EndpointResponses},
{"openai from completions", EndpointChatCompletions, "/v1/chat/completions", service.PlatformOpenAI, EndpointResponses},
{"openai embeddings", EndpointEmbeddings, "/v1/embeddings", service.PlatformOpenAI, EndpointEmbeddings},
{"openai alpha search", EndpointAlphaSearch, "/backend-api/codex/alpha/search", service.PlatformOpenAI, EndpointAlphaSearch},
{"openai image generations", EndpointImagesGenerations, "/v1/images/generations", service.PlatformOpenAI, EndpointImagesGenerations},
{"openai image edits", EndpointImagesEdits, "/openai/v1/images/edits", service.PlatformOpenAI, EndpointImagesEdits},
{"grok chat defaults to responses without runtime result", EndpointChatCompletions, "/v1/chat/completions", service.PlatformGrok, EndpointResponses},
{"grok responses", EndpointResponses, "/v1/responses", service.PlatformGrok, EndpointResponses},
{"grok video generations", EndpointVideosGenerations, "/v1/videos/generations", service.PlatformGrok, EndpointVideosGenerations},
{"grok video status", EndpointVideos, "/videos/req_123", service.PlatformGrok, EndpointVideos},
@@ -138,6 +144,59 @@ func TestDeriveUpstreamEndpoint(t *testing.T) {
}
}
func TestResolveOpenAIUpstreamEndpointPrefersForwardResult(t *testing.T) {
tests := []struct {
name string
account *service.Account
result *service.OpenAIForwardResult
runtimeEndpoint string
want string
}{
{
name: "grok raw chat result overrides stale context",
account: &service.Account{Platform: service.PlatformGrok, Type: service.AccountTypeOAuth},
result: &service.OpenAIForwardResult{UpstreamEndpoint: EndpointChatCompletions},
runtimeEndpoint: EndpointResponses,
want: EndpointChatCompletions,
},
{
name: "grok chat bridged to responses",
account: &service.Account{Platform: service.PlatformGrok, Type: service.AccountTypeOAuth},
result: &service.OpenAIForwardResult{UpstreamEndpoint: EndpointResponses},
want: EndpointResponses,
},
{
name: "grok empty result keeps responses default",
account: &service.Account{Platform: service.PlatformGrok, Type: service.AccountTypeOAuth},
result: &service.OpenAIForwardResult{},
want: EndpointResponses,
},
{
name: "grok raw error uses runtime endpoint",
account: &service.Account{Platform: service.PlatformGrok, Type: service.AccountTypeOAuth},
runtimeEndpoint: EndpointChatCompletions,
want: EndpointChatCompletions,
},
{
name: "openai behavior remains responses",
account: &service.Account{Platform: service.PlatformOpenAI, Type: service.AccountTypeOAuth},
result: &service.OpenAIForwardResult{},
want: EndpointResponses,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodPost, EndpointChatCompletions, nil)
c.Set(ctxKeyInboundEndpoint, EndpointChatCompletions)
service.SetActualOpenAIUpstreamEndpoint(c, tt.runtimeEndpoint)
require.Equal(t, tt.want, resolveOpenAIUpstreamEndpoint(c, tt.account, tt.result))
})
}
}
// ──────────────────────────────────────────────────────────
// responsesSubpathSuffix
// ──────────────────────────────────────────────────────────
+9 -3
View File
@@ -20,7 +20,6 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
pkgerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
pkghttputil "github.com/Wei-Shaw/sub2api/internal/pkg/httputil"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
@@ -138,7 +137,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
defer h.maybeLogCompatibilityFallbackMetrics(reqLog)
// 读取请求体
body, err := pkghttputil.ReadRequestBodyWithPrealloc(c.Request)
body, err := readLenientJSONRequestBodyWithPrealloc(c.Request, h.cfg)
if err != nil {
if maxErr, ok := extractMaxBytesError(err); ok {
h.errorResponse(c, http.StatusRequestEntityTooLarge, "invalid_request_error", buildBodyTooLargeMessage(maxErr.Limit))
@@ -158,6 +157,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
bodyRef := service.NewRequestBodyRef(body)
parsedReq, err := service.ParseGatewayRequest(bodyRef, domain.PlatformAnthropic)
if err != nil {
logRequestBodyParseFailure(reqLog, body, err)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -1628,6 +1628,11 @@ func (h *GatewayHandler) mapUpstreamError(statusCode int) (int, string, string)
// handleStreamingAwareError handles errors that may occur after streaming has started
func (h *GatewayHandler) handleStreamingAwareError(c *gin.Context, status int, errType, message string, streamStarted bool) {
if streamStarted {
// 响应状态码已固化为 200(ping/部分数据已 flush),错误只能就地以 SSE 帧回传。
// 标记本次流内错误,供 ops_error_logger 补记——否则该中间件按 status>=400 采集,
// 这类挂在 200 流上的失败(如并发限流回退)不会进错误看板。
service.MarkOpsStreamError(c, errType, message, status)
// /v1/responses 的严格 SDK(Codex CLI)要求终止事件必须属于
// response.completed/failed/incomplete/cancelled 集合。
// Anthropic-backed Responses 路径同样会因为通用 error 帧被拒。
@@ -1776,7 +1781,7 @@ func (h *GatewayHandler) CountTokens(c *gin.Context) {
defer h.maybeLogCompatibilityFallbackMetrics(reqLog)
// 读取请求体
body, err := pkghttputil.ReadRequestBodyWithPrealloc(c.Request)
body, err := readLenientJSONRequestBodyWithPrealloc(c.Request, h.cfg)
if err != nil {
if maxErr, ok := extractMaxBytesError(err); ok {
h.errorResponse(c, http.StatusRequestEntityTooLarge, "invalid_request_error", buildBodyTooLargeMessage(maxErr.Limit))
@@ -1796,6 +1801,7 @@ func (h *GatewayHandler) CountTokens(c *gin.Context) {
bodyRef := service.NewRequestBodyRef(body)
parsedReq, err := service.ParseGatewayRequest(bodyRef, domain.PlatformAnthropic)
if err != nil {
logRequestBodyParseFailure(reqLog, body, err)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -7,7 +7,6 @@ import (
"strconv"
"time"
pkghttputil "github.com/Wei-Shaw/sub2api/internal/pkg/httputil"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -45,7 +44,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
)
// Read request body
body, err := pkghttputil.ReadRequestBodyWithPrealloc(c.Request)
body, err := readLenientJSONRequestBodyWithPrealloc(c.Request, h.cfg)
if err != nil {
if maxErr, ok := extractMaxBytesError(err); ok {
h.chatCompletionsErrorResponse(c, http.StatusRequestEntityTooLarge, "invalid_request_error", buildBodyTooLargeMessage(maxErr.Limit))
@@ -64,6 +63,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
// Validate JSON
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.chatCompletionsErrorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -7,7 +7,6 @@ import (
"strconv"
"time"
pkghttputil "github.com/Wei-Shaw/sub2api/internal/pkg/httputil"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -45,7 +44,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
)
// Read request body
body, err := pkghttputil.ReadRequestBodyWithPrealloc(c.Request)
body, err := readLenientJSONRequestBodyWithPrealloc(c.Request, h.cfg)
if err != nil {
if maxErr, ok := extractMaxBytesError(err); ok {
h.responsesErrorResponse(c, http.StatusRequestEntityTooLarge, "invalid_request_error", buildBodyTooLargeMessage(maxErr.Limit))
@@ -64,6 +63,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
// Validate JSON
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.responsesErrorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
+1
View File
@@ -58,6 +58,7 @@ type Handlers struct {
Payment *PaymentHandler
PaymentWebhook *PaymentWebhookHandler
AvailableChannel *AvailableChannelHandler
BatchImage *BatchImageHandler
}
// BuildInfo contains build-time information
@@ -107,3 +107,31 @@ func classifyNoAccountErrorFromGin(
}
return classifyNoAccountError(ctx, diag, apiKey, routingModel, displayModel, platform)
}
func classifyOpenAICompatibleNoAccountErrorFromGin(
c *gin.Context,
diag service.ModelAvailabilityDiagnoser,
apiKey *service.APIKey,
routingModel string,
displayModel string,
) noAccountErrorClassification {
return classifyNoAccountErrorFromGin(
c,
diag,
apiKey,
routingModel,
displayModel,
openAICompatibleRequestPlatform(apiKey),
)
}
func openAICompatibleSelectionErrorForLog(err error, platform string) error {
if err == nil || platform != service.PlatformGrok {
return err
}
message := strings.ReplaceAll(err.Error(), "OpenAI accounts", "Grok accounts")
if message == err.Error() {
return err
}
return fmt.Errorf("%s", message)
}
@@ -4,6 +4,7 @@ package handler
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
@@ -114,6 +115,33 @@ func TestClassifyNoAccountError_ModelNotSupported_Returns404(t *testing.T) {
require.Equal(t, int64(42), *fd.calls[0].GroupID)
}
func TestClassifyOpenAICompatibleNoAccountError_GrokUsesGrokPlatform(t *testing.T) {
c := newTestGinContextWithRequest()
fd := &fakeDiagnoser{resp: service.ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: false}}
groupID := int64(43)
apiKey := &service.APIKey{
GroupID: &groupID,
Group: &service.Group{
ID: groupID,
Platform: service.PlatformGrok,
},
}
cls := classifyOpenAICompatibleNoAccountErrorFromGin(c, fd, apiKey, "grok-4.5", "grok-4.5")
require.Equal(t, http.StatusNotFound, cls.Status)
require.Equal(t, "model_not_found", cls.ErrType)
require.True(t, cls.ModelNotFound)
require.Len(t, fd.calls, 1)
require.Equal(t, service.PlatformGrok, fd.calls[0].Platform)
logErr := openAICompatibleSelectionErrorForLog(
fmt.Errorf("no available OpenAI accounts supporting model: grok-4.5"),
service.PlatformGrok,
)
require.EqualError(t, logErr, "no available Grok accounts supporting model: grok-4.5")
}
func TestClassifyNoAccountError_HasModelSupport_KeepsRoutingMessageGenerationToCaller(t *testing.T) {
c := newTestGinContextWithRequest()
fd := &fakeDiagnoser{resp: service.ModelAvailabilityDiagnosis{HasAccountsInPool: true, HasModelSupport: true}}
@@ -0,0 +1,250 @@
package handler
import (
"context"
"errors"
"net/http"
"strconv"
"strings"
"time"
pkghttputil "github.com/Wei-Shaw/sub2api/internal/pkg/httputil"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/tidwall/gjson"
"go.uber.org/zap"
)
// AlphaSearch proxies the standalone search endpoint used by Codex Responses Lite.
func (h *OpenAIGatewayHandler) AlphaSearch(c *gin.Context) {
streamStarted := false
defer h.recoverResponsesPanic(c, &streamStarted)
setOpenAIClientTransportHTTP(c)
requestStart := time.Now()
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok || apiKey.Group == nil {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return
}
if apiKey.Group.Platform != service.PlatformOpenAI {
h.errorResponse(c, http.StatusNotFound, "not_found_error", "Codex alpha search is only available for OpenAI groups")
return
}
subject, ok := middleware2.GetAuthSubjectFromContext(c)
if !ok {
h.errorResponse(c, http.StatusInternalServerError, "api_error", "User context not found")
return
}
reqLog := requestLogger(
c,
"handler.openai_gateway.alpha_search",
zap.Int64("user_id", subject.UserID),
zap.Int64("api_key_id", apiKey.ID),
zap.Any("group_id", apiKey.GroupID),
)
if !h.ensureResponsesDependencies(c, reqLog) {
return
}
body, err := pkghttputil.ReadRequestBodyWithPrealloc(c.Request)
if err != nil {
if maxErr, ok := extractMaxBytesError(err); ok {
h.errorResponse(c, http.StatusRequestEntityTooLarge, "invalid_request_error", buildBodyTooLargeMessage(maxErr.Limit))
return
}
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to read request body")
return
}
if len(body) == 0 {
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Request body is empty")
return
}
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
modelResult := gjson.GetBytes(body, "model")
if !modelResult.Exists() || modelResult.Type != gjson.String || strings.TrimSpace(modelResult.String()) == "" {
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "model is required")
return
}
requestedModel := strings.TrimSpace(modelResult.String())
reqLog = reqLog.With(zap.String("model", requestedModel))
setOpsRequestContext(c, requestedModel, false)
setOpsEndpointContext(c, "", int16(service.RequestTypeSync))
channelMapping, _ := h.gatewayService.ResolveChannelMappingAndRestrict(c.Request.Context(), apiKey.GroupID, requestedModel)
forwardBody := openAIModelMappedBody(body, channelMapping.Mapped, channelMapping.MappedModel, h.gatewayService.ReplaceModelInBody)
subscription, _ := middleware2.GetSubscriptionFromContext(c)
service.SetOpsLatencyMs(c, service.OpsAuthLatencyMsKey, time.Since(requestStart).Milliseconds())
userRelease, acquired := h.acquireResponsesUserSlot(c, subject.UserID, subject.Concurrency, false, &streamStarted, reqLog)
if !acquired {
return
}
if userRelease != nil {
defer userRelease()
}
if err := h.billingCacheService.CheckBillingEligibility(c.Request.Context(), apiKey.User, apiKey, apiKey.Group, subscription, service.QuotaPlatform(c.Request.Context(), apiKey)); err != nil {
status, code, message, retryAfter := billingErrorDetails(err)
if retryAfter > 0 {
c.Header("Retry-After", strconv.Itoa(retryAfter))
}
h.errorResponse(c, status, code, message)
return
}
searchID := strings.TrimSpace(gjson.GetBytes(body, "id").String())
sessionHash := h.gatewayService.GenerateSessionHashWithFallback(c, nil, searchID)
failedAccountIDs := make(map[int64]struct{})
var lastFailoverErr *service.UpstreamFailoverError
switchCount := 0
routingStart := time.Now()
for {
selection, _, err := h.gatewayService.SelectAccountWithSchedulerForCapability(
c.Request.Context(),
apiKey.GroupID,
"",
sessionHash,
requestedModel,
failedAccountIDs,
service.OpenAIUpstreamTransportHTTPSSE,
service.OpenAIEndpointCapabilityChatCompletions,
false,
false,
service.PlatformOpenAI,
)
if err != nil || selection == nil || selection.Account == nil {
if len(failedAccountIDs) == 0 {
cls := classifyNoAccountErrorFromGin(c, h.gatewayService, apiKey, requestedModel, requestedModel, service.PlatformOpenAI)
if !cls.ModelNotFound {
markOpsRoutingCapacityLimitedIfNoAvailable(c, err)
}
h.errorResponse(c, cls.Status, cls.ErrType, cls.Message)
return
}
if lastFailoverErr != nil {
h.handleFailoverExhausted(c, lastFailoverErr, false)
} else {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Upstream request failed")
}
return
}
account := selection.Account
setOpsSelectedAccount(c, account.ID, account.Platform)
accountRelease, acquired := h.acquireResponsesAccountSlot(c, apiKey.GroupID, sessionHash, selection, false, &streamStarted, reqLog)
if !acquired {
return
}
service.SetOpsLatencyMs(c, service.OpsRoutingLatencyMsKey, time.Since(routingStart).Milliseconds())
writerSizeBeforeForward := c.Writer.Size()
forwardStart := time.Now()
var result *service.OpenAIForwardResult
result, err = func() (*service.OpenAIForwardResult, error) {
if accountRelease != nil {
defer accountRelease()
}
return h.gatewayService.ForwardAlphaSearch(c.Request.Context(), c, account, forwardBody)
}()
service.SetOpsLatencyMs(c, service.OpsResponseLatencyMsKey, time.Since(forwardStart).Milliseconds())
if err == nil {
h.gatewayService.ReportOpenAIAccountScheduleResult(account.ID, true, nil)
if result != nil {
h.recordAlphaSearchUsage(c, apiKey, account, subscription, channelMapping, requestedModel, body, result, subject.UserID)
}
return
}
var failoverErr *service.UpstreamFailoverError
if !errors.As(err, &failoverErr) {
h.gatewayService.ReportOpenAIAccountScheduleResult(account.ID, false, nil)
if c.Writer.Size() == writerSizeBeforeForward {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Upstream request failed")
}
reqLog.Warn("openai_alpha_search.forward_failed", zap.Int64("account_id", account.ID), zap.Error(err))
return
}
h.gatewayService.ReportOpenAIAccountScheduleResult(account.ID, false, nil)
if c.Writer.Size() != writerSizeBeforeForward {
h.handleFailoverExhausted(c, failoverErr, true)
return
}
h.gatewayService.RecordOpenAIAccountSwitch()
failedAccountIDs[account.ID] = struct{}{}
lastFailoverErr = failoverErr
if switchCount >= h.maxAccountSwitches {
h.handleFailoverExhausted(c, failoverErr, false)
return
}
switchCount++
if h.gatewayService.ShouldStopOpenAIOAuth429Failover(account, failoverErr.StatusCode, switchCount) {
h.handleFailoverExhausted(c, failoverErr, false)
return
}
reqLog.Warn("openai_alpha_search.upstream_failover_switching",
zap.Int64("account_id", account.ID),
zap.Int("upstream_status", failoverErr.StatusCode),
zap.Int("switch_count", switchCount),
)
}
}
// recordAlphaSearchUsage 为一次成功的 alpha/search 网页搜索落按次计费用量行
// (上游不返回 usage 字段,按 WebSearchCalls 走分组单价 × 倍率的按次口径)。
// 与 images 一致使用 mandatory 池提交,池满时同步兜底执行,保证扣费不丢。
func (h *OpenAIGatewayHandler) recordAlphaSearchUsage(
c *gin.Context,
apiKey *service.APIKey,
account *service.Account,
subscription *service.UserSubscription,
channelMapping service.ChannelMappingResult,
requestedModel string,
body []byte,
result *service.OpenAIForwardResult,
userID int64,
) {
userAgent := c.GetHeader("User-Agent")
clientIP := ip.GetClientIP(c)
requestPayloadHash := service.HashUsageRequestPayload(body)
inboundEndpoint := GetInboundEndpoint(c)
upstreamEndpoint := GetUpstreamEndpoint(c, account.Platform)
quotaPlatform := service.QuotaPlatform(c.Request.Context(), apiKey)
h.submitMandatoryUsageRecordTask(c.Request.Context(), func(ctx context.Context) {
if err := h.gatewayService.RecordUsage(ctx, &service.OpenAIRecordUsageInput{
Result: result,
APIKey: apiKey,
User: apiKey.User,
Account: account,
Subscription: subscription,
InboundEndpoint: inboundEndpoint,
UpstreamEndpoint: upstreamEndpoint,
UserAgent: userAgent,
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
QuotaPlatform: quotaPlatform,
ChannelUsageFields: channelMapping.ToUsageFields(requestedModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.alpha_search"),
zap.Int64("user_id", userID),
zap.Int64("api_key_id", apiKey.ID),
zap.Any("group_id", apiKey.GroupID),
zap.String("model", requestedModel),
zap.Int64("account_id", account.ID),
).Error("openai_alpha_search.record_usage_failed", zap.Error(err))
}
})
}

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