Merge remote-tracking branch 'upstream/main' into codex/add-response-format-compat

This commit is contained in:
CHOS1N
2026-07-07 20:05:50 +08:00
305 changed files with 54450 additions and 1254 deletions
+2 -2
View File
@@ -5,14 +5,14 @@ exceptions:
severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2023-30533)"
mitigation: "Load only on export; restrict export permissions and data scope"
expires_on: "2026-07-06"
expires_on: "2026-10-06"
owner: "security@your-domain"
- package: xlsx
advisory: "GHSA-5pgg-2g8v-p4x9"
severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2024-22363)"
mitigation: "Load only on export; restrict export permissions and data scope"
expires_on: "2026-07-06"
expires_on: "2026-10-06"
owner: "security@your-domain"
- package: lodash
advisory: "GHSA-r5fr-rjxr-66jc"
+6 -1
View File
@@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1.7
# =============================================================================
# Sub2API Multi-Stage Dockerfile
# =============================================================================
@@ -12,11 +13,13 @@ 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
+17 -4
View File
@@ -41,6 +41,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 +150,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
@@ -523,20 +536,20 @@ Additional security-related options are available in `config.yaml`:
**⚠️ Security Warning: HTTP URL Configuration**
When `security.url_allowlist.enabled=false`, the system performs minimal URL validation by default, **rejecting HTTP URLs** and only allowing HTTPS. To allow HTTP URLs (e.g., for development or internal testing), you must explicitly set:
When `security.url_allowlist.enabled=false`, the system performs minimal URL validation and **allows HTTP URLs by default** (dev-friendly mode; Docker Compose deployments use the same default). For production, explicitly tighten this to HTTPS-only:
```yaml
security:
url_allowlist:
enabled: false # Disable allowlist checks
allow_insecure_http: true # Allow HTTP URLs (⚠️ INSECURE)
allow_insecure_http: false # HTTPS only (recommended for production)
```
**Or via environment variable:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**Risks of allowing HTTP:**
@@ -550,7 +563,7 @@ SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
- ✅ Testing account connectivity before obtaining HTTPS
- ❌ Production environments (use HTTPS only)
**Example error without this setting:**
**Example error for HTTP URLs when `allow_insecure_http: false` is set:**
```
Invalid base URL: invalid url scheme: http
```
+17 -4
View File
@@ -42,6 +42,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 +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>
@@ -568,20 +581,20 @@ gateway:
**⚠️ 安全警告:HTTP URL 配置**
当 `security.url_allowlist.enabled=false` 时,系统默认执行最小 URL 校验,**拒绝 HTTP URL**,仅允许 HTTPS。要允许 HTTP URL(例如用于开发或内网测试),必须显式设置:
当 `security.url_allowlist.enabled=false` 时,系统仅执行最小 URL 校验,且**默认允许 HTTP URL**(开发友好模式,Docker Compose 部署的默认值一致)。生产环境建议显式收紧为仅允许 HTTPS:
```yaml
security:
url_allowlist:
enabled: false # 禁用白名单检查
allow_insecure_http: true # 允许 HTTP URL(⚠️ 不安全)
allow_insecure_http: false # 仅允许 HTTPS(生产环境推荐)
```
**或通过环境变量:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**允许 HTTP 的风险:**
@@ -595,7 +608,7 @@ SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
- ✅ 获取 HTTPS 前测试账号连通性
- ❌ 生产环境(仅使用 HTTPS)
**未设置此项时的错误示例:**
**设置 `allow_insecure_http: false` 后,HTTP URL 会返回如下错误:**
```
Invalid base URL: invalid url scheme: http
```
+17 -4
View File
@@ -41,6 +41,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 +148,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>
## 概要
@@ -521,20 +534,20 @@ default:
**⚠️ セキュリティ警告: HTTP URL 設定**
`security.url_allowlist.enabled=false` の場合、システムはデフォルトで最小限の URL バリデーションを行い、**HTTP URL を拒否**して HTTPS のみを許可します。HTTP URL を許可するには(開発環境や内部テスト用など)、以下を明示的に設定する必要があります:
`security.url_allowlist.enabled=false` の場合、システムは最小限の URL バリデーションのみを行い、**デフォルトで HTTP URL を許可**します(開発フレンドリーモード。Docker Compose デプロイのデフォルトも同じです)。本番環境では、以下のように明示的に HTTPS のみに制限することを推奨します:
```yaml
security:
url_allowlist:
enabled: false # 許可リストチェックを無効化
allow_insecure_http: true # HTTP URL を許可(⚠️ セキュリティリスクあり)
allow_insecure_http: false # HTTPS のみ許可(本番環境推奨)
```
**または環境変数で設定:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```
**HTTP を許可するリスク:**
@@ -548,7 +561,7 @@ SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
- ✅ HTTPS 取得前のアカウント接続テスト
- ❌ 本番環境(HTTPS のみを使用)
**この設定なしで表示されるエラー例:**
**`allow_insecure_http: false` 設定時に HTTP URL で表示されるエラー例:**
```
Invalid base URL: invalid url scheme: http
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+1 -1
View File
@@ -1 +1 @@
0.1.144
0.1.146
+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
+30 -7
View File
@@ -67,7 +67,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
serviceUserPlatformQuotaRepository := repository.NewUserPlatformQuotaServiceAdapter(userPlatformQuotaRepository)
billingCacheService := service.ProvideBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, userRPMCache, userGroupRateRepository, configConfig, serviceUserPlatformQuotaRepository)
apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.ProvideAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig, billingCacheService)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
apiKeyService := service.ProvideAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig, billingCacheService, concurrencyService)
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
@@ -92,10 +96,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
usageLogRepository := repository.NewUsageLogRepository(client, db)
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
opsRepository := repository.NewOpsRepository(db)
schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
batchImageRepository := repository.NewBatchImageRepository(db)
batchImageQueue := repository.NewBatchImageQueue(redisClient, configConfig)
batchImageDownloadLimiter := repository.NewBatchImageDownloadLimiter(redisClient, configConfig)
usageBillingRepository := repository.NewUsageBillingRepository(client, db)
gatewayCache := repository.NewGatewayCache(redisClient)
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
@@ -134,6 +137,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
channelRepository := repository.NewChannelRepository(db)
channelService := service.NewChannelService(channelRepository, groupRepository, apiKeyAuthCacheInvalidator, pricingService)
modelPricingResolver := service.NewModelPricingResolver(channelService, billingService)
batchImageModelPricingResolver := service.ProvideBatchImageModelPricingResolver(modelPricingResolver)
batchImagePublicService := service.NewBatchImagePublicService(batchImageRepository, accountRepository, groupRepository, userGroupRateRepository, batchImageQueue, batchImageModelPricingResolver, usageBillingRepository, apiKeyAuthCacheInvalidator, configConfig)
batchImageDownloadService := service.NewBatchImageDownloadService(batchImageRepository, accountRepository, batchImageDownloadLimiter, configConfig)
batchImageCleanupService := service.ProvideBatchImageCleanupService(batchImageRepository, accountRepository, configConfig)
batchImageWorkerRuntime := service.ProvideBatchImageWorkerRuntime(batchImageRepository, accountRepository, batchImageQueue, usageBillingRepository, usageLogRepository, batchImageModelPricingResolver, apiKeyAuthCacheInvalidator, configConfig)
notificationEmailService := service.NewNotificationEmailService(settingRepository, emailService)
balanceNotifyService := service.ProvideBalanceNotifyService(emailService, settingRepository, accountRepository, notificationEmailService)
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService, channelService, modelPricingResolver, balanceNotifyService, serviceUserPlatformQuotaRepository)
@@ -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,
+37 -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,10 @@ 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"`
// 是否仅允许 Claude Code 客户端
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
// 非 Claude Code 请求降级使用的分组 ID
@@ -205,9 +211,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.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:
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 +361,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 +400,18 @@ 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.FieldClaudeCodeOnly:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field claude_code_only", values[i])
@@ -631,6 +655,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 +679,12 @@ 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("claude_code_only=")
builder.WriteString(fmt.Sprintf("%v", _m.ClaudeCodeOnly))
builder.WriteString(", ")
+30
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,10 @@ 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"
// 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 +194,14 @@ var Columns = []string{
FieldMonthlyLimitUsd,
FieldDefaultValidityDays,
FieldAllowImageGeneration,
FieldAllowBatchImageGeneration,
FieldImageRateIndependent,
FieldImageRateMultiplier,
FieldImagePrice1k,
FieldImagePrice2k,
FieldImagePrice4k,
FieldBatchImageDiscountMultiplier,
FieldBatchImageHoldMultiplier,
FieldClaudeCodeOnly,
FieldFallbackGroupID,
FieldFallbackGroupIDOnInvalidRequest,
@@ -277,10 +286,16 @@ 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
// 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 +427,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 +457,16 @@ 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()
}
// ByClaudeCodeOnly orders the results by the claude_code_only field.
func ByClaudeCodeOnly(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldClaudeCodeOnly, opts...).ToFunc()
+105
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,16 @@ 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))
}
// 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 +1140,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 +1350,86 @@ 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))
}
// ClaudeCodeOnlyEQ applies the EQ predicate on the "claude_code_only" field.
func ClaudeCodeOnlyEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
+235
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,34 @@ 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
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_c *GroupCreate) SetClaudeCodeOnly(v bool) *GroupCreate {
_c.mutation.SetClaudeCodeOnly(v)
@@ -736,6 +778,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 +790,14 @@ 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.ClaudeCodeOnly(); !ok {
v := group.DefaultClaudeCodeOnly
_c.mutation.SetClaudeCodeOnly(v)
@@ -869,12 +923,21 @@ 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.ClaudeCodeOnly(); !ok {
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
}
@@ -1019,6 +1082,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 +1106,14 @@ 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.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
_node.ClaudeCodeOnly = value
@@ -1537,6 +1612,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 +1726,42 @@ 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
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsert) SetClaudeCodeOnly(v bool) *GroupUpsert {
u.Set(group.FieldClaudeCodeOnly, v)
@@ -2235,6 +2358,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 +2491,48 @@ 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()
})
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsertOne) SetClaudeCodeOnly(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
@@ -3153,6 +3332,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 +3465,48 @@ 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()
})
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (u *GroupUpsertBulk) SetClaudeCodeOnly(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
+142
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,48 @@ 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
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdate) SetClaudeCodeOnly(v bool) *GroupUpdate {
_u.mutation.SetClaudeCodeOnly(v)
@@ -1116,6 +1172,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 +1211,18 @@ 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.ClaudeCodeOnly(); ok {
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
}
@@ -1853,6 +1924,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 +2054,48 @@ 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
}
// SetClaudeCodeOnly sets the "claude_code_only" field.
func (_u *GroupUpdateOne) SetClaudeCodeOnly(v bool) *GroupUpdateOne {
_u.mutation.SetClaudeCodeOnly(v)
@@ -2647,6 +2774,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 +2813,18 @@ 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.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:
+200 -2
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,14 @@ 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: "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 +910,7 @@ var (
{
Name: "group_sort_order",
Unique: false,
Columns: []*schema.Column{GroupsColumns[32]},
Columns: []*schema.Column{GroupsColumns[35]},
},
},
}
@@ -1500,6 +1685,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 +1712,7 @@ var (
{
Name: "user_status",
Unique: false,
Columns: []*schema.Column{UsersColumns[9]},
Columns: []*schema.Column{UsersColumns[10]},
},
{
Name: "user_deleted_at",
@@ -1799,6 +1985,9 @@ var (
AnnouncementReadsTable,
AuthIdentitiesTable,
AuthIdentityChannelsTable,
BatchImageEventsTable,
BatchImageItemsTable,
BatchImageJobsTable,
ChannelMonitorsTable,
ChannelMonitorDailyRollupsTable,
ChannelMonitorHistoriesTable,
@@ -1862,6 +2051,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",
+5793 -2
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)
+210 -25
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,74 @@ 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)
// groupDescClaudeCodeOnly is the schema descriptor for claude_code_only field.
groupDescClaudeCodeOnly := groupFields[21].Descriptor()
groupDescClaudeCodeOnly := groupFields[24].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[28].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[29].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[30].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[31].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[32].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[33].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[34].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[35].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[36].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[37].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[38].Descriptor()
// group.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
group.DefaultRpmLimit = groupDescRpmLimit.Default.(int)
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
@@ -1860,54 +2041,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"),
}
}
+11
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,14 @@ 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("批量图片生成冻结价格比例,按普通生图原价乘以该比例冻结,结算后释放差额"),
// Claude Code 客户端限制 (added by migration 029)
field.Bool("claude_code_only").
+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)
+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)
}
+162 -2
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"`
@@ -969,6 +1020,9 @@ type GatewayOpenAIWSSchedulerScoreWeights struct {
Reset float64 `mapstructure:"reset"`
// QuotaHeadroom 倾向 7d 剩余额度更健康的账号;默认 0(关闭,不改变原有行为)。
QuotaHeadroom float64 `mapstructure:"quota_headroom"`
// PreviousResponse/SessionSticky 仅在开启 OpenAI 高级调度的粘性加权时生效。
PreviousResponse float64 `mapstructure:"previous_response"`
SessionSticky float64 `mapstructure:"session_sticky"`
}
// GatewayOpenAISchedulerConfig OpenAI 高级调度器配置。
@@ -1729,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)
@@ -1891,6 +1992,8 @@ func setDefaults() {
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.ttft", 0.5)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.reset", 0.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.quota_headroom", 0.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.previous_response", 5.0)
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.session_sticky", 3.0)
// OpenAI HTTP upstream protocol strategy
viper.SetDefault("gateway.openai_http2.enabled", true)
viper.SetDefault("gateway.openai_http2.allow_proxy_fallback_to_http1", true)
@@ -2328,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")
@@ -2674,7 +2832,9 @@ func (c *Config) Validate() error {
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom < 0 {
c.Gateway.OpenAIWS.SchedulerScoreWeights.QuotaHeadroom < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.PreviousResponse < 0 ||
c.Gateway.OpenAIWS.SchedulerScoreWeights.SessionSticky < 0 {
return fmt.Errorf("gateway.openai_ws.scheduler_score_weights.* must be non-negative")
}
weightSum := c.Gateway.OpenAIWS.SchedulerScoreWeights.Priority +
+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")
@@ -171,13 +171,29 @@ type CheckMixedChannelRequest struct {
// AccountWithConcurrency extends Account with real-time concurrency info
type AccountWithConcurrency struct {
*dto.Account
CurrentConcurrency int `json:"current_concurrency"`
CurrentConcurrency int `json:"current_concurrency"`
SchedulerScore *AccountSchedulerScore `json:"scheduler_score,omitempty"`
SchedulerScores []AccountSchedulerGroupScore `json:"scheduler_scores,omitempty"`
// 以下字段仅对 Anthropic OAuth/SetupToken 账号有效,且仅在启用相应功能时返回
CurrentWindowCost *float64 `json:"current_window_cost,omitempty"` // 当前窗口费用
ActiveSessions *int `json:"active_sessions,omitempty"` // 当前活跃会话数
CurrentRPM *int `json:"current_rpm,omitempty"` // 当前分钟 RPM 计数
}
type AccountSchedulerScore struct {
BaseScore float64 `json:"base_score"`
StickyScore float64 `json:"sticky_score"`
StickyScoreInfinity bool `json:"sticky_score_infinity"`
StickyWeightedEnabled bool `json:"sticky_weighted_enabled"`
}
type AccountSchedulerGroupScore struct {
GroupID *int64 `json:"group_id"`
GroupName string `json:"group_name,omitempty"`
GroupPriority *int `json:"group_priority,omitempty"`
AccountSchedulerScore
}
const accountListGroupUngroupedQueryValue = "ungrouped"
func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, account *service.Account) AccountWithConcurrency {
@@ -226,6 +242,232 @@ func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, ac
return item
}
// scoreOpenAIAccountSchedulerPool 对池内 OpenAI 账号计算调度分数快照。
// loadMap 为共享的账号负载数据(含池内全部账号即可,多余条目无害);传 nil 时自行批查。
func (h *AccountHandler) scoreOpenAIAccountSchedulerPool(ctx context.Context, accounts []service.Account, loadMap map[int64]*service.AccountLoadInfo) map[int64]AccountSchedulerScore {
if len(accounts) == 0 {
return nil
}
openAIAccounts := make([]*service.Account, 0, len(accounts))
for i := range accounts {
account := &accounts[i]
if account.Platform != service.PlatformOpenAI {
continue
}
openAIAccounts = append(openAIAccounts, account)
}
if len(openAIAccounts) == 0 {
return nil
}
if loadMap == nil {
loadMap = h.fetchOpenAIAccountLoadMap(ctx, openAIAccounts)
}
var scores map[int64]service.OpenAIAccountSchedulerScoreSnapshot
if h.rateLimitService != nil {
scores = h.rateLimitService.BuildOpenAIAccountSchedulerScoreSnapshot(ctx, openAIAccounts, loadMap)
} else {
scores = service.BuildOpenAIAccountSchedulerScoreSnapshot(openAIAccounts, loadMap)
}
result := make(map[int64]AccountSchedulerScore, len(scores))
for accountID, score := range scores {
result[accountID] = AccountSchedulerScore{
BaseScore: score.BaseScore,
StickyScore: score.StickyScore,
StickyScoreInfinity: score.StickyScoreInfinity,
StickyWeightedEnabled: score.StickyWeightedEnabled,
}
}
return result
}
// fetchOpenAIAccountLoadMap 一次性批查给定 OpenAI 账号的负载数据;
// 失败时记录日志并返回空表(分数按零负载计算,属可接受降级)。
func (h *AccountHandler) fetchOpenAIAccountLoadMap(ctx context.Context, openAIAccounts []*service.Account) map[int64]*service.AccountLoadInfo {
loadMap := map[int64]*service.AccountLoadInfo{}
if h.concurrencyService == nil || len(openAIAccounts) == 0 {
return loadMap
}
seen := make(map[int64]struct{}, len(openAIAccounts))
loadReq := make([]service.AccountWithConcurrency, 0, len(openAIAccounts))
for _, account := range openAIAccounts {
if account == nil {
continue
}
if _, ok := seen[account.ID]; ok {
continue
}
seen[account.ID] = struct{}{}
loadReq = append(loadReq, service.AccountWithConcurrency{
ID: account.ID,
MaxConcurrency: account.EffectiveLoadFactor(),
})
}
if batchLoad, err := h.concurrencyService.GetAccountsLoadBatch(ctx, loadReq); err != nil {
slog.Warn("openai_scheduler_score_load_batch_failed", "error", err)
} else if batchLoad != nil {
loadMap = batchLoad
}
return loadMap
}
func (h *AccountHandler) buildOpenAIAccountSchedulerScores(
ctx context.Context,
accounts []service.Account,
filterPool []service.Account,
) (map[int64]*AccountSchedulerScore, map[int64][]AccountSchedulerGroupScore) {
if len(accounts) == 0 {
return nil, nil
}
if len(filterPool) == 0 {
filterPool = accounts
}
pageOpenAIAccountIDs := make(map[int64]struct{})
groupIDs := make(map[int64]struct{})
for i := range accounts {
account := &accounts[i]
if account.Platform != service.PlatformOpenAI {
continue
}
pageOpenAIAccountIDs[account.ID] = struct{}{}
if len(account.AccountGroups) == 0 && len(account.GroupIDs) == 0 {
continue
}
for _, accountGroup := range account.AccountGroups {
if accountGroup.GroupID > 0 {
groupIDs[accountGroup.GroupID] = struct{}{}
}
}
for _, groupID := range account.GroupIDs {
if groupID > 0 {
groupIDs[groupID] = struct{}{}
}
}
}
if len(pageOpenAIAccountIDs) == 0 {
return nil, nil
}
// 先取各分组池,再对"过滤池 ∪ 分组池"的账号并集做一次负载批查,
// 避免每个池各查一次 Redis 的 N+1。
groupIDList := make([]int64, 0, len(groupIDs))
for groupID := range groupIDs {
groupIDList = append(groupIDList, groupID)
}
sort.Slice(groupIDList, func(i, j int) bool { return groupIDList[i] < groupIDList[j] })
groupPools := make(map[int64][]service.Account, len(groupIDList))
if h.adminService != nil {
for _, groupID := range groupIDList {
gid := groupID
pool, err := h.adminService.ListOpenAISchedulableAccountsForSchedulerScore(ctx, &gid)
if err != nil {
slog.Warn("openai_scheduler_group_score_pool_failed", "group_id", gid, "error", err)
continue
}
groupPools[gid] = pool
}
}
loadUnion := make([]*service.Account, 0, len(filterPool))
collectOpenAIAccounts := func(pool []service.Account) {
for i := range pool {
if pool[i].Platform == service.PlatformOpenAI {
loadUnion = append(loadUnion, &pool[i])
}
}
}
collectOpenAIAccounts(filterPool)
for _, pool := range groupPools {
collectOpenAIAccounts(pool)
}
loadMap := h.fetchOpenAIAccountLoadMap(ctx, loadUnion)
baseScores := make(map[int64]*AccountSchedulerScore)
for accountID, score := range h.scoreOpenAIAccountSchedulerPool(ctx, filterPool, loadMap) {
copiedScore := score
baseScores[accountID] = &copiedScore
}
groupScoresByAccount := make(map[int64][]AccountSchedulerGroupScore)
scoreGroupPool := func(groupID *int64, groupNameByID map[int64]string, groupPriorityByAccount map[int64]int, pool []service.Account) {
if len(pool) == 0 {
return
}
scores := h.scoreOpenAIAccountSchedulerPool(ctx, pool, loadMap)
for accountID, schedulerScore := range scores {
if _, ok := pageOpenAIAccountIDs[accountID]; !ok {
continue
}
groupScore := AccountSchedulerGroupScore{
GroupID: groupID,
AccountSchedulerScore: schedulerScore,
}
if groupID != nil {
groupScore.GroupName = groupNameByID[*groupID]
if priority, ok := groupPriorityByAccount[accountID]; ok {
groupScore.GroupPriority = &priority
}
}
groupScoresByAccount[accountID] = append(groupScoresByAccount[accountID], groupScore)
}
}
for _, groupID := range groupIDList {
gid := groupID
pool, ok := groupPools[gid]
if !ok {
continue
}
groupNameByID := make(map[int64]string)
groupPriorityByAccount := make(map[int64]int)
for i := range pool {
account := &pool[i]
for _, accountGroup := range account.AccountGroups {
if accountGroup.GroupID != gid {
continue
}
groupPriorityByAccount[account.ID] = accountGroup.Priority
if accountGroup.Group != nil {
groupNameByID[gid] = accountGroup.Group.Name
}
}
}
scoreGroupPool(&gid, groupNameByID, groupPriorityByAccount, pool)
}
for accountID := range groupScoresByAccount {
sort.SliceStable(groupScoresByAccount[accountID], func(i, j int) bool {
left := groupScoresByAccount[accountID][i]
right := groupScoresByAccount[accountID][j]
return *left.GroupID < *right.GroupID
})
}
return baseScores, groupScoresByAccount
}
func (h *AccountHandler) listAccountSchedulerScoreFilterPool(
ctx context.Context,
platform, accountType, status, search string,
groupID int64,
privacyMode string,
) []service.Account {
if h.adminService == nil || (platform != "" && platform != service.PlatformOpenAI) {
return nil
}
// 池只用于 OpenAI 分数计算(非 OpenAI 账号会在打分时被丢弃),
// 无论列表页平台过滤为何,查询一律限定 openai,避免无过滤时全表扫描。
accounts, err := h.adminService.ListAccountsForSchedulerScoreFilter(ctx, service.PlatformOpenAI, accountType, status, search, groupID, privacyMode)
if err != nil {
slog.Warn("openai_scheduler_filter_score_pool_failed", "error", err)
return nil
}
return accounts
}
// List handles listing all accounts with pagination
// GET /api/v1/admin/accounts
func (h *AccountHandler) List(c *gin.Context) {
@@ -278,6 +520,20 @@ func (h *AccountHandler) List(c *gin.Context) {
var windowCosts map[int64]float64
var activeSessions map[int64]int
var rpmCounts map[int64]int
// 仅当前页存在 OpenAI 账号时才计算调度分数,避免为空结果付出池查询开销。
var schedulerScores map[int64]*AccountSchedulerScore
var schedulerGroupScores map[int64][]AccountSchedulerGroupScore
pageHasOpenAIAccounts := false
for i := range accounts {
if accounts[i].Platform == service.PlatformOpenAI {
pageHasOpenAIAccounts = true
break
}
}
if pageHasOpenAIAccounts {
schedulerFilterPool := h.listAccountSchedulerScoreFilterPool(c.Request.Context(), platform, accountType, status, search, groupID, privacyMode)
schedulerScores, schedulerGroupScores = h.buildOpenAIAccountSchedulerScores(c.Request.Context(), accounts, schedulerFilterPool)
}
// 始终获取并发数(Redis ZCARD,极低开销)
if h.concurrencyService != nil {
@@ -358,6 +614,8 @@ func (h *AccountHandler) List(c *gin.Context) {
item := AccountWithConcurrency{
Account: dto.AccountFromService(acc),
CurrentConcurrency: concurrencyCounts[acc.ID],
SchedulerScore: schedulerScores[acc.ID],
SchedulerScores: schedulerGroupScores[acc.ID],
}
// 添加窗口费用(仅当启用时)
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
@@ -50,3 +51,222 @@ func TestAccountHandlerListIncludesCreatedAt(t *testing.T) {
_, offset := parsed.Zone()
require.Equal(t, 0, offset)
}
func TestAccountHandlerListReturnsSchedulerScoresPerGroup(t *testing.T) {
router, adminSvc := setupAccountListRouter()
now := time.Now().UTC()
groupID := int64(41)
adminSvc.accounts = []service.Account{
{
ID: 101,
Name: "account-high-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 1,
AccountGroups: []service.AccountGroup{
{AccountID: 101, GroupID: groupID, Priority: 100, Group: &service.Group{ID: groupID, Name: "openai"}},
},
GroupIDs: []int64{groupID},
CreatedAt: now,
UpdatedAt: now,
},
{
ID: 102,
Name: "account-low-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 100000,
AccountGroups: []service.AccountGroup{
{AccountID: 102, GroupID: groupID, Priority: 1, Group: &service.Group{ID: groupID, Name: "openai"}},
},
GroupIDs: []int64{groupID},
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)
var payload struct {
Data struct {
Items []struct {
ID int64 `json:"id"`
SchedulerScore struct {
BaseScore float64 `json:"base_score"`
} `json:"scheduler_score"`
SchedulerScores []struct {
GroupID *int64 `json:"group_id"`
GroupName string `json:"group_name"`
GroupPriority *int `json:"group_priority"`
BaseScore float64 `json:"base_score"`
} `json:"scheduler_scores"`
} `json:"items"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
require.Len(t, payload.Data.Items, 2)
var high, low *struct {
ID int64 `json:"id"`
SchedulerScore struct {
BaseScore float64 `json:"base_score"`
} `json:"scheduler_score"`
SchedulerScores []struct {
GroupID *int64 `json:"group_id"`
GroupName string `json:"group_name"`
GroupPriority *int `json:"group_priority"`
BaseScore float64 `json:"base_score"`
} `json:"scheduler_scores"`
}
for i := range payload.Data.Items {
item := &payload.Data.Items[i]
switch item.ID {
case 101:
high = item
case 102:
low = item
}
}
require.NotNil(t, high)
require.NotNil(t, low)
require.Len(t, high.SchedulerScores, 1)
require.Len(t, low.SchedulerScores, 1)
require.Equal(t, groupID, *high.SchedulerScores[0].GroupID)
require.Equal(t, "openai", high.SchedulerScores[0].GroupName)
require.Equal(t, 100, *high.SchedulerScores[0].GroupPriority)
require.Equal(t, 1, *low.SchedulerScores[0].GroupPriority)
require.Greater(t, high.SchedulerScores[0].BaseScore, low.SchedulerScores[0].BaseScore)
}
func TestAccountHandlerListKeepsSchedulerScoreScopedToFilter(t *testing.T) {
router, adminSvc := setupAccountListRouter()
now := time.Now().UTC()
groupID := int64(42)
visibleAccount := service.Account{
ID: 201,
Name: "visible-low-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 100000,
AccountGroups: []service.AccountGroup{
{AccountID: 201, GroupID: groupID, Priority: 1, Group: &service.Group{ID: groupID, Name: "openai"}},
},
GroupIDs: []int64{groupID},
CreatedAt: now,
UpdatedAt: now,
}
hiddenGroupPeer := service.Account{
ID: 202,
Name: "hidden-high-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 1,
AccountGroups: []service.AccountGroup{
{AccountID: 202, GroupID: groupID, Priority: 2, Group: &service.Group{ID: groupID, Name: "openai"}},
},
GroupIDs: []int64{groupID},
CreatedAt: now,
UpdatedAt: now,
}
adminSvc.accounts = []service.Account{visibleAccount}
adminSvc.accountSchedulerScoreFilterAccounts = []service.Account{visibleAccount, hiddenGroupPeer}
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)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
var payload struct {
Data struct {
Items []struct {
ID int64 `json:"id"`
SchedulerScore struct {
BaseScore float64 `json:"base_score"`
} `json:"scheduler_score"`
SchedulerScores []struct {
GroupID *int64 `json:"group_id"`
BaseScore float64 `json:"base_score"`
} `json:"scheduler_scores"`
} `json:"items"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
require.Len(t, payload.Data.Items, 1)
item := payload.Data.Items[0]
require.Equal(t, int64(201), item.ID)
require.Len(t, item.SchedulerScores, 1)
require.Equal(t, groupID, *item.SchedulerScores[0].GroupID)
require.Equal(t, item.SchedulerScores[0].BaseScore, item.SchedulerScore.BaseScore)
}
func TestAccountHandlerListSchedulerScoreIgnoresPagination(t *testing.T) {
router, adminSvc := setupAccountListRouter()
now := time.Now().UTC()
visibleAccount := service.Account{
ID: 301,
Name: "visible-low-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 100000,
CreatedAt: now,
UpdatedAt: now,
}
hiddenFilterPeer := service.Account{
ID: 302,
Name: "hidden-high-priority",
Platform: service.PlatformOpenAI,
Type: service.AccountTypeAPIKey,
Status: service.StatusActive,
Schedulable: true,
Concurrency: 10,
Priority: 1,
CreatedAt: now,
UpdatedAt: now,
}
adminSvc.accounts = []service.Account{visibleAccount}
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)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
var payload struct {
Data struct {
Items []struct {
ID int64 `json:"id"`
SchedulerScore struct {
BaseScore float64 `json:"base_score"`
} `json:"scheduler_score"`
SchedulerScores []struct {
GroupID *int64 `json:"group_id"`
BaseScore float64 `json:"base_score"`
} `json:"scheduler_scores"`
} `json:"items"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
require.Len(t, payload.Data.Items, 1)
require.Equal(t, int64(301), payload.Data.Items[0].ID)
require.Less(t, payload.Data.Items[0].SchedulerScore.BaseScore, 3.75)
require.Empty(t, payload.Data.Items[0].SchedulerScores)
}
@@ -10,27 +10,29 @@ import (
)
type stubAdminService struct {
users []service.User
apiKeys []service.APIKey
groups []service.Group
accounts []service.Account
proxies []service.Proxy
proxyCounts []service.ProxyWithAccountCount
redeems []service.RedeemCode
boundAuthIdentity *service.AdminBindAuthIdentityInput
boundAuthIdentityFor int64
createdAccounts []*service.CreateAccountInput
createdProxies []*service.CreateProxyInput
updatedProxyIDs []int64
updatedProxies []*service.UpdateProxyInput
testedProxyIDs []int64
getUserErr error
createAccountErr error
createSparkShadowErr error
updateAccountErr error
bulkUpdateAccountErr error
checkMixedErr error
lastMixedCheck struct {
users []service.User
apiKeys []service.APIKey
groups []service.Group
accounts []service.Account
accountSchedulerScoreFilterAccounts []service.Account
openAISchedulerScorePoolAccounts []service.Account
proxies []service.Proxy
proxyCounts []service.ProxyWithAccountCount
redeems []service.RedeemCode
boundAuthIdentity *service.AdminBindAuthIdentityInput
boundAuthIdentityFor int64
createdAccounts []*service.CreateAccountInput
createdProxies []*service.CreateProxyInput
updatedProxyIDs []int64
updatedProxies []*service.UpdateProxyInput
testedProxyIDs []int64
getUserErr error
createAccountErr error
createSparkShadowErr error
updateAccountErr error
bulkUpdateAccountErr error
checkMixedErr error
lastMixedCheck struct {
accountID int64
platform string
groupIDs []int64
@@ -329,7 +331,56 @@ func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int,
s.lastListAccounts.sortBy = sortBy
s.lastListAccounts.sortOrder = sortOrder
s.lastListAccounts.calls++
return s.accounts, int64(len(s.accounts)), nil
accounts := s.accounts
total := len(accounts)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = total
}
start := (page - 1) * pageSize
if start >= total {
return []service.Account{}, int64(total), nil
}
end := start + pageSize
if end > total {
end = total
}
return accounts[start:end], int64(total), nil
}
func (s *stubAdminService) ListAccountsForSchedulerScoreFilter(_ context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, error) {
if s.accountSchedulerScoreFilterAccounts != nil {
return s.accountSchedulerScoreFilterAccounts, nil
}
return s.accounts, nil
}
func (s *stubAdminService) ListOpenAISchedulableAccountsForSchedulerScore(_ context.Context, groupID *int64) ([]service.Account, error) {
accounts := s.openAISchedulerScorePoolAccounts
if accounts == nil {
accounts = s.accounts
}
out := make([]service.Account, 0, len(accounts))
for _, account := range accounts {
if account.Platform != service.PlatformOpenAI || !account.IsSchedulable() {
continue
}
if groupID == nil {
if len(account.AccountGroups) == 0 && len(account.GroupIDs) == 0 {
out = append(out, account)
}
continue
}
for _, accountGroup := range account.AccountGroups {
if accountGroup.GroupID == *groupID {
out = append(out, account)
break
}
}
}
return out, nil
}
func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
@@ -93,8 +93,11 @@ 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"`
PeakRateEnabled bool `json:"peak_rate_enabled"`
PeakStart string `json:"peak_start"`
PeakEnd string `json:"peak_end"`
@@ -138,8 +141,11 @@ 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"`
PeakRateEnabled *bool `json:"peak_rate_enabled"`
PeakStart *string `json:"peak_start"`
PeakEnd *string `json:"peak_end"`
@@ -301,8 +307,11 @@ 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,
PeakRateEnabled: req.PeakRateEnabled,
PeakStart: req.PeakStart,
PeakEnd: req.PeakEnd,
@@ -361,8 +370,11 @@ 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,
PeakRateEnabled: req.PeakRateEnabled,
PeakStart: req.PeakStart,
PeakEnd: req.PeakEnd,
+488 -362
View File
@@ -119,188 +119,211 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
}
payload := dto.SystemSettings{
RegistrationEnabled: settings.RegistrationEnabled,
EmailVerifyEnabled: settings.EmailVerifyEnabled,
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
PromoCodeEnabled: settings.PromoCodeEnabled,
PasswordResetEnabled: settings.PasswordResetEnabled,
FrontendURL: settings.FrontendURL,
InvitationCodeEnabled: settings.InvitationCodeEnabled,
TotpEnabled: settings.TotpEnabled,
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
LoginAgreementEnabled: settings.LoginAgreementEnabled,
LoginAgreementMode: settings.LoginAgreementMode,
LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
LoginAgreementDocuments: loginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
SMTPHost: settings.SMTPHost,
SMTPPort: settings.SMTPPort,
SMTPUsername: settings.SMTPUsername,
SMTPPasswordConfigured: settings.SMTPPasswordConfigured,
SMTPFrom: settings.SMTPFrom,
SMTPFromName: settings.SMTPFromName,
SMTPUseTLS: settings.SMTPUseTLS,
TurnstileEnabled: settings.TurnstileEnabled,
TurnstileSiteKey: settings.TurnstileSiteKey,
TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured,
APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP,
LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled,
LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
DingTalkConnectEnabled: settings.DingTalkConnectEnabled,
DingTalkConnectClientID: settings.DingTalkConnectClientID,
DingTalkConnectClientSecretConfigured: settings.DingTalkConnectClientSecretConfigured,
DingTalkConnectRedirectURL: settings.DingTalkConnectRedirectURL,
DingTalkConnectCorpRestrictionPolicy: settings.DingTalkConnectCorpRestrictionPolicy,
DingTalkConnectInternalCorpID: settings.DingTalkConnectInternalCorpID,
DingTalkConnectBypassRegistration: settings.DingTalkConnectBypassRegistration,
DingTalkConnectSyncCorpEmail: settings.DingTalkConnectSyncCorpEmail,
DingTalkConnectSyncDisplayName: settings.DingTalkConnectSyncDisplayName,
DingTalkConnectSyncDept: settings.DingTalkConnectSyncDept,
DingTalkConnectSyncCorpEmailAttrKey: settings.DingTalkConnectSyncCorpEmailAttrKey,
DingTalkConnectSyncDisplayNameAttrKey: settings.DingTalkConnectSyncDisplayNameAttrKey,
DingTalkConnectSyncDeptAttrKey: settings.DingTalkConnectSyncDeptAttrKey,
DingTalkConnectSyncCorpEmailAttrName: settings.DingTalkConnectSyncCorpEmailAttrName,
DingTalkConnectSyncDisplayNameAttrName: settings.DingTalkConnectSyncDisplayNameAttrName,
DingTalkConnectSyncDeptAttrName: settings.DingTalkConnectSyncDeptAttrName,
WeChatConnectEnabled: settings.WeChatConnectEnabled,
WeChatConnectAppID: settings.WeChatConnectAppID,
WeChatConnectAppSecretConfigured: settings.WeChatConnectAppSecretConfigured,
WeChatConnectOpenAppID: settings.WeChatConnectOpenAppID,
WeChatConnectOpenAppSecretConfigured: settings.WeChatConnectOpenAppSecretConfigured,
WeChatConnectMPAppID: settings.WeChatConnectMPAppID,
WeChatConnectMPAppSecretConfigured: settings.WeChatConnectMPAppSecretConfigured,
WeChatConnectMobileAppID: settings.WeChatConnectMobileAppID,
WeChatConnectMobileAppSecretConfigured: settings.WeChatConnectMobileAppSecretConfigured,
WeChatConnectOpenEnabled: settings.WeChatConnectOpenEnabled,
WeChatConnectMPEnabled: settings.WeChatConnectMPEnabled,
WeChatConnectMobileEnabled: settings.WeChatConnectMobileEnabled,
WeChatConnectMode: settings.WeChatConnectMode,
WeChatConnectScopes: settings.WeChatConnectScopes,
WeChatConnectRedirectURL: settings.WeChatConnectRedirectURL,
WeChatConnectFrontendRedirectURL: settings.WeChatConnectFrontendRedirectURL,
OIDCConnectEnabled: settings.OIDCConnectEnabled,
OIDCConnectProviderName: settings.OIDCConnectProviderName,
OIDCConnectClientID: settings.OIDCConnectClientID,
OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
OIDCConnectScopes: settings.OIDCConnectScopes,
OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
GitHubOAuthClientID: settings.GitHubOAuthClientID,
GitHubOAuthClientSecretConfigured: settings.GitHubOAuthClientSecretConfigured,
GitHubOAuthRedirectURL: settings.GitHubOAuthRedirectURL,
GitHubOAuthFrontendRedirectURL: settings.GitHubOAuthFrontendRedirectURL,
GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
GoogleOAuthClientID: settings.GoogleOAuthClientID,
GoogleOAuthClientSecretConfigured: settings.GoogleOAuthClientSecretConfigured,
GoogleOAuthRedirectURL: settings.GoogleOAuthRedirectURL,
GoogleOAuthFrontendRedirectURL: settings.GoogleOAuthFrontendRedirectURL,
SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle,
APIBaseURL: settings.APIBaseURL,
ContactInfo: settings.ContactInfo,
DocURL: settings.DocURL,
HomeContent: settings.HomeContent,
HideCcsImportButton: settings.HideCcsImportButton,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
TableDefaultPageSize: settings.TableDefaultPageSize,
TablePageSizeOptions: settings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
DefaultConcurrency: settings.DefaultConcurrency,
DefaultBalance: settings.DefaultBalance,
RiskControlEnabled: settings.RiskControlEnabled,
CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
AffiliateRebateRate: settings.AffiliateRebateRate,
AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
DefaultSubscriptions: defaultSubscriptions,
EnableModelFallback: settings.EnableModelFallback,
FallbackModelAnthropic: settings.FallbackModelAnthropic,
FallbackModelOpenAI: settings.FallbackModelOpenAI,
FallbackModelGemini: settings.FallbackModelGemini,
FallbackModelAntigravity: settings.FallbackModelAntigravity,
EnableIdentityPatch: settings.EnableIdentityPatch,
IdentityPatchPrompt: settings.IdentityPatchPrompt,
OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
OpsQueryModeDefault: settings.OpsQueryModeDefault,
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
BackendModeEnabled: settings.BackendModeEnabled,
EnableFingerprintUnification: settings.EnableFingerprintUnification,
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
EnableCCHSigning: settings.EnableCCHSigning,
EnableClaudeOAuthSystemPromptInjection: settings.EnableClaudeOAuthSystemPromptInjection,
ClaudeOAuthSystemPrompt: settings.ClaudeOAuthSystemPrompt,
ClaudeOAuthSystemPromptBlocks: settings.ClaudeOAuthSystemPromptBlocks,
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
EnableClientDatelineNormalization: settings.EnableClientDatelineNormalization,
AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
MinCodexVersion: settings.MinCodexVersion,
MaxCodexVersion: settings.MaxCodexVersion,
CodexCLIOnlyBlacklist: settings.CodexCLIOnlyBlacklist,
CodexCLIOnlyWhitelist: settings.CodexCLIOnlyWhitelist,
CodexCLIOnlyAllowAppServerClients: settings.CodexCLIOnlyAllowAppServerClients,
CodexCLIOnlyEngineFingerprintSignals: settings.CodexCLIOnlyEngineFingerprintSignals,
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
PaymentVisibleMethodAlipayEnabled: settings.PaymentVisibleMethodAlipayEnabled,
PaymentVisibleMethodWxpayEnabled: settings.PaymentVisibleMethodWxpayEnabled,
OpenAIAdvancedSchedulerEnabled: settings.OpenAIAdvancedSchedulerEnabled,
BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
SubscriptionExpiryNotifyEnabled: settings.SubscriptionExpiryNotifyEnabled,
AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(settings.AccountQuotaNotifyEmails),
PaymentEnabled: paymentCfg.Enabled,
PaymentMinAmount: paymentCfg.MinAmount,
PaymentMaxAmount: paymentCfg.MaxAmount,
PaymentDailyLimit: paymentCfg.DailyLimit,
PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
PaymentEnabledTypes: paymentCfg.EnabledTypes,
PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
PaymentHelpImageURL: paymentCfg.HelpImageURL,
PaymentHelpText: paymentCfg.HelpText,
PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
RegistrationEnabled: settings.RegistrationEnabled,
EmailVerifyEnabled: settings.EmailVerifyEnabled,
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
PromoCodeEnabled: settings.PromoCodeEnabled,
PasswordResetEnabled: settings.PasswordResetEnabled,
FrontendURL: settings.FrontendURL,
InvitationCodeEnabled: settings.InvitationCodeEnabled,
TotpEnabled: settings.TotpEnabled,
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
LoginAgreementEnabled: settings.LoginAgreementEnabled,
LoginAgreementMode: settings.LoginAgreementMode,
LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
LoginAgreementDocuments: loginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
SMTPHost: settings.SMTPHost,
SMTPPort: settings.SMTPPort,
SMTPUsername: settings.SMTPUsername,
SMTPPasswordConfigured: settings.SMTPPasswordConfigured,
SMTPFrom: settings.SMTPFrom,
SMTPFromName: settings.SMTPFromName,
SMTPUseTLS: settings.SMTPUseTLS,
TurnstileEnabled: settings.TurnstileEnabled,
TurnstileSiteKey: settings.TurnstileSiteKey,
TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured,
APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP,
LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled,
LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
DingTalkConnectEnabled: settings.DingTalkConnectEnabled,
DingTalkConnectClientID: settings.DingTalkConnectClientID,
DingTalkConnectClientSecretConfigured: settings.DingTalkConnectClientSecretConfigured,
DingTalkConnectRedirectURL: settings.DingTalkConnectRedirectURL,
DingTalkConnectCorpRestrictionPolicy: settings.DingTalkConnectCorpRestrictionPolicy,
DingTalkConnectInternalCorpID: settings.DingTalkConnectInternalCorpID,
DingTalkConnectBypassRegistration: settings.DingTalkConnectBypassRegistration,
DingTalkConnectSyncCorpEmail: settings.DingTalkConnectSyncCorpEmail,
DingTalkConnectSyncDisplayName: settings.DingTalkConnectSyncDisplayName,
DingTalkConnectSyncDept: settings.DingTalkConnectSyncDept,
DingTalkConnectSyncCorpEmailAttrKey: settings.DingTalkConnectSyncCorpEmailAttrKey,
DingTalkConnectSyncDisplayNameAttrKey: settings.DingTalkConnectSyncDisplayNameAttrKey,
DingTalkConnectSyncDeptAttrKey: settings.DingTalkConnectSyncDeptAttrKey,
DingTalkConnectSyncCorpEmailAttrName: settings.DingTalkConnectSyncCorpEmailAttrName,
DingTalkConnectSyncDisplayNameAttrName: settings.DingTalkConnectSyncDisplayNameAttrName,
DingTalkConnectSyncDeptAttrName: settings.DingTalkConnectSyncDeptAttrName,
WeChatConnectEnabled: settings.WeChatConnectEnabled,
WeChatConnectAppID: settings.WeChatConnectAppID,
WeChatConnectAppSecretConfigured: settings.WeChatConnectAppSecretConfigured,
WeChatConnectOpenAppID: settings.WeChatConnectOpenAppID,
WeChatConnectOpenAppSecretConfigured: settings.WeChatConnectOpenAppSecretConfigured,
WeChatConnectMPAppID: settings.WeChatConnectMPAppID,
WeChatConnectMPAppSecretConfigured: settings.WeChatConnectMPAppSecretConfigured,
WeChatConnectMobileAppID: settings.WeChatConnectMobileAppID,
WeChatConnectMobileAppSecretConfigured: settings.WeChatConnectMobileAppSecretConfigured,
WeChatConnectOpenEnabled: settings.WeChatConnectOpenEnabled,
WeChatConnectMPEnabled: settings.WeChatConnectMPEnabled,
WeChatConnectMobileEnabled: settings.WeChatConnectMobileEnabled,
WeChatConnectMode: settings.WeChatConnectMode,
WeChatConnectScopes: settings.WeChatConnectScopes,
WeChatConnectRedirectURL: settings.WeChatConnectRedirectURL,
WeChatConnectFrontendRedirectURL: settings.WeChatConnectFrontendRedirectURL,
OIDCConnectEnabled: settings.OIDCConnectEnabled,
OIDCConnectProviderName: settings.OIDCConnectProviderName,
OIDCConnectClientID: settings.OIDCConnectClientID,
OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
OIDCConnectScopes: settings.OIDCConnectScopes,
OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
GitHubOAuthClientID: settings.GitHubOAuthClientID,
GitHubOAuthClientSecretConfigured: settings.GitHubOAuthClientSecretConfigured,
GitHubOAuthRedirectURL: settings.GitHubOAuthRedirectURL,
GitHubOAuthFrontendRedirectURL: settings.GitHubOAuthFrontendRedirectURL,
GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
GoogleOAuthClientID: settings.GoogleOAuthClientID,
GoogleOAuthClientSecretConfigured: settings.GoogleOAuthClientSecretConfigured,
GoogleOAuthRedirectURL: settings.GoogleOAuthRedirectURL,
GoogleOAuthFrontendRedirectURL: settings.GoogleOAuthFrontendRedirectURL,
SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle,
APIBaseURL: settings.APIBaseURL,
ContactInfo: settings.ContactInfo,
DocURL: settings.DocURL,
HomeContent: settings.HomeContent,
HideCcsImportButton: settings.HideCcsImportButton,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
TableDefaultPageSize: settings.TableDefaultPageSize,
TablePageSizeOptions: settings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
DefaultConcurrency: settings.DefaultConcurrency,
DefaultBalance: settings.DefaultBalance,
RiskControlEnabled: settings.RiskControlEnabled,
CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
AffiliateRebateRate: settings.AffiliateRebateRate,
AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
DefaultSubscriptions: defaultSubscriptions,
EnableModelFallback: settings.EnableModelFallback,
FallbackModelAnthropic: settings.FallbackModelAnthropic,
FallbackModelOpenAI: settings.FallbackModelOpenAI,
FallbackModelGemini: settings.FallbackModelGemini,
FallbackModelAntigravity: settings.FallbackModelAntigravity,
EnableIdentityPatch: settings.EnableIdentityPatch,
IdentityPatchPrompt: settings.IdentityPatchPrompt,
OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
OpsQueryModeDefault: settings.OpsQueryModeDefault,
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
BackendModeEnabled: settings.BackendModeEnabled,
EnableFingerprintUnification: settings.EnableFingerprintUnification,
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
EnableCCHSigning: settings.EnableCCHSigning,
EnableClaudeOAuthSystemPromptInjection: settings.EnableClaudeOAuthSystemPromptInjection,
ClaudeOAuthSystemPrompt: settings.ClaudeOAuthSystemPrompt,
ClaudeOAuthSystemPromptBlocks: settings.ClaudeOAuthSystemPromptBlocks,
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
EnableClientDatelineNormalization: settings.EnableClientDatelineNormalization,
AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
MinCodexVersion: settings.MinCodexVersion,
MaxCodexVersion: settings.MaxCodexVersion,
CodexCLIOnlyBlacklist: settings.CodexCLIOnlyBlacklist,
CodexCLIOnlyWhitelist: settings.CodexCLIOnlyWhitelist,
CodexCLIOnlyAllowAppServerClients: settings.CodexCLIOnlyAllowAppServerClients,
CodexCLIOnlyEngineFingerprintSignals: settings.CodexCLIOnlyEngineFingerprintSignals,
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
PaymentVisibleMethodAlipayEnabled: settings.PaymentVisibleMethodAlipayEnabled,
PaymentVisibleMethodWxpayEnabled: settings.PaymentVisibleMethodWxpayEnabled,
OpenAIAdvancedSchedulerEnabled: settings.OpenAIAdvancedSchedulerEnabled,
OpenAIAdvancedSchedulerStickyWeightedEnabled: settings.OpenAIAdvancedSchedulerStickyWeightedEnabled,
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: settings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled,
OpenAIAdvancedSchedulerLBTopK: settings.OpenAIAdvancedSchedulerLBTopK,
OpenAIAdvancedSchedulerWeightPriority: settings.OpenAIAdvancedSchedulerWeightPriority,
OpenAIAdvancedSchedulerWeightLoad: settings.OpenAIAdvancedSchedulerWeightLoad,
OpenAIAdvancedSchedulerWeightQueue: settings.OpenAIAdvancedSchedulerWeightQueue,
OpenAIAdvancedSchedulerWeightErrorRate: settings.OpenAIAdvancedSchedulerWeightErrorRate,
OpenAIAdvancedSchedulerWeightTTFT: settings.OpenAIAdvancedSchedulerWeightTTFT,
OpenAIAdvancedSchedulerWeightReset: settings.OpenAIAdvancedSchedulerWeightReset,
OpenAIAdvancedSchedulerWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerWeightQuotaHeadroom,
OpenAIAdvancedSchedulerWeightPreviousResponse: settings.OpenAIAdvancedSchedulerWeightPreviousResponse,
OpenAIAdvancedSchedulerWeightSessionSticky: settings.OpenAIAdvancedSchedulerWeightSessionSticky,
OpenAIAdvancedSchedulerEffectiveLBTopK: settings.OpenAIAdvancedSchedulerEffectiveLBTopK,
OpenAIAdvancedSchedulerEffectiveWeightPriority: settings.OpenAIAdvancedSchedulerEffectiveWeightPriority,
OpenAIAdvancedSchedulerEffectiveWeightLoad: settings.OpenAIAdvancedSchedulerEffectiveWeightLoad,
OpenAIAdvancedSchedulerEffectiveWeightQueue: settings.OpenAIAdvancedSchedulerEffectiveWeightQueue,
OpenAIAdvancedSchedulerEffectiveWeightErrorRate: settings.OpenAIAdvancedSchedulerEffectiveWeightErrorRate,
OpenAIAdvancedSchedulerEffectiveWeightTTFT: settings.OpenAIAdvancedSchedulerEffectiveWeightTTFT,
OpenAIAdvancedSchedulerEffectiveWeightReset: settings.OpenAIAdvancedSchedulerEffectiveWeightReset,
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom,
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse: settings.OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse,
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky: settings.OpenAIAdvancedSchedulerEffectiveWeightSessionSticky,
BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
SubscriptionExpiryNotifyEnabled: settings.SubscriptionExpiryNotifyEnabled,
AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(settings.AccountQuotaNotifyEmails),
PaymentEnabled: paymentCfg.Enabled,
PaymentMinAmount: paymentCfg.MinAmount,
PaymentMaxAmount: paymentCfg.MaxAmount,
PaymentDailyLimit: paymentCfg.DailyLimit,
PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
PaymentEnabledTypes: paymentCfg.EnabledTypes,
PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
PaymentSubscriptionUSDToCNYRate: paymentCfg.SubscriptionUSDToCNYRate,
PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
PaymentHelpImageURL: paymentCfg.HelpImageURL,
PaymentHelpText: paymentCfg.HelpText,
PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
@@ -618,7 +641,19 @@ type UpdateSettingsRequest struct {
PaymentVisibleMethodWxpayEnabled *bool `json:"payment_visible_method_wxpay_enabled"`
// OpenAI account scheduling
OpenAIAdvancedSchedulerEnabled *bool `json:"openai_advanced_scheduler_enabled"`
OpenAIAdvancedSchedulerEnabled *bool `json:"openai_advanced_scheduler_enabled"`
OpenAIAdvancedSchedulerStickyWeightedEnabled *bool `json:"openai_advanced_scheduler_sticky_weighted_enabled"`
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled *bool `json:"openai_advanced_scheduler_subscription_priority_enabled"`
OpenAIAdvancedSchedulerLBTopK *string `json:"openai_advanced_scheduler_lb_top_k"`
OpenAIAdvancedSchedulerWeightPriority *string `json:"openai_advanced_scheduler_weight_priority"`
OpenAIAdvancedSchedulerWeightLoad *string `json:"openai_advanced_scheduler_weight_load"`
OpenAIAdvancedSchedulerWeightQueue *string `json:"openai_advanced_scheduler_weight_queue"`
OpenAIAdvancedSchedulerWeightErrorRate *string `json:"openai_advanced_scheduler_weight_error_rate"`
OpenAIAdvancedSchedulerWeightTTFT *string `json:"openai_advanced_scheduler_weight_ttft"`
OpenAIAdvancedSchedulerWeightReset *string `json:"openai_advanced_scheduler_weight_reset"`
OpenAIAdvancedSchedulerWeightQuotaHeadroom *string `json:"openai_advanced_scheduler_weight_quota_headroom"`
OpenAIAdvancedSchedulerWeightPreviousResponse *string `json:"openai_advanced_scheduler_weight_previous_response"`
OpenAIAdvancedSchedulerWeightSessionSticky *string `json:"openai_advanced_scheduler_weight_session_sticky"`
// 余额不足提醒
BalanceLowNotifyEnabled *bool `json:"balance_low_notify_enabled"`
@@ -638,6 +673,7 @@ type UpdateSettingsRequest struct {
PaymentEnabledTypes []string `json:"payment_enabled_types"`
PaymentBalanceDisabled *bool `json:"payment_balance_disabled"`
PaymentBalanceRechargeMultiplier *float64 `json:"payment_balance_recharge_multiplier"`
PaymentSubscriptionUSDToCNYRate *float64 `json:"payment_subscription_usd_to_cny_rate"`
PaymentRechargeFeeRate *float64 `json:"payment_recharge_fee_rate"`
PaymentLoadBalanceStrat *string `json:"payment_load_balance_strategy"`
PaymentProductNamePrefix *string `json:"payment_product_name_prefix"`
@@ -1792,6 +1828,28 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
return previousSettings.OpenAIAdvancedSchedulerEnabled
}(),
OpenAIAdvancedSchedulerStickyWeightedEnabled: func() bool {
if req.OpenAIAdvancedSchedulerStickyWeightedEnabled != nil {
return *req.OpenAIAdvancedSchedulerStickyWeightedEnabled
}
return previousSettings.OpenAIAdvancedSchedulerStickyWeightedEnabled
}(),
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: func() bool {
if req.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled != nil {
return *req.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled
}
return previousSettings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled
}(),
OpenAIAdvancedSchedulerLBTopK: stringSetting(req.OpenAIAdvancedSchedulerLBTopK, previousSettings.OpenAIAdvancedSchedulerLBTopK),
OpenAIAdvancedSchedulerWeightPriority: stringSetting(req.OpenAIAdvancedSchedulerWeightPriority, previousSettings.OpenAIAdvancedSchedulerWeightPriority),
OpenAIAdvancedSchedulerWeightLoad: stringSetting(req.OpenAIAdvancedSchedulerWeightLoad, previousSettings.OpenAIAdvancedSchedulerWeightLoad),
OpenAIAdvancedSchedulerWeightQueue: stringSetting(req.OpenAIAdvancedSchedulerWeightQueue, previousSettings.OpenAIAdvancedSchedulerWeightQueue),
OpenAIAdvancedSchedulerWeightErrorRate: stringSetting(req.OpenAIAdvancedSchedulerWeightErrorRate, previousSettings.OpenAIAdvancedSchedulerWeightErrorRate),
OpenAIAdvancedSchedulerWeightTTFT: stringSetting(req.OpenAIAdvancedSchedulerWeightTTFT, previousSettings.OpenAIAdvancedSchedulerWeightTTFT),
OpenAIAdvancedSchedulerWeightReset: stringSetting(req.OpenAIAdvancedSchedulerWeightReset, previousSettings.OpenAIAdvancedSchedulerWeightReset),
OpenAIAdvancedSchedulerWeightQuotaHeadroom: stringSetting(req.OpenAIAdvancedSchedulerWeightQuotaHeadroom, previousSettings.OpenAIAdvancedSchedulerWeightQuotaHeadroom),
OpenAIAdvancedSchedulerWeightPreviousResponse: stringSetting(req.OpenAIAdvancedSchedulerWeightPreviousResponse, previousSettings.OpenAIAdvancedSchedulerWeightPreviousResponse),
OpenAIAdvancedSchedulerWeightSessionSticky: stringSetting(req.OpenAIAdvancedSchedulerWeightSessionSticky, previousSettings.OpenAIAdvancedSchedulerWeightSessionSticky),
BalanceLowNotifyEnabled: func() bool {
if req.BalanceLowNotifyEnabled != nil {
return *req.BalanceLowNotifyEnabled
@@ -1959,6 +2017,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
EnabledTypes: req.PaymentEnabledTypes,
BalanceDisabled: req.PaymentBalanceDisabled,
BalanceRechargeMultiplier: req.PaymentBalanceRechargeMultiplier,
SubscriptionUSDToCNYRate: req.PaymentSubscriptionUSDToCNYRate,
RechargeFeeRate: req.PaymentRechargeFeeRate,
LoadBalanceStrategy: req.PaymentLoadBalanceStrat,
ProductNamePrefix: req.PaymentProductNamePrefix,
@@ -2014,184 +2073,207 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
payload := dto.SystemSettings{
RegistrationEnabled: updatedSettings.RegistrationEnabled,
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
RegistrationEmailSuffixWhitelist: updatedSettings.RegistrationEmailSuffixWhitelist,
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
FrontendURL: updatedSettings.FrontendURL,
InvitationCodeEnabled: updatedSettings.InvitationCodeEnabled,
TotpEnabled: updatedSettings.TotpEnabled,
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
LoginAgreementEnabled: updatedSettings.LoginAgreementEnabled,
LoginAgreementMode: updatedSettings.LoginAgreementMode,
LoginAgreementUpdatedAt: updatedSettings.LoginAgreementUpdatedAt,
LoginAgreementDocuments: loginAgreementDocumentsToDTO(updatedSettings.LoginAgreementDocuments),
SMTPHost: updatedSettings.SMTPHost,
SMTPPort: updatedSettings.SMTPPort,
SMTPUsername: updatedSettings.SMTPUsername,
SMTPPasswordConfigured: updatedSettings.SMTPPasswordConfigured,
SMTPFrom: updatedSettings.SMTPFrom,
SMTPFromName: updatedSettings.SMTPFromName,
SMTPUseTLS: updatedSettings.SMTPUseTLS,
TurnstileEnabled: updatedSettings.TurnstileEnabled,
TurnstileSiteKey: updatedSettings.TurnstileSiteKey,
TurnstileSecretKeyConfigured: updatedSettings.TurnstileSecretKeyConfigured,
APIKeyACLTrustForwardedIP: updatedSettings.APIKeyACLTrustForwardedIP,
LinuxDoConnectEnabled: updatedSettings.LinuxDoConnectEnabled,
LinuxDoConnectClientID: updatedSettings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: updatedSettings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: updatedSettings.LinuxDoConnectRedirectURL,
DingTalkConnectEnabled: updatedSettings.DingTalkConnectEnabled,
DingTalkConnectClientID: updatedSettings.DingTalkConnectClientID,
DingTalkConnectClientSecretConfigured: updatedSettings.DingTalkConnectClientSecretConfigured,
DingTalkConnectRedirectURL: updatedSettings.DingTalkConnectRedirectURL,
DingTalkConnectCorpRestrictionPolicy: updatedSettings.DingTalkConnectCorpRestrictionPolicy,
DingTalkConnectInternalCorpID: updatedSettings.DingTalkConnectInternalCorpID,
DingTalkConnectBypassRegistration: updatedSettings.DingTalkConnectBypassRegistration,
DingTalkConnectSyncCorpEmail: updatedSettings.DingTalkConnectSyncCorpEmail,
DingTalkConnectSyncDisplayName: updatedSettings.DingTalkConnectSyncDisplayName,
DingTalkConnectSyncDept: updatedSettings.DingTalkConnectSyncDept,
DingTalkConnectSyncCorpEmailAttrKey: updatedSettings.DingTalkConnectSyncCorpEmailAttrKey,
DingTalkConnectSyncDisplayNameAttrKey: updatedSettings.DingTalkConnectSyncDisplayNameAttrKey,
DingTalkConnectSyncDeptAttrKey: updatedSettings.DingTalkConnectSyncDeptAttrKey,
DingTalkConnectSyncCorpEmailAttrName: updatedSettings.DingTalkConnectSyncCorpEmailAttrName,
DingTalkConnectSyncDisplayNameAttrName: updatedSettings.DingTalkConnectSyncDisplayNameAttrName,
DingTalkConnectSyncDeptAttrName: updatedSettings.DingTalkConnectSyncDeptAttrName,
WeChatConnectEnabled: updatedSettings.WeChatConnectEnabled,
WeChatConnectAppID: updatedSettings.WeChatConnectAppID,
WeChatConnectAppSecretConfigured: updatedSettings.WeChatConnectAppSecretConfigured,
WeChatConnectOpenAppID: updatedSettings.WeChatConnectOpenAppID,
WeChatConnectOpenAppSecretConfigured: updatedSettings.WeChatConnectOpenAppSecretConfigured,
WeChatConnectMPAppID: updatedSettings.WeChatConnectMPAppID,
WeChatConnectMPAppSecretConfigured: updatedSettings.WeChatConnectMPAppSecretConfigured,
WeChatConnectMobileAppID: updatedSettings.WeChatConnectMobileAppID,
WeChatConnectMobileAppSecretConfigured: updatedSettings.WeChatConnectMobileAppSecretConfigured,
WeChatConnectOpenEnabled: updatedSettings.WeChatConnectOpenEnabled,
WeChatConnectMPEnabled: updatedSettings.WeChatConnectMPEnabled,
WeChatConnectMobileEnabled: updatedSettings.WeChatConnectMobileEnabled,
WeChatConnectMode: updatedSettings.WeChatConnectMode,
WeChatConnectScopes: updatedSettings.WeChatConnectScopes,
WeChatConnectRedirectURL: updatedSettings.WeChatConnectRedirectURL,
WeChatConnectFrontendRedirectURL: updatedSettings.WeChatConnectFrontendRedirectURL,
OIDCConnectEnabled: updatedSettings.OIDCConnectEnabled,
OIDCConnectProviderName: updatedSettings.OIDCConnectProviderName,
OIDCConnectClientID: updatedSettings.OIDCConnectClientID,
OIDCConnectClientSecretConfigured: updatedSettings.OIDCConnectClientSecretConfigured,
OIDCConnectIssuerURL: updatedSettings.OIDCConnectIssuerURL,
OIDCConnectDiscoveryURL: updatedSettings.OIDCConnectDiscoveryURL,
OIDCConnectAuthorizeURL: updatedSettings.OIDCConnectAuthorizeURL,
OIDCConnectTokenURL: updatedSettings.OIDCConnectTokenURL,
OIDCConnectUserInfoURL: updatedSettings.OIDCConnectUserInfoURL,
OIDCConnectJWKSURL: updatedSettings.OIDCConnectJWKSURL,
OIDCConnectScopes: updatedSettings.OIDCConnectScopes,
OIDCConnectRedirectURL: updatedSettings.OIDCConnectRedirectURL,
OIDCConnectFrontendRedirectURL: updatedSettings.OIDCConnectFrontendRedirectURL,
OIDCConnectTokenAuthMethod: updatedSettings.OIDCConnectTokenAuthMethod,
OIDCConnectUsePKCE: updatedSettings.OIDCConnectUsePKCE,
OIDCConnectValidateIDToken: updatedSettings.OIDCConnectValidateIDToken,
OIDCConnectAllowedSigningAlgs: updatedSettings.OIDCConnectAllowedSigningAlgs,
OIDCConnectClockSkewSeconds: updatedSettings.OIDCConnectClockSkewSeconds,
OIDCConnectRequireEmailVerified: updatedSettings.OIDCConnectRequireEmailVerified,
OIDCConnectUserInfoEmailPath: updatedSettings.OIDCConnectUserInfoEmailPath,
OIDCConnectUserInfoIDPath: updatedSettings.OIDCConnectUserInfoIDPath,
OIDCConnectUserInfoUsernamePath: updatedSettings.OIDCConnectUserInfoUsernamePath,
GitHubOAuthEnabled: updatedSettings.GitHubOAuthEnabled,
GitHubOAuthClientID: updatedSettings.GitHubOAuthClientID,
GitHubOAuthClientSecretConfigured: updatedSettings.GitHubOAuthClientSecretConfigured,
GitHubOAuthRedirectURL: updatedSettings.GitHubOAuthRedirectURL,
GitHubOAuthFrontendRedirectURL: updatedSettings.GitHubOAuthFrontendRedirectURL,
GoogleOAuthEnabled: updatedSettings.GoogleOAuthEnabled,
GoogleOAuthClientID: updatedSettings.GoogleOAuthClientID,
GoogleOAuthClientSecretConfigured: updatedSettings.GoogleOAuthClientSecretConfigured,
GoogleOAuthRedirectURL: updatedSettings.GoogleOAuthRedirectURL,
GoogleOAuthFrontendRedirectURL: updatedSettings.GoogleOAuthFrontendRedirectURL,
SiteName: updatedSettings.SiteName,
SiteLogo: updatedSettings.SiteLogo,
SiteSubtitle: updatedSettings.SiteSubtitle,
APIBaseURL: updatedSettings.APIBaseURL,
ContactInfo: updatedSettings.ContactInfo,
DocURL: updatedSettings.DocURL,
HomeContent: updatedSettings.HomeContent,
HideCcsImportButton: updatedSettings.HideCcsImportButton,
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
TableDefaultPageSize: updatedSettings.TableDefaultPageSize,
TablePageSizeOptions: updatedSettings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints),
DefaultConcurrency: updatedSettings.DefaultConcurrency,
DefaultBalance: updatedSettings.DefaultBalance,
AffiliateRebateRate: updatedSettings.AffiliateRebateRate,
AffiliateRebateFreezeHours: updatedSettings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: updatedSettings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: updatedSettings.AffiliateRebatePerInviteeCap,
DefaultUserRPMLimit: updatedSettings.DefaultUserRPMLimit,
DefaultSubscriptions: updatedDefaultSubscriptions,
EnableModelFallback: updatedSettings.EnableModelFallback,
FallbackModelAnthropic: updatedSettings.FallbackModelAnthropic,
FallbackModelOpenAI: updatedSettings.FallbackModelOpenAI,
FallbackModelGemini: updatedSettings.FallbackModelGemini,
FallbackModelAntigravity: updatedSettings.FallbackModelAntigravity,
EnableIdentityPatch: updatedSettings.EnableIdentityPatch,
IdentityPatchPrompt: updatedSettings.IdentityPatchPrompt,
OpsMonitoringEnabled: updatedSettings.OpsMonitoringEnabled,
OpsRealtimeMonitoringEnabled: updatedSettings.OpsRealtimeMonitoringEnabled,
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
MaxClaudeCodeVersion: updatedSettings.MaxClaudeCodeVersion,
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
BackendModeEnabled: updatedSettings.BackendModeEnabled,
EnableFingerprintUnification: updatedSettings.EnableFingerprintUnification,
EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough,
EnableCCHSigning: updatedSettings.EnableCCHSigning,
EnableClaudeOAuthSystemPromptInjection: updatedSettings.EnableClaudeOAuthSystemPromptInjection,
ClaudeOAuthSystemPrompt: updatedSettings.ClaudeOAuthSystemPrompt,
ClaudeOAuthSystemPromptBlocks: updatedSettings.ClaudeOAuthSystemPromptBlocks,
EnableAnthropicCacheTTL1hInjection: updatedSettings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: updatedSettings.RewriteMessageCacheControl,
EnableClientDatelineNormalization: updatedSettings.EnableClientDatelineNormalization,
AntigravityUserAgentVersion: updatedSettings.AntigravityUserAgentVersion,
OpenAICodexUserAgent: updatedSettings.OpenAICodexUserAgent,
MinCodexVersion: updatedSettings.MinCodexVersion,
MaxCodexVersion: updatedSettings.MaxCodexVersion,
CodexCLIOnlyBlacklist: updatedSettings.CodexCLIOnlyBlacklist,
CodexCLIOnlyWhitelist: updatedSettings.CodexCLIOnlyWhitelist,
CodexCLIOnlyAllowAppServerClients: updatedSettings.CodexCLIOnlyAllowAppServerClients,
CodexCLIOnlyEngineFingerprintSignals: updatedSettings.CodexCLIOnlyEngineFingerprintSignals,
PaymentVisibleMethodAlipaySource: updatedSettings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: updatedSettings.PaymentVisibleMethodWxpaySource,
PaymentVisibleMethodAlipayEnabled: updatedSettings.PaymentVisibleMethodAlipayEnabled,
PaymentVisibleMethodWxpayEnabled: updatedSettings.PaymentVisibleMethodWxpayEnabled,
OpenAIAdvancedSchedulerEnabled: updatedSettings.OpenAIAdvancedSchedulerEnabled,
BalanceLowNotifyEnabled: updatedSettings.BalanceLowNotifyEnabled,
BalanceLowNotifyThreshold: updatedSettings.BalanceLowNotifyThreshold,
BalanceLowNotifyRechargeURL: updatedSettings.BalanceLowNotifyRechargeURL,
SubscriptionExpiryNotifyEnabled: updatedSettings.SubscriptionExpiryNotifyEnabled,
AccountQuotaNotifyEnabled: updatedSettings.AccountQuotaNotifyEnabled,
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(updatedSettings.AccountQuotaNotifyEmails),
PaymentEnabled: updatedPaymentCfg.Enabled,
PaymentMinAmount: updatedPaymentCfg.MinAmount,
PaymentMaxAmount: updatedPaymentCfg.MaxAmount,
PaymentDailyLimit: updatedPaymentCfg.DailyLimit,
PaymentOrderTimeoutMin: updatedPaymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: updatedPaymentCfg.MaxPendingOrders,
PaymentEnabledTypes: updatedPaymentCfg.EnabledTypes,
PaymentBalanceDisabled: updatedPaymentCfg.BalanceDisabled,
PaymentBalanceRechargeMultiplier: updatedPaymentCfg.BalanceRechargeMultiplier,
PaymentRechargeFeeRate: updatedPaymentCfg.RechargeFeeRate,
PaymentLoadBalanceStrat: updatedPaymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: updatedPaymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: updatedPaymentCfg.ProductNameSuffix,
PaymentHelpImageURL: updatedPaymentCfg.HelpImageURL,
PaymentHelpText: updatedPaymentCfg.HelpText,
PaymentCancelRateLimitEnabled: updatedPaymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: updatedPaymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode,
PaymentAlipayForceQRCode: updatedPaymentCfg.AlipayForceQRCode,
RegistrationEnabled: updatedSettings.RegistrationEnabled,
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
RegistrationEmailSuffixWhitelist: updatedSettings.RegistrationEmailSuffixWhitelist,
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
FrontendURL: updatedSettings.FrontendURL,
InvitationCodeEnabled: updatedSettings.InvitationCodeEnabled,
TotpEnabled: updatedSettings.TotpEnabled,
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
LoginAgreementEnabled: updatedSettings.LoginAgreementEnabled,
LoginAgreementMode: updatedSettings.LoginAgreementMode,
LoginAgreementUpdatedAt: updatedSettings.LoginAgreementUpdatedAt,
LoginAgreementDocuments: loginAgreementDocumentsToDTO(updatedSettings.LoginAgreementDocuments),
SMTPHost: updatedSettings.SMTPHost,
SMTPPort: updatedSettings.SMTPPort,
SMTPUsername: updatedSettings.SMTPUsername,
SMTPPasswordConfigured: updatedSettings.SMTPPasswordConfigured,
SMTPFrom: updatedSettings.SMTPFrom,
SMTPFromName: updatedSettings.SMTPFromName,
SMTPUseTLS: updatedSettings.SMTPUseTLS,
TurnstileEnabled: updatedSettings.TurnstileEnabled,
TurnstileSiteKey: updatedSettings.TurnstileSiteKey,
TurnstileSecretKeyConfigured: updatedSettings.TurnstileSecretKeyConfigured,
APIKeyACLTrustForwardedIP: updatedSettings.APIKeyACLTrustForwardedIP,
LinuxDoConnectEnabled: updatedSettings.LinuxDoConnectEnabled,
LinuxDoConnectClientID: updatedSettings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: updatedSettings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: updatedSettings.LinuxDoConnectRedirectURL,
DingTalkConnectEnabled: updatedSettings.DingTalkConnectEnabled,
DingTalkConnectClientID: updatedSettings.DingTalkConnectClientID,
DingTalkConnectClientSecretConfigured: updatedSettings.DingTalkConnectClientSecretConfigured,
DingTalkConnectRedirectURL: updatedSettings.DingTalkConnectRedirectURL,
DingTalkConnectCorpRestrictionPolicy: updatedSettings.DingTalkConnectCorpRestrictionPolicy,
DingTalkConnectInternalCorpID: updatedSettings.DingTalkConnectInternalCorpID,
DingTalkConnectBypassRegistration: updatedSettings.DingTalkConnectBypassRegistration,
DingTalkConnectSyncCorpEmail: updatedSettings.DingTalkConnectSyncCorpEmail,
DingTalkConnectSyncDisplayName: updatedSettings.DingTalkConnectSyncDisplayName,
DingTalkConnectSyncDept: updatedSettings.DingTalkConnectSyncDept,
DingTalkConnectSyncCorpEmailAttrKey: updatedSettings.DingTalkConnectSyncCorpEmailAttrKey,
DingTalkConnectSyncDisplayNameAttrKey: updatedSettings.DingTalkConnectSyncDisplayNameAttrKey,
DingTalkConnectSyncDeptAttrKey: updatedSettings.DingTalkConnectSyncDeptAttrKey,
DingTalkConnectSyncCorpEmailAttrName: updatedSettings.DingTalkConnectSyncCorpEmailAttrName,
DingTalkConnectSyncDisplayNameAttrName: updatedSettings.DingTalkConnectSyncDisplayNameAttrName,
DingTalkConnectSyncDeptAttrName: updatedSettings.DingTalkConnectSyncDeptAttrName,
WeChatConnectEnabled: updatedSettings.WeChatConnectEnabled,
WeChatConnectAppID: updatedSettings.WeChatConnectAppID,
WeChatConnectAppSecretConfigured: updatedSettings.WeChatConnectAppSecretConfigured,
WeChatConnectOpenAppID: updatedSettings.WeChatConnectOpenAppID,
WeChatConnectOpenAppSecretConfigured: updatedSettings.WeChatConnectOpenAppSecretConfigured,
WeChatConnectMPAppID: updatedSettings.WeChatConnectMPAppID,
WeChatConnectMPAppSecretConfigured: updatedSettings.WeChatConnectMPAppSecretConfigured,
WeChatConnectMobileAppID: updatedSettings.WeChatConnectMobileAppID,
WeChatConnectMobileAppSecretConfigured: updatedSettings.WeChatConnectMobileAppSecretConfigured,
WeChatConnectOpenEnabled: updatedSettings.WeChatConnectOpenEnabled,
WeChatConnectMPEnabled: updatedSettings.WeChatConnectMPEnabled,
WeChatConnectMobileEnabled: updatedSettings.WeChatConnectMobileEnabled,
WeChatConnectMode: updatedSettings.WeChatConnectMode,
WeChatConnectScopes: updatedSettings.WeChatConnectScopes,
WeChatConnectRedirectURL: updatedSettings.WeChatConnectRedirectURL,
WeChatConnectFrontendRedirectURL: updatedSettings.WeChatConnectFrontendRedirectURL,
OIDCConnectEnabled: updatedSettings.OIDCConnectEnabled,
OIDCConnectProviderName: updatedSettings.OIDCConnectProviderName,
OIDCConnectClientID: updatedSettings.OIDCConnectClientID,
OIDCConnectClientSecretConfigured: updatedSettings.OIDCConnectClientSecretConfigured,
OIDCConnectIssuerURL: updatedSettings.OIDCConnectIssuerURL,
OIDCConnectDiscoveryURL: updatedSettings.OIDCConnectDiscoveryURL,
OIDCConnectAuthorizeURL: updatedSettings.OIDCConnectAuthorizeURL,
OIDCConnectTokenURL: updatedSettings.OIDCConnectTokenURL,
OIDCConnectUserInfoURL: updatedSettings.OIDCConnectUserInfoURL,
OIDCConnectJWKSURL: updatedSettings.OIDCConnectJWKSURL,
OIDCConnectScopes: updatedSettings.OIDCConnectScopes,
OIDCConnectRedirectURL: updatedSettings.OIDCConnectRedirectURL,
OIDCConnectFrontendRedirectURL: updatedSettings.OIDCConnectFrontendRedirectURL,
OIDCConnectTokenAuthMethod: updatedSettings.OIDCConnectTokenAuthMethod,
OIDCConnectUsePKCE: updatedSettings.OIDCConnectUsePKCE,
OIDCConnectValidateIDToken: updatedSettings.OIDCConnectValidateIDToken,
OIDCConnectAllowedSigningAlgs: updatedSettings.OIDCConnectAllowedSigningAlgs,
OIDCConnectClockSkewSeconds: updatedSettings.OIDCConnectClockSkewSeconds,
OIDCConnectRequireEmailVerified: updatedSettings.OIDCConnectRequireEmailVerified,
OIDCConnectUserInfoEmailPath: updatedSettings.OIDCConnectUserInfoEmailPath,
OIDCConnectUserInfoIDPath: updatedSettings.OIDCConnectUserInfoIDPath,
OIDCConnectUserInfoUsernamePath: updatedSettings.OIDCConnectUserInfoUsernamePath,
GitHubOAuthEnabled: updatedSettings.GitHubOAuthEnabled,
GitHubOAuthClientID: updatedSettings.GitHubOAuthClientID,
GitHubOAuthClientSecretConfigured: updatedSettings.GitHubOAuthClientSecretConfigured,
GitHubOAuthRedirectURL: updatedSettings.GitHubOAuthRedirectURL,
GitHubOAuthFrontendRedirectURL: updatedSettings.GitHubOAuthFrontendRedirectURL,
GoogleOAuthEnabled: updatedSettings.GoogleOAuthEnabled,
GoogleOAuthClientID: updatedSettings.GoogleOAuthClientID,
GoogleOAuthClientSecretConfigured: updatedSettings.GoogleOAuthClientSecretConfigured,
GoogleOAuthRedirectURL: updatedSettings.GoogleOAuthRedirectURL,
GoogleOAuthFrontendRedirectURL: updatedSettings.GoogleOAuthFrontendRedirectURL,
SiteName: updatedSettings.SiteName,
SiteLogo: updatedSettings.SiteLogo,
SiteSubtitle: updatedSettings.SiteSubtitle,
APIBaseURL: updatedSettings.APIBaseURL,
ContactInfo: updatedSettings.ContactInfo,
DocURL: updatedSettings.DocURL,
HomeContent: updatedSettings.HomeContent,
HideCcsImportButton: updatedSettings.HideCcsImportButton,
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
TableDefaultPageSize: updatedSettings.TableDefaultPageSize,
TablePageSizeOptions: updatedSettings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints),
DefaultConcurrency: updatedSettings.DefaultConcurrency,
DefaultBalance: updatedSettings.DefaultBalance,
AffiliateRebateRate: updatedSettings.AffiliateRebateRate,
AffiliateRebateFreezeHours: updatedSettings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: updatedSettings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: updatedSettings.AffiliateRebatePerInviteeCap,
DefaultUserRPMLimit: updatedSettings.DefaultUserRPMLimit,
DefaultSubscriptions: updatedDefaultSubscriptions,
EnableModelFallback: updatedSettings.EnableModelFallback,
FallbackModelAnthropic: updatedSettings.FallbackModelAnthropic,
FallbackModelOpenAI: updatedSettings.FallbackModelOpenAI,
FallbackModelGemini: updatedSettings.FallbackModelGemini,
FallbackModelAntigravity: updatedSettings.FallbackModelAntigravity,
EnableIdentityPatch: updatedSettings.EnableIdentityPatch,
IdentityPatchPrompt: updatedSettings.IdentityPatchPrompt,
OpsMonitoringEnabled: updatedSettings.OpsMonitoringEnabled,
OpsRealtimeMonitoringEnabled: updatedSettings.OpsRealtimeMonitoringEnabled,
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
MaxClaudeCodeVersion: updatedSettings.MaxClaudeCodeVersion,
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
BackendModeEnabled: updatedSettings.BackendModeEnabled,
EnableFingerprintUnification: updatedSettings.EnableFingerprintUnification,
EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough,
EnableCCHSigning: updatedSettings.EnableCCHSigning,
EnableClaudeOAuthSystemPromptInjection: updatedSettings.EnableClaudeOAuthSystemPromptInjection,
ClaudeOAuthSystemPrompt: updatedSettings.ClaudeOAuthSystemPrompt,
ClaudeOAuthSystemPromptBlocks: updatedSettings.ClaudeOAuthSystemPromptBlocks,
EnableAnthropicCacheTTL1hInjection: updatedSettings.EnableAnthropicCacheTTL1hInjection,
RewriteMessageCacheControl: updatedSettings.RewriteMessageCacheControl,
EnableClientDatelineNormalization: updatedSettings.EnableClientDatelineNormalization,
AntigravityUserAgentVersion: updatedSettings.AntigravityUserAgentVersion,
OpenAICodexUserAgent: updatedSettings.OpenAICodexUserAgent,
MinCodexVersion: updatedSettings.MinCodexVersion,
MaxCodexVersion: updatedSettings.MaxCodexVersion,
CodexCLIOnlyBlacklist: updatedSettings.CodexCLIOnlyBlacklist,
CodexCLIOnlyWhitelist: updatedSettings.CodexCLIOnlyWhitelist,
CodexCLIOnlyAllowAppServerClients: updatedSettings.CodexCLIOnlyAllowAppServerClients,
CodexCLIOnlyEngineFingerprintSignals: updatedSettings.CodexCLIOnlyEngineFingerprintSignals,
PaymentVisibleMethodAlipaySource: updatedSettings.PaymentVisibleMethodAlipaySource,
PaymentVisibleMethodWxpaySource: updatedSettings.PaymentVisibleMethodWxpaySource,
PaymentVisibleMethodAlipayEnabled: updatedSettings.PaymentVisibleMethodAlipayEnabled,
PaymentVisibleMethodWxpayEnabled: updatedSettings.PaymentVisibleMethodWxpayEnabled,
OpenAIAdvancedSchedulerEnabled: updatedSettings.OpenAIAdvancedSchedulerEnabled,
OpenAIAdvancedSchedulerStickyWeightedEnabled: updatedSettings.OpenAIAdvancedSchedulerStickyWeightedEnabled,
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: updatedSettings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled,
OpenAIAdvancedSchedulerLBTopK: updatedSettings.OpenAIAdvancedSchedulerLBTopK,
OpenAIAdvancedSchedulerWeightPriority: updatedSettings.OpenAIAdvancedSchedulerWeightPriority,
OpenAIAdvancedSchedulerWeightLoad: updatedSettings.OpenAIAdvancedSchedulerWeightLoad,
OpenAIAdvancedSchedulerWeightQueue: updatedSettings.OpenAIAdvancedSchedulerWeightQueue,
OpenAIAdvancedSchedulerWeightErrorRate: updatedSettings.OpenAIAdvancedSchedulerWeightErrorRate,
OpenAIAdvancedSchedulerWeightTTFT: updatedSettings.OpenAIAdvancedSchedulerWeightTTFT,
OpenAIAdvancedSchedulerWeightReset: updatedSettings.OpenAIAdvancedSchedulerWeightReset,
OpenAIAdvancedSchedulerWeightQuotaHeadroom: updatedSettings.OpenAIAdvancedSchedulerWeightQuotaHeadroom,
OpenAIAdvancedSchedulerWeightPreviousResponse: updatedSettings.OpenAIAdvancedSchedulerWeightPreviousResponse,
OpenAIAdvancedSchedulerWeightSessionSticky: updatedSettings.OpenAIAdvancedSchedulerWeightSessionSticky,
OpenAIAdvancedSchedulerEffectiveLBTopK: updatedSettings.OpenAIAdvancedSchedulerEffectiveLBTopK,
OpenAIAdvancedSchedulerEffectiveWeightPriority: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightPriority,
OpenAIAdvancedSchedulerEffectiveWeightLoad: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightLoad,
OpenAIAdvancedSchedulerEffectiveWeightQueue: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightQueue,
OpenAIAdvancedSchedulerEffectiveWeightErrorRate: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightErrorRate,
OpenAIAdvancedSchedulerEffectiveWeightTTFT: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightTTFT,
OpenAIAdvancedSchedulerEffectiveWeightReset: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightReset,
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom,
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse,
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky: updatedSettings.OpenAIAdvancedSchedulerEffectiveWeightSessionSticky,
BalanceLowNotifyEnabled: updatedSettings.BalanceLowNotifyEnabled,
BalanceLowNotifyThreshold: updatedSettings.BalanceLowNotifyThreshold,
BalanceLowNotifyRechargeURL: updatedSettings.BalanceLowNotifyRechargeURL,
SubscriptionExpiryNotifyEnabled: updatedSettings.SubscriptionExpiryNotifyEnabled,
AccountQuotaNotifyEnabled: updatedSettings.AccountQuotaNotifyEnabled,
AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(updatedSettings.AccountQuotaNotifyEmails),
PaymentEnabled: updatedPaymentCfg.Enabled,
PaymentMinAmount: updatedPaymentCfg.MinAmount,
PaymentMaxAmount: updatedPaymentCfg.MaxAmount,
PaymentDailyLimit: updatedPaymentCfg.DailyLimit,
PaymentOrderTimeoutMin: updatedPaymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: updatedPaymentCfg.MaxPendingOrders,
PaymentEnabledTypes: updatedPaymentCfg.EnabledTypes,
PaymentBalanceDisabled: updatedPaymentCfg.BalanceDisabled,
PaymentBalanceRechargeMultiplier: updatedPaymentCfg.BalanceRechargeMultiplier,
PaymentSubscriptionUSDToCNYRate: updatedPaymentCfg.SubscriptionUSDToCNYRate,
PaymentRechargeFeeRate: updatedPaymentCfg.RechargeFeeRate,
PaymentLoadBalanceStrat: updatedPaymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: updatedPaymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: updatedPaymentCfg.ProductNameSuffix,
PaymentHelpImageURL: updatedPaymentCfg.HelpImageURL,
PaymentHelpText: updatedPaymentCfg.HelpText,
PaymentCancelRateLimitEnabled: updatedPaymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: updatedPaymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode,
PaymentAlipayForceQRCode: updatedPaymentCfg.AlipayForceQRCode,
ChannelMonitorEnabled: updatedSettings.ChannelMonitorEnabled,
ChannelMonitorDefaultIntervalSeconds: updatedSettings.ChannelMonitorDefaultIntervalSeconds,
@@ -2238,7 +2320,8 @@ func hasPaymentFields(req UpdateSettingsRequest) bool {
req.PaymentMaxAmount != nil || req.PaymentDailyLimit != nil ||
req.PaymentOrderTimeoutMin != nil || req.PaymentMaxPendingOrders != nil ||
req.PaymentEnabledTypes != nil || req.PaymentBalanceDisabled != nil ||
req.PaymentBalanceRechargeMultiplier != nil || req.PaymentRechargeFeeRate != nil ||
req.PaymentBalanceRechargeMultiplier != nil || req.PaymentSubscriptionUSDToCNYRate != nil ||
req.PaymentRechargeFeeRate != nil ||
req.PaymentLoadBalanceStrat != nil || req.PaymentProductNamePrefix != nil ||
req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil ||
req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil ||
@@ -2677,6 +2760,42 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
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")
@@ -3829,3 +3948,10 @@ func equalPlatformQuotaSettings(before, after map[string]*service.DefaultPlatfor
}
return true
}
func stringSetting(value *string, fallback string) string {
if value == nil {
return fallback
}
return *value
}
@@ -217,12 +217,13 @@ func TestSettingHandler_UpdateSettings_PersistsPaymentVisibleMethodsAndAdvancedS
handler := NewSettingHandler(svc, nil, nil, nil, nil, nil, nil)
body := map[string]any{
"promo_code_enabled": true,
"payment_visible_method_alipay_source": "easypay",
"payment_visible_method_wxpay_source": "wxpay",
"payment_visible_method_alipay_enabled": true,
"payment_visible_method_wxpay_enabled": false,
"openai_advanced_scheduler_enabled": true,
"promo_code_enabled": true,
"payment_visible_method_alipay_source": "easypay",
"payment_visible_method_wxpay_source": "wxpay",
"payment_visible_method_alipay_enabled": true,
"payment_visible_method_wxpay_enabled": false,
"openai_advanced_scheduler_enabled": true,
"openai_advanced_scheduler_subscription_priority_enabled": true,
}
rawBody, err := json.Marshal(body)
require.NoError(t, err)
@@ -240,6 +241,7 @@ func TestSettingHandler_UpdateSettings_PersistsPaymentVisibleMethodsAndAdvancedS
require.Equal(t, "true", repo.values[service.SettingPaymentVisibleMethodAlipayEnabled])
require.Equal(t, "false", repo.values[service.SettingPaymentVisibleMethodWxpayEnabled])
require.Equal(t, "true", repo.values["openai_advanced_scheduler_enabled"])
require.Equal(t, "true", repo.values[service.SettingKeyOpenAIAdvancedSchedulerSubscriptionPriorityEnabled])
var resp response.Response
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
@@ -250,6 +252,7 @@ func TestSettingHandler_UpdateSettings_PersistsPaymentVisibleMethodsAndAdvancedS
require.Equal(t, true, data["payment_visible_method_alipay_enabled"])
require.Equal(t, false, data["payment_visible_method_wxpay_enabled"])
require.Equal(t, true, data["openai_advanced_scheduler_enabled"])
require.Equal(t, true, data["openai_advanced_scheduler_subscription_priority_enabled"])
}
func TestSettingHandler_UpdateSettings_PreservesLegacyBlankPaymentVisibleMethodSource(t *testing.T) {
@@ -0,0 +1,257 @@
package handler
import (
"errors"
"io"
"net/http"
"strconv"
"strings"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
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.service.MarkDownloaded(c.Request.Context(), owner, c.Param("id"))
}
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.service.MarkDownloaded(c.Request.Context(), owner, c.Param("id"))
}
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,
},
})
}
@@ -11,18 +11,20 @@ import (
func TestAPIKeyFromService_MapsLastUsedAt(t *testing.T) {
lastUsed := time.Now().UTC().Truncate(time.Second)
src := &service.APIKey{
ID: 1,
UserID: 2,
Key: "sk-map-last-used",
Name: "Mapper",
Status: service.StatusActive,
LastUsedAt: &lastUsed,
ID: 1,
UserID: 2,
Key: "sk-map-last-used",
Name: "Mapper",
Status: service.StatusActive,
LastUsedAt: &lastUsed,
CurrentConcurrency: 3,
}
out := APIKeyFromService(src)
require.NotNil(t, out)
require.NotNil(t, out.LastUsedAt)
require.WithinDuration(t, lastUsed, *out.LastUsedAt, time.Second)
require.Equal(t, 3, out.CurrentConcurrency)
}
func TestAPIKeyFromService_MapsNilLastUsedAt(t *testing.T) {
+30 -25
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,
@@ -79,31 +80,32 @@ func APIKeyFromService(k *service.APIKey) *APIKey {
return nil
}
out := &APIKey{
ID: k.ID,
UserID: k.UserID,
Key: k.Key,
Name: k.Name,
GroupID: k.GroupID,
Status: k.Status,
IPWhitelist: k.IPWhitelist,
IPBlacklist: k.IPBlacklist,
LastUsedAt: k.LastUsedAt,
Quota: k.Quota,
QuotaUsed: k.QuotaUsed,
ExpiresAt: k.ExpiresAt,
CreatedAt: k.CreatedAt,
UpdatedAt: k.UpdatedAt,
RateLimit5h: k.RateLimit5h,
RateLimit1d: k.RateLimit1d,
RateLimit7d: k.RateLimit7d,
Usage5h: k.EffectiveUsage5h(),
Usage1d: k.EffectiveUsage1d(),
Usage7d: k.EffectiveUsage7d(),
Window5hStart: k.Window5hStart,
Window1dStart: k.Window1dStart,
Window7dStart: k.Window7dStart,
User: UserFromServiceShallow(k.User),
Group: GroupFromServiceShallow(k.Group),
ID: k.ID,
UserID: k.UserID,
Key: k.Key,
Name: k.Name,
GroupID: k.GroupID,
Status: k.Status,
IPWhitelist: k.IPWhitelist,
IPBlacklist: k.IPBlacklist,
LastUsedAt: k.LastUsedAt,
Quota: k.Quota,
QuotaUsed: k.QuotaUsed,
ExpiresAt: k.ExpiresAt,
CreatedAt: k.CreatedAt,
UpdatedAt: k.UpdatedAt,
CurrentConcurrency: k.CurrentConcurrency,
RateLimit5h: k.RateLimit5h,
RateLimit1d: k.RateLimit1d,
RateLimit7d: k.RateLimit7d,
Usage5h: k.EffectiveUsage5h(),
Usage1d: k.EffectiveUsage1d(),
Usage7d: k.EffectiveUsage7d(),
Window5hStart: k.Window5hStart,
Window1dStart: k.Window1dStart,
Window7dStart: k.Window7dStart,
User: UserFromServiceShallow(k.User),
Group: GroupFromServiceShallow(k.Group),
}
if k.Window5hStart != nil && !service.IsWindowExpired(k.Window5hStart, service.RateLimitWindow5h) {
t := k.Window5hStart.Add(service.RateLimitWindow5h)
@@ -179,8 +181,11 @@ 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,
PeakRateEnabled: g.PeakRateEnabled,
PeakStart: g.PeakStart,
PeakEnd: g.PeakEnd,
+24 -1
View File
@@ -208,7 +208,29 @@ type SystemSettings struct {
PaymentVisibleMethodWxpayEnabled bool `json:"payment_visible_method_wxpay_enabled"`
// OpenAI account scheduling
OpenAIAdvancedSchedulerEnabled bool `json:"openai_advanced_scheduler_enabled"`
OpenAIAdvancedSchedulerEnabled bool `json:"openai_advanced_scheduler_enabled"`
OpenAIAdvancedSchedulerStickyWeightedEnabled bool `json:"openai_advanced_scheduler_sticky_weighted_enabled"`
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled bool `json:"openai_advanced_scheduler_subscription_priority_enabled"`
OpenAIAdvancedSchedulerLBTopK string `json:"openai_advanced_scheduler_lb_top_k"`
OpenAIAdvancedSchedulerWeightPriority string `json:"openai_advanced_scheduler_weight_priority"`
OpenAIAdvancedSchedulerWeightLoad string `json:"openai_advanced_scheduler_weight_load"`
OpenAIAdvancedSchedulerWeightQueue string `json:"openai_advanced_scheduler_weight_queue"`
OpenAIAdvancedSchedulerWeightErrorRate string `json:"openai_advanced_scheduler_weight_error_rate"`
OpenAIAdvancedSchedulerWeightTTFT string `json:"openai_advanced_scheduler_weight_ttft"`
OpenAIAdvancedSchedulerWeightReset string `json:"openai_advanced_scheduler_weight_reset"`
OpenAIAdvancedSchedulerWeightQuotaHeadroom string `json:"openai_advanced_scheduler_weight_quota_headroom"`
OpenAIAdvancedSchedulerWeightPreviousResponse string `json:"openai_advanced_scheduler_weight_previous_response"`
OpenAIAdvancedSchedulerWeightSessionSticky string `json:"openai_advanced_scheduler_weight_session_sticky"`
OpenAIAdvancedSchedulerEffectiveLBTopK string `json:"openai_advanced_scheduler_effective_lb_top_k"`
OpenAIAdvancedSchedulerEffectiveWeightPriority string `json:"openai_advanced_scheduler_effective_weight_priority"`
OpenAIAdvancedSchedulerEffectiveWeightLoad string `json:"openai_advanced_scheduler_effective_weight_load"`
OpenAIAdvancedSchedulerEffectiveWeightQueue string `json:"openai_advanced_scheduler_effective_weight_queue"`
OpenAIAdvancedSchedulerEffectiveWeightErrorRate string `json:"openai_advanced_scheduler_effective_weight_error_rate"`
OpenAIAdvancedSchedulerEffectiveWeightTTFT string `json:"openai_advanced_scheduler_effective_weight_ttft"`
OpenAIAdvancedSchedulerEffectiveWeightReset string `json:"openai_advanced_scheduler_effective_weight_reset"`
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom string `json:"openai_advanced_scheduler_effective_weight_quota_headroom"`
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse string `json:"openai_advanced_scheduler_effective_weight_previous_response"`
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky string `json:"openai_advanced_scheduler_effective_weight_session_sticky"`
// Payment configuration
PaymentEnabled bool `json:"payment_enabled"`
@@ -220,6 +242,7 @@ type SystemSettings struct {
PaymentEnabledTypes []string `json:"payment_enabled_types"`
PaymentBalanceDisabled bool `json:"payment_balance_disabled"`
PaymentBalanceRechargeMultiplier float64 `json:"payment_balance_recharge_multiplier"`
PaymentSubscriptionUSDToCNYRate float64 `json:"payment_subscription_usd_to_cny_rate"`
PaymentRechargeFeeRate float64 `json:"payment_recharge_fee_rate"`
PaymentLoadBalanceStrat string `json:"payment_load_balance_strategy"`
PaymentProductNamePrefix string `json:"payment_product_name_prefix"`
+9 -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"`
@@ -63,6 +64,8 @@ type APIKey struct {
ExpiresAt *time.Time `json:"expires_at"` // Expiration time (nil = never expires)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// CurrentConcurrency is the real-time active request count for this API key.
CurrentConcurrency int `json:"current_concurrency"`
// Rate limit fields
RateLimit5h float64 `json:"rate_limit_5h"`
@@ -97,9 +100,12 @@ 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"`
// 高峰时段倍率配置
PeakRateEnabled bool `json:"peak_rate_enabled"`
PeakStart string `json:"peak_start"`
+109 -7
View File
@@ -19,6 +19,7 @@ const (
EndpointChatCompletions = "/v1/chat/completions"
EndpointEmbeddings = "/v1/embeddings"
EndpointResponses = "/v1/responses"
EndpointResponsesCompact = "/v1/responses/compact"
EndpointImagesGenerations = "/v1/images/generations"
EndpointImagesEdits = "/v1/images/edits"
EndpointVideosGenerations = "/v1/videos/generations"
@@ -42,6 +43,33 @@ const (
// "/v1/chat/completions" → "/v1/chat/completions"
// "/openai/v1/responses/foo" → "/v1/responses"
// "/v1beta/models/gemini:gen" → "/v1beta/models"
//
// The OpenAI Responses API is also exposed via a few bare/alias
// routes that do not carry a "/v1/" prefix (top-level bare route and
// the Codex direct route). "/responses/compact" (and "/backend-api/
// codex/responses/compact") is a distinct client endpoint — the
// "compact" client — and is normalized to its OWN canonical inbound
// endpoint, EndpointResponsesCompact, rather than being folded into
// the root Responses endpoint. Any other subpath under the bare/alias
// roots (i.e. not "compact" itself or nested under it) remains a
// subresource suffix of the root Responses endpoint:
//
// "/v1/responses/compact" → EndpointResponsesCompact
// "/v1/responses/compact/detail" → EndpointResponsesCompact
// "/openai/v1/responses/compact" → EndpointResponsesCompact
// "/openai/v1/responses/compact/detail" → EndpointResponsesCompact
// "/responses/compact" → EndpointResponsesCompact
// "/responses/compact/detail" → EndpointResponsesCompact
// "/backend-api/codex/responses/compact" → EndpointResponsesCompact
// "/backend-api/codex/responses/compact/detail" → EndpointResponsesCompact
// "/v1/responses" → EndpointResponses
// "/openai/v1/responses" → EndpointResponses
// "/responses" → EndpointResponses
// "/backend-api/codex/responses" → EndpointResponses
//
// The compact check MUST be evaluated before the root Responses check,
// otherwise "/v1/responses" (a prefix of "/v1/responses/compact")
// would erroneously match first.
func NormalizeInboundEndpoint(path string) string {
path = strings.TrimSpace(path)
switch {
@@ -59,7 +87,9 @@ func NormalizeInboundEndpoint(path string) string {
return EndpointVideosGenerations
case strings.Contains(path, EndpointVideos) || strings.Contains(path, "/videos/"):
return EndpointVideos
case strings.Contains(path, EndpointResponses):
case strings.Contains(path, EndpointResponsesCompact) || isResponsesCompactAliasPath(path):
return EndpointResponsesCompact
case strings.Contains(path, EndpointResponses) || isResponsesRootAliasPath(path):
return EndpointResponses
case strings.Contains(path, EndpointGeminiModels):
return EndpointGeminiModels
@@ -68,6 +98,59 @@ func NormalizeInboundEndpoint(path string) string {
}
}
// isResponsesCompactAliasPath reports whether path is the bare/alias
// "compact" client endpoint — i.e. it is rooted at "/responses/compact"
// or "/backend-api/codex/responses/compact" (bare routes that serve
// the OpenAI Responses API "compact" client without a "/v1/" prefix),
// or any subpath nested under either of those roots:
//
// - "/responses/compact" (bare route, compact client)
// - "/responses/compact/*subpath" (nested, e.g. "/responses/compact/detail")
// - "/backend-api/codex/responses/compact" (Codex direct route, compact client)
// - "/backend-api/codex/responses/compact/*subpath" (nested, e.g.
// "/backend-api/codex/responses/compact/detail")
//
// This MUST be checked before isResponsesRootAliasPath, since
// "/responses" is a prefix of "/responses/compact".
func isResponsesCompactAliasPath(path string) bool {
trimmed := strings.TrimRight(strings.TrimSpace(path), "/")
if trimmed == "" {
return false
}
return isBareOrSubpathOf(trimmed, "/responses/compact") || isBareOrSubpathOf(trimmed, "/backend-api/codex/responses/compact")
}
// isResponsesRootAliasPath reports whether path is one of the bare/alias
// routes that serve the root OpenAI Responses API without a "/v1/"
// prefix, or any non-"compact" subpath registered under them:
//
// - "/responses" (top-level bare route)
// - "/responses/*subpath" (any subpath other than "compact",
// since "compact" is its own distinct inbound endpoint)
// - "/backend-api/codex/responses" (Codex direct route)
// - "/backend-api/codex/responses/*subpath" (any subpath other than
// "compact")
//
// Only the top-level bare route and the Codex direct route (and their
// subpaths) are recognized here — this deliberately does NOT generalize
// to any path merely ending in "/responses" (e.g. an unrelated
// "/foo/responses" must not match).
func isResponsesRootAliasPath(path string) bool {
trimmed := strings.TrimRight(strings.TrimSpace(path), "/")
if trimmed == "" {
return false
}
return isBareOrSubpathOf(trimmed, "/responses") || isBareOrSubpathOf(trimmed, "/backend-api/codex/responses")
}
// isBareOrSubpathOf reports whether path is exactly root, or a subpath
// rooted at root (i.e. root followed by "/"). This anchors the match
// at the start of path so it cannot match paths where root appears
// nested under some other unrelated prefix.
func isBareOrSubpathOf(path, root string) bool {
return path == root || strings.HasPrefix(path, root+"/")
}
// DeriveUpstreamEndpoint determines the upstream endpoint from the
// account platform and the normalized inbound endpoint.
//
@@ -88,10 +171,20 @@ func DeriveUpstreamEndpoint(inbound, rawRequestPath, platform string) string {
return inbound
}
// OpenAI forwards everything to the Responses API.
// Preserve subresource suffix (e.g. /v1/responses/compact).
// Preserve subresource suffix (e.g. /v1/responses/compact,
// /v1/responses/compact/detail) as derived from the raw path.
if suffix := responsesSubpathSuffix(rawRequestPath); suffix != "" {
return EndpointResponses + suffix
}
// The raw path carried no derivable suffix (e.g. it was already
// normalized upstream, or the caller only has the canonical
// inbound endpoint available) — fall back to the canonical
// compact endpoint when that's what the inbound request was
// recognized as, so it isn't silently treated as the root
// Responses endpoint.
if inbound == EndpointResponsesCompact {
return EndpointResponsesCompact
}
return EndpointResponses
case service.PlatformAnthropic:
@@ -142,10 +235,13 @@ func responsesSubpathSuffix(rawPath string) string {
// Apply this middleware to all gateway route groups.
func InboundEndpointMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
path := c.FullPath()
if path == "" && c.Request != nil && c.Request.URL != nil {
path := ""
if c.Request != nil && c.Request.URL != nil {
path = c.Request.URL.Path
}
if path == "" {
path = c.FullPath()
}
c.Set(ctxKeyInboundEndpoint, NormalizeInboundEndpoint(path))
c.Next()
}
@@ -158,7 +254,11 @@ func InboundEndpointMiddleware() gin.HandlerFunc {
// GetInboundEndpoint returns the canonical inbound endpoint stored by
// InboundEndpointMiddleware. If the middleware did not run (e.g. in
// tests), it falls back to normalizing c.FullPath() on the fly.
// tests), it falls back to normalizing c.Request.URL.Path on the fly
// (preferring the raw request path over c.FullPath(), which collapses
// wildcard route patterns such as "/v1/responses/*subpath" and would
// otherwise mis-normalize concrete requests like "/v1/responses/compact"
// to the root Responses endpoint).
func GetInboundEndpoint(c *gin.Context) string {
if v, ok := c.Get(ctxKeyInboundEndpoint); ok {
if s, ok := v.(string); ok && s != "" {
@@ -168,10 +268,12 @@ func GetInboundEndpoint(c *gin.Context) string {
// Fallback: normalize on the fly.
path := ""
if c != nil {
path = c.FullPath()
if path == "" && c.Request != nil && c.Request.URL != nil {
if c.Request != nil && c.Request.URL != nil {
path = c.Request.URL.Path
}
if path == "" {
path = c.FullPath()
}
}
return NormalizeInboundEndpoint(path)
}
+178 -8
View File
@@ -26,23 +26,42 @@ func TestNormalizeInboundEndpoint(t *testing.T) {
{"/v1/chat/completions", EndpointChatCompletions},
{"/v1/embeddings", EndpointEmbeddings},
{"/v1/responses", EndpointResponses},
{"/v1/responses/compact", EndpointResponsesCompact},
{"/v1/responses/compact/detail", EndpointResponsesCompact},
{"/v1/images/generations", EndpointImagesGenerations},
{"/v1/images/edits", EndpointImagesEdits},
{"/v1/videos/generations", EndpointVideosGenerations},
{"/v1/videos/req_123", EndpointVideos},
{"/v1beta/models", EndpointGeminiModels},
// Prefixed paths (antigravity, openai).
// Prefixed paths (antigravity, openai) — root Responses.
{"/antigravity/v1/messages", EndpointMessages},
{"/openai/v1/responses", EndpointResponses},
{"/openai/v1/responses/compact", EndpointResponses},
{"/openai/v1/images/generations", EndpointImagesGenerations},
{"/openai/v1/images/edits", EndpointImagesEdits},
{"/antigravity/v1beta/models/gemini:generateContent", EndpointGeminiModels},
// Gin route patterns with wildcards.
{"/v1beta/models/*modelAction", EndpointGeminiModels},
{"/v1/responses/*subpath", EndpointResponses},
// Prefixed paths — "/responses/compact" is its OWN distinct
// inbound endpoint, not folded into the root Responses endpoint.
{"/openai/v1/responses/compact", EndpointResponsesCompact},
{"/openai/v1/responses/compact/detail", EndpointResponsesCompact},
// Bare top-level alias route "/responses" — root vs. compact.
{"/responses", EndpointResponses},
{"/responses/compact", EndpointResponsesCompact},
{"/responses/compact/detail", EndpointResponsesCompact},
// 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},
// Must NOT generalize to arbitrary paths merely ending in
// "/responses" (or "/responses/compact") that are unrelated to
// the two known bare alias roots, unless they already carry a
// supported "/v1/responses..." prefix form.
{"/foo/responses", "/foo/responses"},
{"/foo/responses/compact", "/foo/responses/compact"},
// Unknown path is returned as-is.
{"/v1/embeddings", "/v1/embeddings"},
@@ -74,10 +93,29 @@ func TestDeriveUpstreamEndpoint(t *testing.T) {
// Gemini.
{"gemini models", EndpointGeminiModels, "/v1beta/models/gemini:gen", service.PlatformGemini, EndpointGeminiModels},
// OpenAI — always /v1/responses.
// OpenAI — root Responses.
{"openai responses root", EndpointResponses, "/v1/responses", service.PlatformOpenAI, EndpointResponses},
{"openai responses compact", EndpointResponses, "/openai/v1/responses/compact", service.PlatformOpenAI, "/v1/responses/compact"},
{"openai responses nested", EndpointResponses, "/openai/v1/responses/compact/detail", service.PlatformOpenAI, "/v1/responses/compact/detail"},
// OpenAI — compact, raw path carries the derivable "/compact"
// (or nested) suffix, which must be preserved on the upstream
// endpoint.
{"openai responses compact", EndpointResponsesCompact, "/openai/v1/responses/compact", service.PlatformOpenAI, "/v1/responses/compact"},
{"openai responses nested", EndpointResponsesCompact, "/openai/v1/responses/compact/detail", service.PlatformOpenAI, "/v1/responses/compact/detail"},
{"openai bare responses compact", EndpointResponsesCompact, "/responses/compact", service.PlatformOpenAI, "/v1/responses/compact"},
{"openai bare responses compact detail", EndpointResponsesCompact, "/responses/compact/detail", service.PlatformOpenAI, "/v1/responses/compact/detail"},
{"openai codex direct responses compact", EndpointResponsesCompact, "/backend-api/codex/responses/compact", service.PlatformOpenAI, "/v1/responses/compact"},
{"openai codex direct responses compact detail", EndpointResponsesCompact, "/backend-api/codex/responses/compact/detail", service.PlatformOpenAI, "/v1/responses/compact/detail"},
// OpenAI — bare root alias routes normalize to root Responses.
{"openai bare responses", EndpointResponses, "/responses", service.PlatformOpenAI, EndpointResponses},
{"openai codex direct responses", EndpointResponses, "/backend-api/codex/responses", service.PlatformOpenAI, EndpointResponses},
// OpenAI — inbound is already the canonical compact endpoint but
// the raw path carries no derivable "/responses..." suffix (e.g.
// it was already normalized upstream). Must not silently fall
// back to the root Responses endpoint.
{"openai responses compact inbound only, unrelated raw path", EndpointResponsesCompact, "/v1/messages", service.PlatformOpenAI, EndpointResponsesCompact},
{"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},
@@ -113,6 +151,12 @@ func TestResponsesSubpathSuffix(t *testing.T) {
{"/v1/responses/", ""},
{"/v1/responses/compact", "/compact"},
{"/openai/v1/responses/compact/detail", "/compact/detail"},
{"/responses", ""},
{"/responses/compact", "/compact"},
{"/responses/compact/detail", "/compact/detail"},
{"/backend-api/codex/responses", ""},
{"/backend-api/codex/responses/compact", "/compact"},
{"/backend-api/codex/responses/compact/detail", "/compact/detail"},
{"/v1/messages", ""},
{"", ""},
}
@@ -154,6 +198,132 @@ func TestGetInboundEndpoint_FallbackWithoutMiddleware(t *testing.T) {
require.Equal(t, EndpointMessages, got)
}
// TestInboundEndpointMiddleware_WildcardRoutes verifies that, when a
// gateway route is registered with a Gin wildcard pattern (e.g.
// "/v1/responses/*subpath"), InboundEndpointMiddleware normalizes based
// on the concrete request path (c.Request.URL.Path) rather than the
// route pattern (c.FullPath()). Using c.FullPath() here would collapse
// every request under the wildcard — including "/v1/responses/compact"
// — down to the literal pattern string, which never matches the
// "compact" alias detection and would incorrectly normalize to the root
// Responses endpoint.
func TestInboundEndpointMiddleware_WildcardRoutes(t *testing.T) {
tests := []struct {
name string
routePath string
requestPath string
want string
}{
{
name: "v1 responses wildcard route, compact request",
routePath: "/v1/responses/*subpath",
requestPath: "/v1/responses/compact",
want: EndpointResponsesCompact,
},
{
name: "bare responses wildcard route, compact request",
routePath: "/responses/*subpath",
requestPath: "/responses/compact",
want: EndpointResponsesCompact,
},
{
name: "codex direct wildcard route, compact request",
routePath: "/backend-api/codex/responses/*subpath",
requestPath: "/backend-api/codex/responses/compact",
want: EndpointResponsesCompact,
},
{
name: "v1 responses wildcard route, non-compact subpath request",
routePath: "/v1/responses/*subpath",
requestPath: "/v1/responses/foo",
want: EndpointResponses,
},
{
name: "bare responses wildcard route, non-compact subpath request",
routePath: "/responses/*subpath",
requestPath: "/responses/foo",
want: EndpointResponses,
},
{
name: "codex direct wildcard route, non-compact subpath request",
routePath: "/backend-api/codex/responses/*subpath",
requestPath: "/backend-api/codex/responses/foo",
want: EndpointResponses,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := gin.New()
router.Use(InboundEndpointMiddleware())
var captured string
router.POST(tt.routePath, func(c *gin.Context) {
captured = GetInboundEndpoint(c)
c.Status(http.StatusOK)
})
req := httptest.NewRequest(http.MethodPost, tt.requestPath, nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, tt.want, captured)
})
}
}
// TestInboundEndpointMiddleware_GeminiWildcardRoute verifies that a Gemini
// wildcard route (e.g. "/v1beta/models/*modelAction", used to capture the
// ":generateContent"-style action suffix embedded in the path) is normalized
// to EndpointGeminiModels via InboundEndpointMiddleware, using the same real
// Gin routing path as TestInboundEndpointMiddleware_WildcardRoutes above.
func TestInboundEndpointMiddleware_GeminiWildcardRoute(t *testing.T) {
router := gin.New()
router.Use(InboundEndpointMiddleware())
var captured string
router.POST("/v1beta/models/*modelAction", func(c *gin.Context) {
captured = GetInboundEndpoint(c)
c.Status(http.StatusOK)
})
req := httptest.NewRequest(http.MethodPost, "/v1beta/models/gemini-2.5-pro:generateContent", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, EndpointGeminiModels, captured)
}
// TestGetInboundEndpoint_FallbackWildcardRouteWithoutMiddleware verifies
// that when InboundEndpointMiddleware did NOT run (so no value is stored
// in gin.Context), the GetInboundEndpoint fallback path still prefers
// c.Request.URL.Path over c.FullPath(). This guards against the fallback
// regressing to prefer c.FullPath() again, which would misnormalize
// concrete requests matched by a wildcard route pattern (e.g.
// "/v1/responses/*subpath" matching "/v1/responses/compact") down to
// the root Responses endpoint.
func TestGetInboundEndpoint_FallbackWildcardRouteWithoutMiddleware(t *testing.T) {
router := gin.New()
// Deliberately do NOT register InboundEndpointMiddleware.
var captured string
router.POST("/v1/responses/*subpath", func(c *gin.Context) {
// Sanity check: FullPath returns the route pattern, not the
// concrete request path, when a wildcard route matches.
require.Equal(t, "/v1/responses/*subpath", c.FullPath())
captured = GetInboundEndpoint(c)
c.Status(http.StatusOK)
})
req := httptest.NewRequest(http.MethodPost, "/v1/responses/compact", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, EndpointResponsesCompact, captured)
}
func TestGetUpstreamEndpoint_FullFlow(t *testing.T) {
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
+39 -1
View File
@@ -158,6 +158,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
}
@@ -1006,7 +1007,8 @@ func (h *GatewayHandler) Models(c *gin.Context) {
// Get available models from account configurations for the selected group platform.
availableModels := h.gatewayService.GetAvailableModels(c.Request.Context(), groupID, platform)
if apiKey != nil && apiKey.Group != nil && apiKey.Group.CustomModelsListEnabled() {
availableModels = filterModelsByCustomList(availableModels, defaultModelIDsForPlatform(platform), apiKey.Group.ModelsListConfig.Models)
fallbackModels := defaultModelIDsForPlatform(platform)
availableModels = filterModelsByCustomList(customModelsListSource(platform, availableModels, fallbackModels), fallbackModels, apiKey.Group.ModelsListConfig.Models)
writeCustomModelsList(c, platform, availableModels)
return
}
@@ -1090,6 +1092,13 @@ func writeOpenAIModelsList(c *gin.Context, modelIDs []string) {
})
}
func customModelsListSource(platform string, availableModels, fallbackModels []string) []string {
if platform == service.PlatformAnthropic && len(availableModels) > 0 {
return mergeModelIDs(availableModels, fallbackModels)
}
return availableModels
}
func filterModelsByCustomList(availableModels, fallbackModels, selectedModels []string) []string {
if len(selectedModels) == 0 {
return availableModels
@@ -1158,6 +1167,15 @@ func defaultModelIDsForPlatform(platform string) []string {
ids = append(ids, model.ID)
}
return ids
case service.PlatformAnthropic:
ids := make([]string, 0, len(claude.DefaultModels)+len(antigravity.DefaultModels()))
for _, model := range claude.DefaultModels {
ids = append(ids, model.ID)
}
for _, model := range antigravity.DefaultModels() {
ids = append(ids, model.ID)
}
return mergeModelIDs(ids, nil)
case service.PlatformGrok:
return xai.DefaultModelIDs()
default:
@@ -1169,6 +1187,25 @@ func defaultModelIDsForPlatform(platform string) []string {
}
}
func mergeModelIDs(primary, secondary []string) []string {
seen := make(map[string]struct{}, len(primary)+len(secondary))
merged := make([]string, 0, len(primary)+len(secondary))
for _, models := range [][]string{primary, secondary} {
for _, model := range models {
model = strings.TrimSpace(model)
if model == "" {
continue
}
if _, ok := seen[model]; ok {
continue
}
seen[model] = struct{}{}
merged = append(merged, model)
}
}
return merged
}
// AntigravityModels 返回 Antigravity 支持的全部模型
// GET /antigravity/models
func (h *GatewayHandler) AntigravityModels(c *gin.Context) {
@@ -1760,6 +1797,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
}
@@ -64,6 +64,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
}
@@ -64,6 +64,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
}
+41 -2
View File
@@ -10,6 +10,7 @@ import (
"sync"
"time"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
@@ -211,6 +212,14 @@ func (h *ConcurrencyHelper) TryAcquireUserSlot(ctx context.Context, userID int64
return result.ReleaseFunc, true, nil
}
func (h *ConcurrencyHelper) TryAcquireUserSlotForAPIKey(ctx context.Context, userID int64, maxConcurrency int, apiKeyID int64) (func(), bool, error) {
releaseFunc, acquired, err := h.TryAcquireUserSlot(ctx, userID, maxConcurrency)
if err != nil || !acquired {
return releaseFunc, acquired, err
}
return h.withAPIKeySlot(ctx, apiKeyID, releaseFunc), true, nil
}
// TryAcquireAccountSlot 尝试立即获取账号并发槽位。
// 返回值: (releaseFunc, acquired, error)
func (h *ConcurrencyHelper) TryAcquireAccountSlot(ctx context.Context, accountID int64, maxConcurrency int) (func(), bool, error) {
@@ -241,7 +250,7 @@ func (h *ConcurrencyHelper) acquireUserSlotWithWaitTimeout(c *gin.Context, userI
}
if acquired {
return releaseFunc, nil
return h.withAPIKeySlotFromGin(c, releaseFunc), nil
}
queueLimit := service.CalculateMaxWait(maxConcurrency) - maxConcurrency
@@ -258,7 +267,37 @@ func (h *ConcurrencyHelper) acquireUserSlotWithWaitTimeout(c *gin.Context, userI
defer h.DecrementWaitCount(ctx, userID)
// Need to wait - handle streaming ping if needed
return h.waitForSlotWithPingTimeout(c, "user", userID, maxConcurrency, timeout, isStream, streamStarted, false)
releaseFunc, err = h.waitForSlotWithPingTimeout(c, "user", userID, maxConcurrency, timeout, isStream, streamStarted, false)
if err != nil {
return nil, err
}
return h.withAPIKeySlotFromGin(c, releaseFunc), nil
}
func (h *ConcurrencyHelper) withAPIKeySlotFromGin(c *gin.Context, releaseFunc func()) func() {
if c == nil {
return releaseFunc
}
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok || apiKey == nil {
return releaseFunc
}
return h.withAPIKeySlot(c.Request.Context(), apiKey.ID, releaseFunc)
}
func (h *ConcurrencyHelper) withAPIKeySlot(ctx context.Context, apiKeyID int64, releaseFunc func()) func() {
if h == nil || h.concurrencyService == nil || apiKeyID <= 0 {
return releaseFunc
}
apiKeyReleaseFunc := h.concurrencyService.TrackAPIKeySlot(ctx, apiKeyID)
return func() {
if releaseFunc != nil {
releaseFunc()
}
if apiKeyReleaseFunc != nil {
apiKeyReleaseFunc()
}
}
}
// AcquireAccountSlotWithWait acquires an account concurrency slot, waiting if necessary.
@@ -9,6 +9,7 @@ import (
"testing"
"time"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
@@ -29,6 +30,9 @@ type helperConcurrencyCacheStub struct {
waitDecrementCalls int
waitMaxWait int
waitIncrementHook func()
apiKeyTrackCalls int
apiKeyReleaseCalls int
apiKeyTrackIDs []int64
}
func (s *helperConcurrencyCacheStub) AcquireAccountSlot(ctx context.Context, accountID int64, maxConcurrency int, requestID string) (bool, error) {
@@ -97,6 +101,29 @@ func (s *helperConcurrencyCacheStub) GetUserConcurrency(ctx context.Context, use
return 0, nil
}
func (s *helperConcurrencyCacheStub) TrackAPIKeySlot(ctx context.Context, apiKeyID int64, requestID string) error {
s.mu.Lock()
defer s.mu.Unlock()
s.apiKeyTrackCalls++
s.apiKeyTrackIDs = append(s.apiKeyTrackIDs, apiKeyID)
return nil
}
func (s *helperConcurrencyCacheStub) ReleaseAPIKeySlot(ctx context.Context, apiKeyID int64, requestID string) error {
s.mu.Lock()
defer s.mu.Unlock()
s.apiKeyReleaseCalls++
return nil
}
func (s *helperConcurrencyCacheStub) GetAPIKeyConcurrencyBatch(ctx context.Context, apiKeyIDs []int64) (map[int64]int, error) {
out := make(map[int64]int, len(apiKeyIDs))
for _, apiKeyID := range apiKeyIDs {
out[apiKeyID] = 0
}
return out, nil
}
func (s *helperConcurrencyCacheStub) IncrementWaitCount(ctx context.Context, userID int64, maxWait int) (bool, error) {
s.mu.Lock()
s.waitIncrementCalls++
@@ -270,6 +297,48 @@ func TestAcquireUserSlotWithWait_ImmediateAcquireSkipsWaitQueue(t *testing.T) {
require.Equal(t, 1, cache.userReleaseCalls)
}
func TestAcquireUserSlotWithWait_TracksAPIKeySlot(t *testing.T) {
cache := &helperConcurrencyCacheStub{
userSeq: []bool{true},
}
concurrency := service.NewConcurrencyService(cache)
helper := NewConcurrencyHelper(concurrency, SSEPingFormatNone, 5*time.Millisecond)
c, _ := newHelperTestContext(http.MethodPost, "/v1/messages")
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{ID: 77})
streamStarted := false
release, err := helper.acquireUserSlotWithWaitTimeout(c, 202, 3, time.Second, false, &streamStarted)
require.NoError(t, err)
require.NotNil(t, release)
require.Equal(t, 1, cache.apiKeyTrackCalls)
require.Equal(t, []int64{77}, cache.apiKeyTrackIDs)
release()
require.Equal(t, 1, cache.userReleaseCalls)
require.Equal(t, 1, cache.apiKeyReleaseCalls)
}
func TestTryAcquireUserSlotForAPIKey_TracksAPIKeySlot(t *testing.T) {
cache := &helperConcurrencyCacheStub{
userSeq: []bool{true},
}
concurrency := service.NewConcurrencyService(cache)
helper := NewConcurrencyHelper(concurrency, SSEPingFormatNone, 5*time.Millisecond)
release, acquired, err := helper.TryAcquireUserSlotForAPIKey(context.Background(), 202, 3, 77)
require.NoError(t, err)
require.True(t, acquired)
require.NotNil(t, release)
require.Equal(t, 1, cache.apiKeyTrackCalls)
require.Equal(t, []int64{77}, cache.apiKeyTrackIDs)
release()
require.Equal(t, 1, cache.userReleaseCalls)
require.Equal(t, 1, cache.apiKeyReleaseCalls)
}
func TestAcquireUserSlotWithWait_WaitSuccessDecrementsBeforeReturn(t *testing.T) {
cache := &helperConcurrencyCacheStub{
userSeq: []bool{false, true},
@@ -269,6 +269,149 @@ func TestGatewayModels_CustomModelsListKeepsConcreteModelAllowedByWildcardMappin
require.Equal(t, []string{"claude-sonnet-4-6"}, modelIDsForTest(got.Data))
}
func TestGatewayModels_AnthropicCustomModelsListIncludesOAuthClaudeAndMappedDeepSeek(t *testing.T) {
gin.SetMode(gin.TestMode)
groupID := int64(28)
h := newGatewayModelsHandlerForTest(
&gatewayModelsAccountRepoStub{
byGroup: map[int64][]service.Account{
groupID: {
{
ID: 1,
Platform: service.PlatformAnthropic,
Type: service.AccountTypeOAuth,
},
{
ID: 2,
Platform: service.PlatformAnthropic,
Type: service.AccountTypeAPIKey,
Credentials: map[string]any{
"model_mapping": map[string]any{
"deepseek-v4-pro": "deepseek-v4-pro",
},
},
},
},
},
},
)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
Group: &service.Group{
ID: groupID,
Platform: service.PlatformAnthropic,
ModelsListConfig: service.GroupModelsListConfig{
Enabled: true,
Models: []string{"claude-fable-5", "claude-opus-4-8", "deepseek-v4-pro"},
},
},
})
h.Models(c)
require.Equal(t, http.StatusOK, rec.Code)
var got gatewayModelsResponseForTest
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got))
require.Equal(t, []string{"claude-fable-5", "claude-opus-4-8", "deepseek-v4-pro"}, modelIDsForTest(got.Data))
}
func TestGatewayModels_AnthropicCustomModelsListDisabledKeepsMappedModelList(t *testing.T) {
gin.SetMode(gin.TestMode)
groupID := int64(29)
h := newGatewayModelsHandlerForTest(
&gatewayModelsAccountRepoStub{
byGroup: map[int64][]service.Account{
groupID: {
{
ID: 1,
Platform: service.PlatformAnthropic,
Type: service.AccountTypeOAuth,
},
{
ID: 2,
Platform: service.PlatformAnthropic,
Type: service.AccountTypeAPIKey,
Credentials: map[string]any{
"model_mapping": map[string]any{
"deepseek-v4-pro": "deepseek-v4-pro",
},
},
},
},
},
},
)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
Group: &service.Group{
ID: groupID,
Platform: service.PlatformAnthropic,
ModelsListConfig: service.GroupModelsListConfig{
Enabled: false,
Models: []string{"claude-fable-5", "deepseek-v4-pro"},
},
},
})
h.Models(c)
require.Equal(t, http.StatusOK, rec.Code)
var got gatewayModelsResponseForTest
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got))
require.Equal(t, []string{"deepseek-v4-pro"}, modelIDsForTest(got.Data))
}
func TestGatewayModels_AnthropicCustomModelsListIncludesOAuthClaudeWithoutMappings(t *testing.T) {
gin.SetMode(gin.TestMode)
groupID := int64(30)
h := newGatewayModelsHandlerForTest(
&gatewayModelsAccountRepoStub{
byGroup: map[int64][]service.Account{
groupID: {
{
ID: 1,
Platform: service.PlatformAnthropic,
Type: service.AccountTypeOAuth,
},
},
},
},
)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodGet, "/v1/models", nil)
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
Group: &service.Group{
ID: groupID,
Platform: service.PlatformAnthropic,
ModelsListConfig: service.GroupModelsListConfig{
Enabled: true,
Models: []string{"claude-opus-4-6-thinking", "claude-sonnet-4-5"},
},
},
})
h.Models(c)
require.Equal(t, http.StatusOK, rec.Code)
var got gatewayModelsResponseForTest
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got))
require.Equal(t, []string{"claude-opus-4-6-thinking", "claude-sonnet-4-5"}, modelIDsForTest(got.Data))
}
func TestGatewayModels_CustomModelsListCanReturnEmptyWhenSelectionsUnavailable(t *testing.T) {
gin.SetMode(gin.TestMode)
+1
View File
@@ -174,6 +174,7 @@ func (h *OpenAIGatewayHandler) handleGrokMedia(c *gin.Context, endpoint service.
service.OpenAIUpstreamTransportHTTPSSE,
"",
false,
false,
service.PlatformGrok,
)
if err != nil {
+1
View File
@@ -58,6 +58,7 @@ type Handlers struct {
Payment *PaymentHandler
PaymentWebhook *PaymentWebhookHandler
AvailableChannel *AvailableChannelHandler
BatchImage *BatchImageHandler
}
// BuildInfo contains build-time information
@@ -64,6 +64,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
}
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -145,6 +146,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
service.OpenAIUpstreamTransportAny,
service.OpenAIEndpointCapabilityChatCompletions,
false,
false,
requestPlatform,
)
if err != nil {
@@ -60,6 +60,7 @@ func (h *OpenAIGatewayHandler) Embeddings(c *gin.Context) {
return
}
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -117,6 +118,7 @@ func (h *OpenAIGatewayHandler) Embeddings(c *gin.Context) {
service.OpenAIUpstreamTransportHTTPSSE,
service.OpenAIEndpointCapabilityEmbeddings,
false,
false,
)
if err != nil {
reqLog.Warn("openai_embeddings.account_select_failed",
@@ -64,6 +64,7 @@ func (h *OpenAIGatewayHandler) CountTokens(c *gin.Context) {
bodyRef := service.NewRequestBodyRef(body)
parsedReq, err := service.ParseGatewayRequest(bodyRef, domain.PlatformAnthropic)
if err != nil {
logRequestBodyParseFailure(reqLog, body, err)
h.anthropicErrorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -110,6 +111,7 @@ func (h *OpenAIGatewayHandler) CountTokens(c *gin.Context) {
service.OpenAIUpstreamTransportAny,
service.OpenAIEndpointCapabilityChatCompletions,
false,
false,
openAICompatibleRequestPlatform(apiKey),
)
service.SetOpsLatencyMs(c, service.OpsAuthLatencyMsKey, time.Since(requestStart).Milliseconds())
@@ -218,6 +218,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// 校验请求体 JSON 合法性
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -350,6 +351,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
service.OpenAIUpstreamTransportAny,
service.OpenAIEndpointCapabilityChatCompletions,
requireCompact,
false,
requestPlatform,
)
if err != nil {
@@ -696,6 +698,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
}
if !gjson.ValidBytes(body) {
logRequestBodyParseFailure(reqLog, body, nil)
h.anthropicErrorResponse(c, http.StatusBadRequest, "invalid_request_error", "Failed to parse request body")
return
}
@@ -783,6 +786,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
service.OpenAIUpstreamTransportAny,
service.OpenAIEndpointCapabilityChatCompletions,
false,
false,
requestPlatform,
)
if err != nil {
@@ -1266,6 +1270,8 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
closeOpenAIClientWS(wsConn, coderws.StatusPolicyViolation, "previous_response_id must be a response.id (resp_*), not a message id")
return
}
firstMessageToolCoverage := service.AnalyzeToolCallOutputContextCoverageBytes(firstMessage)
previousResponseCanMove := !firstMessageToolCoverage.HasFunctionCallOutput || firstMessageToolCoverage.ContextCoversAllCallIDs
reqLog = reqLog.With(
zap.Bool("ws_ingress", true),
zap.String("model", reqModel),
@@ -1318,7 +1324,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
// 必须尽早注册,确保任何 early return 都能释放已获取的并发槽位。
defer releaseTurnSlots()
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlot(ctx, subject.UserID, subject.Concurrency)
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlotForAPIKey(ctx, subject.UserID, subject.Concurrency, apiKey.ID)
if err != nil {
reqLog.Warn("openai.websocket_user_slot_acquire_failed", zap.Error(err))
closeOpenAIClientWS(wsConn, coderws.StatusInternalError, "failed to acquire user concurrency slot")
@@ -1333,7 +1339,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
if currentUserRelease != nil {
return true
}
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlot(ctx, subject.UserID, subject.Concurrency)
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlotForAPIKey(ctx, subject.UserID, subject.Concurrency, apiKey.ID)
if err != nil {
reqLog.Warn("openai.websocket_user_slot_reacquire_failed", zap.Error(err))
closeOpenAIClientWS(wsConn, coderws.StatusInternalError, "failed to acquire user concurrency slot")
@@ -1381,6 +1387,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
requiredTransport,
service.OpenAIEndpointCapabilityChatCompletions,
false,
previousResponseCanMove,
requestPlatform,
)
if err != nil {
@@ -1484,7 +1491,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
// 防御式清理:避免异常路径下旧槽位覆盖导致泄漏。
releaseTurnSlots()
// 非首轮 turn 需要重新抢占并发槽位,避免长连接空闲占槽。
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlot(ctx, subject.UserID, subject.Concurrency)
userReleaseFunc, userAcquired, err := h.concurrencyHelper.TryAcquireUserSlotForAPIKey(ctx, subject.UserID, subject.Concurrency, apiKey.ID)
if err != nil {
return service.NewOpenAIWSClientCloseError(coderws.StatusInternalError, "failed to acquire user concurrency slot", err)
}
@@ -1581,8 +1588,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
// 说明该会话链不属于本次调度到的账号,原样转发会触发上游会话链鉴权失败(“鉴权失败,请检查 API Key”)。
// 故剥离首包里的 previous_response_id,改用首包内 input 重建上下文;带 function_call_output 的
// 工具续链无法重建,保持原样。仅作用于首轮首包,后续 turn 的续链由 WS 转发层既有逻辑处理。
if previousResponseID != "" && !scheduleDecision.StickyPreviousHit &&
!service.ValidateFunctionCallOutputContextBytes(wsFirstMessage).HasFunctionCallOutput {
if previousResponseID != "" && !scheduleDecision.StickyPreviousHit && previousResponseCanMove {
wsFirstMessage = service.RemovePreviousResponseIDFromBody(wsFirstMessage)
reqLog.Debug("openai.websocket_previous_response_id_stripped_cross_group",
zap.Int64("account_id", account.ID),
@@ -150,6 +150,7 @@ func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) {
Plans: planList,
BalanceDisabled: cfg.BalanceDisabled,
BalanceRechargeMultiplier: cfg.BalanceRechargeMultiplier,
SubscriptionUSDToCNYRate: cfg.SubscriptionUSDToCNYRate,
RechargeFeeRate: cfg.RechargeFeeRate,
HelpText: cfg.HelpText,
HelpImageURL: cfg.HelpImageURL,
@@ -165,6 +166,7 @@ type checkoutInfoResponse struct {
Plans []checkoutPlan `json:"plans"`
BalanceDisabled bool `json:"balance_disabled"`
BalanceRechargeMultiplier float64 `json:"balance_recharge_multiplier"`
SubscriptionUSDToCNYRate float64 `json:"subscription_usd_to_cny_rate"`
RechargeFeeRate float64 `json:"recharge_fee_rate"`
HelpText string `json:"help_text"`
HelpImageURL string `json:"help_image_url"`
@@ -0,0 +1,54 @@
package handler
import (
"strconv"
"github.com/Wei-Shaw/sub2api/internal/service"
"go.uber.org/zap"
)
// parseFailureSnippetLen bounds the head/tail snippets logged on body parse
// failure. 256 bytes is enough to see the structural context (model field,
// first content block / trailing brace) without dumping user payloads.
const parseFailureSnippetLen = 256
// logRequestBodyParseFailure records the real reason a request body failed
// JSON parsing/validation. The client keeps receiving the generic
// "Failed to parse request body"; the sanitized diagnostics (underlying
// error with byte offset, body length, escaped head/tail snippets) land in
// the server log only, so operators can distinguish genuinely invalid JSON
// from a truncated or partially consumed body.
//
// err may be nil for call sites that validate with gjson.ValidBytes directly;
// the diagnostic error is derived from the body in that case.
func logRequestBodyParseFailure(reqLog *zap.Logger, body []byte, err error) {
if reqLog == nil {
return
}
if err == nil {
err = service.DescribeInvalidJSON(body)
}
head := body
var tail []byte
if len(body) > parseFailureSnippetLen {
head = body[:parseFailureSnippetLen]
tail = body[len(body)-parseFailureSnippetLen:]
}
fields := []zap.Field{
zap.Error(err),
zap.Int("body_len", len(body)),
zap.String("body_head", sanitizeBodySnippet(head)),
}
if len(tail) > 0 {
fields = append(fields, zap.String("body_tail", sanitizeBodySnippet(tail)))
}
reqLog.Warn("parse request body failed", fields...)
}
// sanitizeBodySnippet escapes control characters and invalid UTF-8 so the
// snippet is always a single printable log line.
func sanitizeBodySnippet(b []byte) string {
return strconv.Quote(string(b))
}
@@ -0,0 +1,100 @@
//go:build unit
package handler
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)
func newObservedLogger(t *testing.T) (*zap.Logger, *observer.ObservedLogs) {
t.Helper()
core, logs := observer.New(zap.WarnLevel)
return zap.New(core), logs
}
func loggedFields(t *testing.T, logs *observer.ObservedLogs) map[string]any {
t.Helper()
entries := logs.All()
require.Len(t, entries, 1)
fields := map[string]any{}
for _, f := range entries[0].Context {
switch f.Key {
case "body_len":
fields[f.Key] = int(f.Integer)
case "error":
fields[f.Key] = f.Interface.(error).Error()
default:
fields[f.Key] = f.String
}
}
return fields
}
func TestLogRequestBodyParseFailure_DerivesErrorWhenNil(t *testing.T) {
log, logs := newObservedLogger(t)
body := []byte(`{"model": bad}`)
logRequestBodyParseFailure(log, body, nil)
fields := loggedFields(t, logs)
require.Equal(t, len(body), fields["body_len"])
require.Contains(t, fields["error"], "invalid json")
require.Contains(t, fields["error"], "offset=11")
}
func TestLogRequestBodyParseFailure_ShortBodyHasNoTail(t *testing.T) {
log, logs := newObservedLogger(t)
body := []byte(`{"broken":`)
logRequestBodyParseFailure(log, body, nil)
fields := loggedFields(t, logs)
require.Contains(t, fields, "body_head")
require.NotContains(t, fields, "body_tail")
require.Contains(t, fields["body_head"].(string), `{\"broken\":`)
}
func TestLogRequestBodyParseFailure_LargeBodyBoundedSnippets(t *testing.T) {
log, logs := newObservedLogger(t)
// ~1MB body: head must show the structural prefix, tail the trailing bytes,
// and neither snippet may exceed the configured bound (plus quoting overhead).
body := []byte(`{"model":"claude-sonnet-4-6","big":"` + strings.Repeat("A", 1<<20) + `"`)
logRequestBodyParseFailure(log, body, nil)
fields := loggedFields(t, logs)
require.Equal(t, len(body), fields["body_len"])
head := fields["body_head"].(string)
tail := fields["body_tail"].(string)
require.Contains(t, head, "claude-sonnet-4-6")
require.Contains(t, tail, "AAA")
require.NotContains(t, tail, "claude-sonnet-4-6")
// strconv.Quote adds surrounding quotes and escapes; 4x is a generous cap.
require.LessOrEqual(t, len(head), parseFailureSnippetLen*4)
require.LessOrEqual(t, len(tail), parseFailureSnippetLen*4)
}
func TestLogRequestBodyParseFailure_EscapesControlCharacters(t *testing.T) {
log, logs := newObservedLogger(t)
body := []byte("{\"model\":\x01\n\"x\"}")
logRequestBodyParseFailure(log, body, nil)
fields := loggedFields(t, logs)
head := fields["body_head"].(string)
require.NotContains(t, head, "\n")
require.NotContains(t, head, "\x01")
require.Contains(t, head, `\n`)
require.Contains(t, head, `\x01`)
}
func TestLogRequestBodyParseFailure_NilLoggerNoPanic(t *testing.T) {
require.NotPanics(t, func() {
logRequestBodyParseFailure(nil, []byte(`{`), nil)
})
}
+12 -7
View File
@@ -85,22 +85,27 @@ func writeResponsesFailedSSE(c *gin.Context, errType, message string) bool {
return true
}
// inboundIsResponses 判断当前请求是否落在任何 /responses 路由上。
// inboundIsResponses 判断当前请求是否落在任意 Responses 路由上
// (不区分 root 还是 compact 变体)。
//
// 不能直接用 GetInboundEndpoint(c) == EndpointResponses 比较,因为
// NormalizeInboundEndpoint 只识别包含 "/v1/responses" 子串的路径;
// 项目里实际注册了多组路由(gateway_v1、top-level bare、codex direct),
// 其中 r.POST("/responses", ...) 和 codexDirect.POST("/responses", ...)
// 的 c.FullPath() 不含 "/v1/" 前缀,会被归一化为原始路径,
// 导致协议合规终止事件没法发出去。
// GetInboundEndpoint/NormalizeInboundEndpoint 会把 compact 变体归一化为
// 单独的 EndpointResponsesCompact(而不是 EndpointResponses),
// 而本函数在这里只关心“是不是 Responses 家族的请求”,
// 不需要区分 root/compact,所以不能用那个等值比较。
//
// 这里用 FullPath 的后缀判断,覆盖所有变体:
// 这里改用 FullPath 的后缀/子串判断,一次性覆盖 root 和 compact 的所有变体:
// - /v1/responses
// - /v1/responses/compact
// - /responses
// - /responses/compact
// - /backend-api/codex/responses
// - /backend-api/codex/responses/compact
//
// 对于通配路由(如 "/v1/responses/*action")注册的 FullPath 本身就带有
// "/responses/" 子串(例如 "/v1/responses/*action"),所以下面的
// strings.Contains(p, "/responses/") 分支同样能覆盖这些通配路由,
// 不需要额外处理通配符本身。
func inboundIsResponses(c *gin.Context) bool {
if c == nil {
return false
+3
View File
@@ -115,6 +115,7 @@ func ProvideHandlers(
paymentHandler *PaymentHandler,
paymentWebhookHandler *PaymentWebhookHandler,
availableChannelHandler *AvailableChannelHandler,
batchImageHandler *BatchImageHandler,
_ *service.IdempotencyCoordinator,
_ *service.IdempotencyCleanupService,
) *Handlers {
@@ -135,6 +136,7 @@ func ProvideHandlers(
Payment: paymentHandler,
PaymentWebhook: paymentWebhookHandler,
AvailableChannel: availableChannelHandler,
BatchImage: batchImageHandler,
}
}
@@ -156,6 +158,7 @@ var ProviderSet = wire.NewSet(
NewPaymentHandler,
NewPaymentWebhookHandler,
NewAvailableChannelHandler,
NewBatchImageHandler,
// Admin handlers
admin.NewDashboardHandler,
+54 -5
View File
@@ -39,6 +39,12 @@ type EasyPay struct {
httpClient *http.Client
}
type easyPayCustomMethod struct {
Type string `json:"type"`
UpstreamType string `json:"upstreamType"`
DisplayName string `json:"displayName"`
}
// NewEasyPay creates a new EasyPay provider.
// config keys: pid, pkey, apiBase, notifyUrl, returnUrl, cid, cidAlipay, cidWxpay
func NewEasyPay(instanceID string, config map[string]string) (*EasyPay, error) {
@@ -95,7 +101,13 @@ func (e *EasyPay) apiBase() string {
func (e *EasyPay) Name() string { return "EasyPay" }
func (e *EasyPay) ProviderKey() string { return payment.TypeEasyPay }
func (e *EasyPay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeAlipay, payment.TypeWxpay}
types := []payment.PaymentType{payment.TypeAlipay, payment.TypeWxpay}
for _, method := range e.customMethods() {
if method.Type != "" {
types = append(types, method.Type)
}
}
return types
}
func (e *EasyPay) MerchantIdentityMetadata() map[string]string {
@@ -124,13 +136,14 @@ func (e *EasyPay) CreatePayment(ctx context.Context, req payment.CreatePaymentRe
// TradeNo is empty; it arrives via the notify callback after payment.
func (e *EasyPay) createRedirectPayment(req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
notifyURL, returnURL := e.resolveURLs(req)
paymentType := e.upstreamPaymentType(req.PaymentType)
params := map[string]string{
"pid": e.config["pid"], "type": req.PaymentType,
"pid": e.config["pid"], "type": paymentType,
"out_trade_no": req.OrderID, "notify_url": notifyURL,
"return_url": returnURL, "name": req.Subject,
"money": req.Amount,
}
if cid := e.resolveCID(req.PaymentType); cid != "" {
if cid := e.resolveCID(paymentType); cid != "" {
params["cid"] = cid
}
if req.IsMobile {
@@ -150,13 +163,14 @@ func (e *EasyPay) createRedirectPayment(req payment.CreatePaymentRequest) (*paym
// createAPIPayment calls mapi.php to get payurl/qrcode (existing behavior).
func (e *EasyPay) createAPIPayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
notifyURL, returnURL := e.resolveURLs(req)
paymentType := e.upstreamPaymentType(req.PaymentType)
params := map[string]string{
"pid": e.config["pid"], "type": req.PaymentType,
"pid": e.config["pid"], "type": paymentType,
"out_trade_no": req.OrderID, "notify_url": notifyURL,
"return_url": returnURL, "name": req.Subject,
"money": req.Amount, "clientip": req.ClientIP,
}
if cid := e.resolveCID(req.PaymentType); cid != "" {
if cid := e.resolveCID(paymentType); cid != "" {
params["cid"] = cid
}
if req.IsMobile {
@@ -204,6 +218,41 @@ func (e *EasyPay) resolveURLs(req payment.CreatePaymentRequest) (string, string)
return notifyURL, returnURL
}
func (e *EasyPay) customMethods() []easyPayCustomMethod {
if e == nil {
return nil
}
raw := strings.TrimSpace(e.config["customMethods"])
if raw == "" {
return nil
}
var methods []easyPayCustomMethod
if err := json.Unmarshal([]byte(raw), &methods); err != nil {
return nil
}
result := make([]easyPayCustomMethod, 0, len(methods))
for _, method := range methods {
method.Type = strings.TrimSpace(method.Type)
method.UpstreamType = strings.TrimSpace(method.UpstreamType)
method.DisplayName = strings.TrimSpace(method.DisplayName)
if method.Type == "" || method.UpstreamType == "" {
continue
}
result = append(result, method)
}
return result
}
func (e *EasyPay) upstreamPaymentType(paymentType string) string {
paymentType = strings.TrimSpace(paymentType)
for _, method := range e.customMethods() {
if paymentType == method.Type {
return method.UpstreamType
}
}
return paymentType
}
func (e *EasyPay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) {
params := map[string]string{
"act": "order", "pid": e.config["pid"],
@@ -179,6 +179,102 @@ func TestEasyPayRefundResponseErrors(t *testing.T) {
}
}
func TestEasyPayCustomMethodsUseConfiguredUpstreamType(t *testing.T) {
t.Parallel()
provider, err := NewEasyPay("test-instance", map[string]string{
"pid": "pid-1",
"pkey": "pkey-1",
"apiBase": "https://pay.example.com",
"notifyUrl": "https://example.com/notify",
"returnUrl": "https://example.com/return",
"paymentMode": paymentModePopup,
"customMethods": `[{"type":"ldc","upstreamType":"epay","displayName":"LDC"},{"type":"usdt_trc20","upstreamType":"usdt","displayName":"USDT-TRC20"}]`,
})
if err != nil {
t.Fatalf("NewEasyPay: %v", err)
}
resp, err := provider.CreatePayment(context.Background(), payment.CreatePaymentRequest{
OrderID: "sub2-custom-1",
Amount: "1.00",
PaymentType: "usdt_trc20",
Subject: "Custom EasyPay",
})
if err != nil {
t.Fatalf("CreatePayment: %v", err)
}
payURL, err := url.Parse(resp.PayURL)
if err != nil {
t.Fatalf("parse pay url: %v", err)
}
if got := payURL.Query().Get("type"); got != "usdt" {
t.Fatalf("pay url type = %q, want usdt (%s)", got, resp.PayURL)
}
}
func TestEasyPayCustomMethodsResolveCIDFromConfiguredUpstreamType(t *testing.T) {
t.Parallel()
provider, err := NewEasyPay("test-instance", map[string]string{
"pid": "pid-1",
"pkey": "pkey-1",
"apiBase": "https://pay.example.com",
"notifyUrl": "https://example.com/notify",
"returnUrl": "https://example.com/return",
"paymentMode": paymentModePopup,
"cidAlipay": "cid-alipay",
"cidWxpay": "cid-wxpay",
"customMethods": `[{"type":"ldc","upstreamType":"alipay","displayName":"LDC"}]`,
})
if err != nil {
t.Fatalf("NewEasyPay: %v", err)
}
resp, err := provider.CreatePayment(context.Background(), payment.CreatePaymentRequest{
OrderID: "sub2-custom-cid",
Amount: "1.00",
PaymentType: "ldc",
Subject: "Custom EasyPay CID",
})
if err != nil {
t.Fatalf("CreatePayment: %v", err)
}
payURL, err := url.Parse(resp.PayURL)
if err != nil {
t.Fatalf("parse pay url: %v", err)
}
if got := payURL.Query().Get("type"); got != "alipay" {
t.Fatalf("pay url type = %q, want alipay (%s)", got, resp.PayURL)
}
if got := payURL.Query().Get("cid"); got != "cid-alipay" {
t.Fatalf("pay url cid = %q, want cid-alipay (%s)", got, resp.PayURL)
}
}
func TestEasyPaySupportedTypesIncludeCustomMethods(t *testing.T) {
t.Parallel()
provider, err := NewEasyPay("test-instance", map[string]string{
"pid": "pid-1",
"pkey": "pkey-1",
"apiBase": "https://pay.example.com",
"notifyUrl": "https://example.com/notify",
"returnUrl": "https://example.com/return",
"customMethods": `[{"type":"ldc","upstreamType":"epay","displayName":"LDC"},{"type":"usdt_trc20","upstreamType":"usdt","displayName":"USDT-TRC20"}]`,
})
if err != nil {
t.Fatalf("NewEasyPay: %v", err)
}
got := strings.Join(provider.SupportedTypes(), ",")
for _, want := range []string{"alipay", "wxpay", "ldc", "usdt_trc20"} {
if !strings.Contains(got, want) {
t.Fatalf("SupportedTypes() = %q, want it to include %q", got, want)
}
}
}
func newTestEasyPay(t *testing.T, apiBase string) *EasyPay {
t.Helper()
+3
View File
@@ -18,6 +18,9 @@ type Model struct {
// DefaultModels OpenAI models list
var DefaultModels = []Model{
{ID: "gpt-5.6-sol", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Sol"},
{ID: "gpt-5.6-terra", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Terra"},
{ID: "gpt-5.6-luna", Object: "model", Created: 1780876800, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.6 Luna"},
{ID: "gpt-5.5", Object: "model", Created: 1776873600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.5"},
{ID: "gpt-5.4", Object: "model", Created: 1738368000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.4"},
{ID: "gpt-5.4-mini", Object: "model", Created: 1738368000, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.4 Mini"},
+2
View File
@@ -12,6 +12,7 @@ type Model struct {
var defaultModels = []Model{
{ID: "grok-4.3", Object: "model", OwnedBy: "xai", DisplayName: "Grok 4.3"},
{ID: "grok-build-0.1", Object: "model", OwnedBy: "xai", DisplayName: "Grok Build 0.1"},
{ID: "grok-composer-2.5-fast", Object: "model", OwnedBy: "xai", DisplayName: "Grok Composer 2.5 Fast"},
{ID: "grok-4.20-0309-reasoning", Object: "model", OwnedBy: "xai", DisplayName: "Grok 4.20 Reasoning"},
{ID: "grok-4.20-0309-non-reasoning", Object: "model", OwnedBy: "xai", DisplayName: "Grok 4.20 Non Reasoning"},
{ID: "grok-4.20-multi-agent-0309", Object: "model", OwnedBy: "xai", DisplayName: "Grok 4.20 Multi Agent"},
@@ -46,6 +47,7 @@ func DefaultModelMapping() map[string]string {
mapping["grok"] = "grok-4.3"
mapping["grok-latest"] = "grok-4.3"
mapping["grok-build"] = "grok-build-0.1"
mapping["grok-composer"] = "grok-composer-2.5-fast"
mapping["grok-4.20-reasoning"] = "grok-4.20-0309-reasoning"
mapping["grok-4.20-non-reasoning"] = "grok-4.20-0309-non-reasoning"
return mapping
+1
View File
@@ -210,6 +210,7 @@ func TestDefaultModelMappingIncludesGrokAliases(t *testing.T) {
require.Equal(t, "grok-4.3", mapping["grok"])
require.Equal(t, "grok-4.3", mapping["grok-latest"])
require.Equal(t, "grok-build-0.1", mapping["grok-build"])
require.Equal(t, "grok-composer-2.5-fast", mapping["grok-composer"])
require.Equal(t, "grok-4.20-0309-reasoning", mapping["grok-4.20-reasoning"])
require.Equal(t, "grok-4.20-0309-non-reasoning", mapping["grok-4.20-non-reasoning"])
require.Equal(t, "grok-4.20-multi-agent-0309", mapping["grok-4.20-multi-agent-0309"])
+14 -1
View File
@@ -482,7 +482,7 @@ func (r *accountRepository) List(ctx context.Context, params pagination.Paginati
return r.ListWithFilters(ctx, params, "", "", "", "", 0, "")
}
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
func (r *accountRepository) accountListFilteredQuery(platform, accountType, status, search string, groupID int64, privacyMode string) *dbent.AccountQuery {
q := r.client.Account.Query()
if platform != "" {
@@ -575,6 +575,11 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
}))
}
return q
}
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
q := r.accountListFilteredQuery(platform, accountType, status, search, groupID, privacyMode)
// Clone before Count so interceptor-appended predicates (SoftDeleteMixin's
// deleted_at IS NULL) don't accumulate on the shared builder and pollute the
// subsequent list query. Same pattern used in group_repo/promo_code_repo/user_repo
@@ -603,6 +608,14 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
return outAccounts, paginationResultFromTotal(int64(total), params), nil
}
func (r *accountRepository) ListAllWithFilters(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, error) {
accounts, err := r.accountListFilteredQuery(platform, accountType, status, search, groupID, privacyMode).All(ctx)
if err != nil {
return nil, err
}
return r.accountsToService(ctx, accounts)
}
func (r *accountRepository) ListOpsAccountsForStats(ctx context.Context, platformFilter string, groupIDFilter *int64) ([]service.Account, error) {
if r == nil || r.client == nil {
return []service.Account{}, nil
@@ -177,6 +177,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
group.FieldWeeklyLimitUsd,
group.FieldMonthlyLimitUsd,
group.FieldAllowImageGeneration,
group.FieldAllowBatchImageGeneration,
group.FieldImageRateIndependent,
group.FieldImageRateMultiplier,
group.FieldImagePrice1k,
@@ -755,6 +756,7 @@ func userEntityToService(u *dbent.User) *service.User {
PasswordHash: u.PasswordHash,
Role: u.Role,
Balance: u.Balance,
FrozenBalance: u.FrozenBalance,
Concurrency: u.Concurrency,
Status: u.Status,
SignupSource: u.SignupSource,
@@ -797,11 +799,14 @@ func groupEntityToService(g *dbent.Group) *service.Group {
WeeklyLimitUSD: g.WeeklyLimitUsd,
MonthlyLimitUSD: g.MonthlyLimitUsd,
AllowImageGeneration: g.AllowImageGeneration,
AllowBatchImageGeneration: g.AllowBatchImageGeneration,
ImageRateIndependent: g.ImageRateIndependent,
ImageRateMultiplier: g.ImageRateMultiplier,
ImagePrice1K: g.ImagePrice1k,
ImagePrice2K: g.ImagePrice2k,
ImagePrice4K: g.ImagePrice4k,
BatchImageDiscountMultiplier: g.BatchImageDiscountMultiplier,
BatchImageHoldMultiplier: g.BatchImageHoldMultiplier,
DefaultValidityDays: g.DefaultValidityDays,
ClaudeCodeOnly: g.ClaudeCodeOnly,
FallbackGroupID: g.FallbackGroupID,
@@ -0,0 +1,102 @@
package repository
import (
"context"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/redis/go-redis/v9"
)
const (
defaultBatchImageDownloadActivePrefix = "batch_image:download:active:"
defaultBatchImageDownloadActiveTTL = 10 * time.Minute
defaultBatchImageDownloadConcurrency = 2
)
var batchImageDownloadAcquireScript = redis.NewScript(`
local current = tonumber(redis.call("GET", KEYS[1]) or "0")
local max = tonumber(ARGV[1])
if current >= max then
return 0
end
redis.call("INCR", KEYS[1])
redis.call("EXPIRE", KEYS[1], ARGV[2])
return 1
`)
var batchImageDownloadReleaseScript = redis.NewScript(`
local current = tonumber(redis.call("GET", KEYS[1]) or "0")
if current <= 1 then
redis.call("DEL", KEYS[1])
return 0
end
return redis.call("DECR", KEYS[1])
`)
type batchImageDownloadLimiter struct {
rdb *redis.Client
activePrefix string
maxActive int
ttl time.Duration
}
func NewBatchImageDownloadLimiter(rdb *redis.Client, cfg *config.Config) service.BatchImageDownloadLimiter {
maxActive := defaultBatchImageDownloadConcurrency
ttl := defaultBatchImageDownloadActiveTTL
if cfg != nil {
if cfg.BatchImage.MaxDownloadConcurrencyPerUser > 0 {
maxActive = cfg.BatchImage.MaxDownloadConcurrencyPerUser
}
if cfg.BatchImage.MaxDownloadDurationSeconds > 0 {
ttl = time.Duration(cfg.BatchImage.MaxDownloadDurationSeconds) * time.Second
}
}
return &batchImageDownloadLimiter{
rdb: rdb,
activePrefix: defaultBatchImageDownloadActivePrefix,
maxActive: maxActive,
ttl: ttl,
}
}
func (l *batchImageDownloadLimiter) Acquire(ctx context.Context, userID string, kind string) (service.BatchImageDownloadPermit, error) {
if l == nil || l.rdb == nil {
return nil, service.ErrBatchImageDownloadLimited
}
key := l.activeKey(userID)
ok, err := batchImageDownloadAcquireScript.Run(ctx, l.rdb, []string{key}, l.maxActive, int(l.ttl.Seconds())).Int()
if err != nil {
return nil, err
}
if ok != 1 {
return nil, service.ErrBatchImageDownloadLimited
}
return &batchImageDownloadPermit{rdb: l.rdb, key: key}, nil
}
func (l *batchImageDownloadLimiter) activeKey(userID string) string {
return l.activePrefix + userID
}
type batchImageDownloadPermit struct {
rdb *redis.Client
key string
once sync.Once
err error
}
func (p *batchImageDownloadPermit) Release(ctx context.Context) error {
if p == nil || p.rdb == nil || p.key == "" {
return nil
}
p.once.Do(func() {
_, p.err = batchImageDownloadReleaseScript.Run(ctx, p.rdb, []string{p.key}).Result()
})
return p.err
}
var _ service.BatchImageDownloadLimiter = (*batchImageDownloadLimiter)(nil)
var _ service.BatchImageDownloadPermit = (*batchImageDownloadPermit)(nil)
@@ -0,0 +1,43 @@
//go:build unit
package repository
import (
"context"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/require"
)
func TestBatchImageDownloadLimiter_AcquireDenyReleaseAndTTL(t *testing.T) {
ctx := context.Background()
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
t.Cleanup(func() { _ = rdb.Close() })
limiter := &batchImageDownloadLimiter{
rdb: rdb,
activePrefix: defaultBatchImageDownloadActivePrefix,
maxActive: 1,
ttl: time.Minute,
}
permit, err := limiter.Acquire(ctx, "11", "zip")
require.NoError(t, err)
require.NotNil(t, permit)
require.True(t, mr.TTL(limiter.activeKey("11")) > 0)
_, err = limiter.Acquire(ctx, "11", "zip")
require.ErrorIs(t, err, service.ErrBatchImageDownloadLimited)
require.NoError(t, permit.Release(ctx))
require.NoError(t, permit.Release(ctx))
require.False(t, mr.Exists(limiter.activeKey("11")))
permit, err = limiter.Acquire(ctx, "11", "zip")
require.NoError(t, err)
require.NotNil(t, permit)
}
@@ -0,0 +1,280 @@
package repository
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/redis/go-redis/v9"
)
const (
defaultBatchImageReadyKey = "batch_image:queue:ready"
defaultBatchImageDelayedKey = "batch_image:queue:delayed"
defaultBatchImageActiveKey = "batch_image:queue:active"
defaultBatchImageInflightPrefix = "batch_image:queue:inflight:"
defaultBatchImageLockPrefix = "batch_image:queue:lock:"
defaultBatchImageInflightTTL = 7 * 24 * time.Hour
defaultBatchImageJobLockTTL = 5 * time.Minute
)
var batchImageMoveDueDelayedScript = redis.NewScript(`
local jobs = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", ARGV[1], "LIMIT", 0, ARGV[2])
for _, job in ipairs(jobs) do
redis.call("ZREM", KEYS[1], job)
redis.call("LPUSH", KEYS[2], job)
end
return #jobs
`)
var batchImageRecoverStaleActiveScript = redis.NewScript(`
local jobs = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", ARGV[1], "LIMIT", 0, ARGV[2])
for _, job in ipairs(jobs) do
redis.call("ZREM", KEYS[1], job)
redis.call("LPUSH", KEYS[2], job)
end
return #jobs
`)
var batchImageReleaseLockScript = redis.NewScript(`
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0
`)
type batchImageQueue struct {
rdb *redis.Client
readyKey string
delayedKey string
activeKey string
inflightPrefix string
lockPrefix string
inflightTTL time.Duration
lockTTL time.Duration
}
func NewBatchImageQueue(rdb *redis.Client, cfg *config.Config) service.BatchImageQueue {
return newBatchImageQueueWithOptions(rdb, batchImageQueueOptionsFromConfig(cfg))
}
type batchImageQueueOptions struct {
ReadyKey string
DelayedKey string
ActiveKey string
InflightPrefix string
LockPrefix string
InflightTTL time.Duration
LockTTL time.Duration
}
func newBatchImageQueueWithOptions(rdb *redis.Client, opts batchImageQueueOptions) *batchImageQueue {
opts = normalizeBatchImageQueueOptions(opts)
return &batchImageQueue{
rdb: rdb,
readyKey: opts.ReadyKey,
delayedKey: opts.DelayedKey,
activeKey: opts.ActiveKey,
inflightPrefix: opts.InflightPrefix,
lockPrefix: opts.LockPrefix,
inflightTTL: opts.InflightTTL,
lockTTL: opts.LockTTL,
}
}
func batchImageQueueOptionsFromConfig(cfg *config.Config) batchImageQueueOptions {
if cfg == nil {
return batchImageQueueOptions{}
}
return batchImageQueueOptions{
ReadyKey: cfg.BatchImage.QueueReadyKey,
DelayedKey: cfg.BatchImage.QueueDelayedKey,
ActiveKey: cfg.BatchImage.QueueActiveKey,
InflightPrefix: cfg.BatchImage.InflightKeyPrefix,
LockPrefix: cfg.BatchImage.LockKeyPrefix,
InflightTTL: time.Duration(cfg.BatchImage.InflightTTLSeconds) * time.Second,
LockTTL: time.Duration(cfg.BatchImage.JobLockTTLSeconds) * time.Second,
}
}
func normalizeBatchImageQueueOptions(opts batchImageQueueOptions) batchImageQueueOptions {
if opts.ReadyKey == "" {
opts.ReadyKey = defaultBatchImageReadyKey
}
if opts.DelayedKey == "" {
opts.DelayedKey = defaultBatchImageDelayedKey
}
if opts.ActiveKey == "" {
opts.ActiveKey = defaultBatchImageActiveKey
}
if opts.InflightPrefix == "" {
opts.InflightPrefix = defaultBatchImageInflightPrefix
}
if opts.LockPrefix == "" {
opts.LockPrefix = defaultBatchImageLockPrefix
}
if opts.InflightTTL <= 0 {
opts.InflightTTL = defaultBatchImageInflightTTL
}
if opts.LockTTL <= 0 {
opts.LockTTL = defaultBatchImageJobLockTTL
}
return opts
}
func (q *batchImageQueue) Enqueue(ctx context.Context, batchID string) error {
if !service.IsValidBatchImageID(batchID) {
return service.ErrInvalidBatchImageQueuePayload
}
ok, err := q.rdb.SetNX(ctx, q.inflightKey(batchID), batchID, q.inflightTTL).Result()
if err != nil {
return err
}
if !ok {
return service.ErrBatchImageAlreadyQueued
}
if err := q.rdb.LPush(ctx, q.readyKey, batchID).Err(); err != nil {
_ = q.rdb.Del(ctx, q.inflightKey(batchID)).Err()
return err
}
return nil
}
func (q *batchImageQueue) Reserve(ctx context.Context, blockTimeout time.Duration) (service.ReservedBatchImageJob, error) {
result, err := q.rdb.BRPop(ctx, blockTimeout, q.readyKey).Result()
if errors.Is(err, redis.Nil) {
return service.ReservedBatchImageJob{}, service.ErrBatchImageQueueEmpty
}
if err != nil {
return service.ReservedBatchImageJob{}, err
}
if len(result) != 2 || !service.IsValidBatchImageID(result[1]) {
return service.ReservedBatchImageJob{}, service.ErrInvalidBatchImageQueuePayload
}
batchID := result[1]
if err := q.rdb.ZAdd(ctx, q.activeKey, redis.Z{
Score: float64(time.Now().UnixMilli()),
Member: batchID,
}).Err(); err != nil {
return service.ReservedBatchImageJob{}, err
}
return service.ReservedBatchImageJob{BatchID: batchID}, nil
}
func (q *batchImageQueue) RequeueAfter(ctx context.Context, batchID string, delay time.Duration) error {
if !service.IsValidBatchImageID(batchID) {
return service.ErrInvalidBatchImageQueuePayload
}
pipe := q.rdb.TxPipeline()
pipe.ZRem(ctx, q.activeKey, batchID)
pipe.ZRem(ctx, q.delayedKey, batchID)
if delay <= 0 {
pipe.LPush(ctx, q.readyKey, batchID)
} else {
pipe.ZAdd(ctx, q.delayedKey, redis.Z{
Score: float64(time.Now().Add(delay).UnixMilli()),
Member: batchID,
})
}
_, err := pipe.Exec(ctx)
return err
}
func (q *batchImageQueue) Ack(ctx context.Context, batchID string) error {
if !service.IsValidBatchImageID(batchID) {
return service.ErrInvalidBatchImageQueuePayload
}
pipe := q.rdb.TxPipeline()
pipe.ZRem(ctx, q.activeKey, batchID)
pipe.ZRem(ctx, q.delayedKey, batchID)
pipe.Del(ctx, q.inflightKey(batchID))
_, err := pipe.Exec(ctx)
return err
}
func (q *batchImageQueue) Heartbeat(ctx context.Context, batchID string) error {
if !service.IsValidBatchImageID(batchID) {
return service.ErrInvalidBatchImageQueuePayload
}
return q.rdb.ZAdd(ctx, q.activeKey, redis.Z{
Score: float64(time.Now().UnixMilli()),
Member: batchID,
}).Err()
}
func (q *batchImageQueue) MoveDueDelayedToReady(ctx context.Context, limit int) (int, error) {
if limit <= 0 {
limit = 100
}
return batchImageMoveDueDelayedScript.Run(ctx, q.rdb, []string{q.delayedKey, q.readyKey}, time.Now().UnixMilli(), limit).Int()
}
func (q *batchImageQueue) RecoverStaleActive(ctx context.Context, staleAfter time.Duration, limit int) (int, error) {
if staleAfter <= 0 {
return 0, service.ErrInvalidBatchImageQueuePayload
}
if limit <= 0 {
limit = 100
}
cutoff := time.Now().Add(-staleAfter).UnixMilli()
return batchImageRecoverStaleActiveScript.Run(ctx, q.rdb, []string{q.activeKey, q.readyKey}, cutoff, limit).Int()
}
func (q *batchImageQueue) TryAcquireJobLock(ctx context.Context, batchID string, ttl time.Duration) (service.BatchImageJobLock, bool, error) {
if !service.IsValidBatchImageID(batchID) {
return nil, false, service.ErrInvalidBatchImageQueuePayload
}
if ttl <= 0 {
ttl = q.lockTTL
}
token, err := newBatchImageLockToken()
if err != nil {
return nil, false, err
}
key := q.lockKey(batchID)
ok, err := q.rdb.SetNX(ctx, key, token, ttl).Result()
if err != nil {
return nil, false, err
}
if !ok {
return nil, false, nil
}
return &batchImageRedisJobLock{rdb: q.rdb, key: key, token: token}, true, nil
}
func (q *batchImageQueue) inflightKey(batchID string) string {
return q.inflightPrefix + batchID
}
func (q *batchImageQueue) lockKey(batchID string) string {
return q.lockPrefix + batchID
}
type batchImageRedisJobLock struct {
rdb *redis.Client
key string
token string
}
func (l *batchImageRedisJobLock) Release(ctx context.Context) error {
if l == nil || l.rdb == nil || l.key == "" || l.token == "" {
return nil
}
return batchImageReleaseLockScript.Run(ctx, l.rdb, []string{l.key}, l.token).Err()
}
func newBatchImageLockToken() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
return hex.EncodeToString(b[:]), nil
}
var _ service.BatchImageQueue = (*batchImageQueue)(nil)
@@ -0,0 +1,123 @@
//go:build unit
package repository
import (
"context"
"errors"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/require"
)
func TestBatchImageQueue_DuplicateEnqueueReturnsAlreadyQueued(t *testing.T) {
ctx := context.Background()
queue, _ := newBatchImageQueueTest(t)
batchID := "imgbatch_duplicate"
require.NoError(t, queue.Enqueue(ctx, batchID))
err := queue.Enqueue(ctx, batchID)
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageAlreadyQueued))
}
func TestBatchImageQueue_RequeueAfterMovesJobFromActiveToDelayed(t *testing.T) {
ctx := context.Background()
queue, _ := newBatchImageQueueTest(t)
batchID := "imgbatch_requeue_after"
require.NoError(t, queue.rdb.ZAdd(ctx, queue.activeKey, redis.Z{
Score: float64(time.Now().UnixMilli()),
Member: batchID,
}).Err())
require.NoError(t, queue.RequeueAfter(ctx, batchID, time.Minute))
require.ErrorIs(t, queue.rdb.ZScore(ctx, queue.activeKey, batchID).Err(), redis.Nil)
score, err := queue.rdb.ZScore(ctx, queue.delayedKey, batchID).Result()
require.NoError(t, err)
require.Greater(t, score, float64(time.Now().UnixMilli()))
}
func TestBatchImageQueue_MoveDueDelayedToReadyMovesDueJobs(t *testing.T) {
ctx := context.Background()
queue, _ := newBatchImageQueueTest(t)
dueBatchID := "imgbatch_due"
futureBatchID := "imgbatch_future"
now := time.Now()
require.NoError(t, queue.rdb.ZAdd(ctx, queue.delayedKey,
redis.Z{Score: float64(now.Add(-time.Second).UnixMilli()), Member: dueBatchID},
redis.Z{Score: float64(now.Add(time.Hour).UnixMilli()), Member: futureBatchID},
).Err())
moved, err := queue.MoveDueDelayedToReady(ctx, 10)
require.NoError(t, err)
require.Equal(t, 1, moved)
require.ErrorIs(t, queue.rdb.ZScore(ctx, queue.delayedKey, dueBatchID).Err(), redis.Nil)
require.NoError(t, queue.rdb.ZScore(ctx, queue.delayedKey, futureBatchID).Err())
reserved, err := queue.Reserve(ctx, time.Millisecond)
require.NoError(t, err)
require.Equal(t, dueBatchID, reserved.BatchID)
}
func TestBatchImageQueue_RecoverStaleActiveMovesStaleJobsToReady(t *testing.T) {
ctx := context.Background()
queue, _ := newBatchImageQueueTest(t)
staleBatchID := "imgbatch_stale"
recentBatchID := "imgbatch_recent"
now := time.Now()
require.NoError(t, queue.rdb.ZAdd(ctx, queue.activeKey,
redis.Z{Score: float64(now.Add(-time.Hour).UnixMilli()), Member: staleBatchID},
redis.Z{Score: float64(now.UnixMilli()), Member: recentBatchID},
).Err())
moved, err := queue.RecoverStaleActive(ctx, 10*time.Minute, 10)
require.NoError(t, err)
require.Equal(t, 1, moved)
require.ErrorIs(t, queue.rdb.ZScore(ctx, queue.activeKey, staleBatchID).Err(), redis.Nil)
require.NoError(t, queue.rdb.ZScore(ctx, queue.activeKey, recentBatchID).Err())
reserved, err := queue.Reserve(ctx, time.Millisecond)
require.NoError(t, err)
require.Equal(t, staleBatchID, reserved.BatchID)
}
func TestBatchImageQueue_JobLockReleaseOnlyDeletesMatchingToken(t *testing.T) {
ctx := context.Background()
queue, _ := newBatchImageQueueTest(t)
batchID := "imgbatch_lock"
lock, ok, err := queue.TryAcquireJobLock(ctx, batchID, time.Minute)
require.NoError(t, err)
require.True(t, ok)
require.NoError(t, queue.rdb.Set(ctx, queue.lockKey(batchID), "other-token", time.Minute).Err())
require.NoError(t, lock.Release(ctx))
got, err := queue.rdb.Get(ctx, queue.lockKey(batchID)).Result()
require.NoError(t, err)
require.Equal(t, "other-token", got)
require.NoError(t, queue.rdb.Del(ctx, queue.lockKey(batchID)).Err())
lock, ok, err = queue.TryAcquireJobLock(ctx, batchID, time.Minute)
require.NoError(t, err)
require.True(t, ok)
require.NoError(t, lock.Release(ctx))
require.ErrorIs(t, queue.rdb.Get(ctx, queue.lockKey(batchID)).Err(), redis.Nil)
}
func newBatchImageQueueTest(t *testing.T) (*batchImageQueue, *miniredis.Miniredis) {
t.Helper()
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
t.Cleanup(func() {
_ = rdb.Close()
})
queue := newBatchImageQueueWithOptions(rdb, batchImageQueueOptions{
InflightTTL: time.Hour,
LockTTL: time.Minute,
})
return queue, mr
}
@@ -0,0 +1,941 @@
package repository
import (
"context"
"database/sql"
"encoding/json"
"strconv"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
)
type batchImageSQLExecutor interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type batchImageRepository struct {
db *sql.DB
sql batchImageSQLExecutor
}
func NewBatchImageRepository(db *sql.DB) service.BatchImageRepository {
return &batchImageRepository{db: db, sql: db}
}
func (r *batchImageRepository) CreateBatchImageJob(ctx context.Context, params service.CreateBatchImageJobParams) (*service.BatchImageJob, error) {
if !service.IsSupportedBatchImageProvider(params.Provider) {
return nil, service.ErrBatchImageInvalidProvider
}
if params.BatchID == "" {
batchID, err := service.NewBatchImageID()
if err != nil {
return nil, err
}
params.BatchID = batchID
}
if params.Status == "" {
params.Status = service.BatchImageJobStatusCreated
}
if params.Currency == "" {
params.Currency = "USD"
}
job, err := createBatchImageJobWithSQL(ctx, r.sql, params)
if err != nil {
return nil, translatePersistenceError(err, nil, service.ErrBatchImageJobExists)
}
return job, nil
}
func (r *batchImageRepository) GetBatchImageJobByBatchID(ctx context.Context, batchID string) (*service.BatchImageJob, error) {
job, err := scanBatchImageJob(r.sql.QueryRowContext(ctx, batchImageJobSelectSQL+" WHERE batch_id = $1", batchID))
if err != nil {
return nil, translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
return job, nil
}
func (r *batchImageRepository) GetBatchImageJobByIdempotencyKey(ctx context.Context, userID, apiKeyID int64, key string) (*service.BatchImageJob, error) {
job, err := scanBatchImageJob(r.sql.QueryRowContext(ctx, batchImageJobSelectSQL+`
WHERE user_id = $1 AND api_key_id = $2 AND idempotency_key = $3
ORDER BY id DESC LIMIT 1`, userID, apiKeyID, key))
if err != nil {
return nil, translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
return job, nil
}
func (r *batchImageRepository) GetBatchImageJobByBatchIDForOwner(ctx context.Context, userID, apiKeyID int64, batchID string) (*service.BatchImageJob, error) {
job, err := scanBatchImageJob(r.sql.QueryRowContext(ctx, batchImageJobSelectSQL+`
WHERE batch_id = $1 AND user_id = $2 AND api_key_id = $3 AND user_deleted_at IS NULL`, batchID, userID, apiKeyID))
if err != nil {
return nil, translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
return job, nil
}
func (r *batchImageRepository) ListBatchImageJobsForOwner(ctx context.Context, userID, apiKeyID int64, filter service.BatchImageJobFilter) ([]*service.BatchImageJob, error) {
limit := filter.Limit
if limit <= 0 || limit > 100 {
limit = 20
}
if filter.Offset < 0 {
filter.Offset = 0
}
query := batchImageJobSelectSQL + " WHERE user_id = $1 AND api_key_id = $2"
args := []any{userID, apiKeyID}
if filter.ExcludeDeleted {
query += " AND user_deleted_at IS NULL"
}
if filter.Status != "" {
query += " AND status = $" + strconv.Itoa(len(args)+1)
args = append(args, filter.Status)
}
if filter.TaskNameLike != "" {
query += " AND task_name ILIKE $" + strconv.Itoa(len(args)+1)
args = append(args, "%"+filter.TaskNameLike+"%")
}
if filter.Downloaded != nil {
if *filter.Downloaded {
query += " AND downloaded_at IS NOT NULL"
} else {
query += " AND downloaded_at IS NULL"
}
}
if filter.CreatedAfter != nil {
query += " AND created_at >= $" + strconv.Itoa(len(args)+1)
args = append(args, *filter.CreatedAfter)
}
if filter.CreatedBefore != nil {
query += " AND created_at < $" + strconv.Itoa(len(args)+1)
args = append(args, *filter.CreatedBefore)
}
query += " ORDER BY created_at DESC, id DESC LIMIT $" + strconv.Itoa(len(args)+1) + " OFFSET $" + strconv.Itoa(len(args)+2)
args = append(args, limit, filter.Offset)
rows, err := r.sql.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
return scanBatchImageJobs(rows)
}
func (r *batchImageRepository) GetBatchImageJobByID(ctx context.Context, id int64) (*service.BatchImageJob, error) {
job, err := scanBatchImageJob(r.sql.QueryRowContext(ctx, batchImageJobSelectSQL+" WHERE id = $1", id))
if err != nil {
return nil, translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
return job, nil
}
func (r *batchImageRepository) TransitionBatchImageJobStatus(ctx context.Context, batchID, toStatus string, opts service.BatchImageTransitionOptions) error {
if r.db == nil {
return r.transitionBatchImageJobStatusWithSQL(ctx, r.sql, batchID, toStatus, opts)
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
if err := r.transitionBatchImageJobStatusWithSQL(ctx, tx, batchID, toStatus, opts); err != nil {
return err
}
return tx.Commit()
}
func (r *batchImageRepository) UpdateBatchImageJobProviderOutputRef(ctx context.Context, batchID, providerOutputRef string) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET provider_output_ref = $2, updated_at = $3
WHERE batch_id = $1`, batchID, providerOutputRef, time.Now())
if err != nil {
return translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageJobNotFound
}
return nil
}
func (r *batchImageRepository) UpdateBatchImageJobProviderSubmit(ctx context.Context, params service.UpdateBatchImageJobProviderSubmitParams) error {
if r.db == nil {
return r.updateBatchImageJobProviderSubmitWithSQL(ctx, r.sql, params)
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
if err := r.updateBatchImageJobProviderSubmitWithSQL(ctx, tx, params); err != nil {
return err
}
return tx.Commit()
}
func (r *batchImageRepository) updateBatchImageJobProviderSubmitWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, params service.UpdateBatchImageJobProviderSubmitParams) error {
var current string
if err := sqlq.QueryRowContext(ctx, `SELECT status FROM batch_image_jobs WHERE batch_id = $1 FOR UPDATE`, params.BatchID).Scan(&current); err != nil {
return translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
if !service.CanTransitionBatchImageJob(current, service.BatchImageJobStatusSubmitted) {
return service.ErrBatchImageInvalidTransition
}
now := time.Now()
if _, err := sqlq.ExecContext(ctx, `
UPDATE batch_image_jobs
SET status = 'submitted',
provider_job_name = $2,
provider_input_ref = NULLIF($3, ''),
provider_output_ref = NULLIF($4, ''),
gcs_input_uri = NULLIF($5, ''),
gcs_output_uri = NULLIF($6, ''),
submitted_at = CASE WHEN submitted_at IS NULL THEN $7 ELSE submitted_at END,
updated_at = $7,
version = version + 1
WHERE batch_id = $1`, params.BatchID, params.ProviderJobName, params.ProviderInputRef, params.ProviderOutputRef, params.GCSInputURI, params.GCSOutputURI, now); err != nil {
return err
}
return appendBatchImageEventWithSQL(ctx, sqlq, params.BatchID, "provider_submitted", params.EventPayload)
}
func (r *batchImageRepository) RecordBatchImageJobSubmitFailure(ctx context.Context, batchID, code, message string, markFailed bool) error {
now := time.Now()
statusSQL := "status"
if markFailed {
statusSQL = "'failed'"
}
_, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET status = `+statusSQL+`,
last_error_code = $2,
last_error_message = $3,
finished_at = CASE WHEN `+statusSQL+` = 'failed' AND finished_at IS NULL THEN $4 ELSE finished_at END,
updated_at = $4,
version = version + 1
WHERE batch_id = $1`, batchID, code, message, now)
if err != nil {
return err
}
eventType := "submit_failed"
if !markFailed {
eventType = "queue_failed"
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, eventType, map[string]any{"error_code": code})
}
func (r *batchImageRepository) MarkBatchImageJobSettled(ctx context.Context, params service.MarkBatchImageJobSettledParams) error {
if r.db == nil {
return r.markBatchImageJobSettledWithSQL(ctx, r.sql, params)
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
if err := r.markBatchImageJobSettledWithSQL(ctx, tx, params); err != nil {
return err
}
return tx.Commit()
}
func (r *batchImageRepository) markBatchImageJobSettledWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, params service.MarkBatchImageJobSettledParams) error {
now := time.Now()
if params.Now != nil {
now = *params.Now
}
outputExpiresAt := params.OutputExpiresAt
res, err := sqlq.ExecContext(ctx, `
UPDATE batch_image_jobs
SET status = 'completed',
actual_cost = $2,
manifest_hash = $3,
settled_at = CASE WHEN settled_at IS NULL THEN $4 ELSE settled_at END,
finished_at = CASE WHEN finished_at IS NULL THEN $4 ELSE finished_at END,
output_expires_at = CASE WHEN output_expires_at IS NULL THEN $5 ELSE output_expires_at END,
updated_at = $4,
version = version + 1
WHERE batch_id = $1
AND status = 'settling'
AND (manifest_hash IS NULL OR manifest_hash = '' OR manifest_hash = $3)`, params.BatchID, params.ActualCost, params.ManifestHash, now, outputExpiresAt)
if err != nil {
return err
}
affected, err := res.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
job, getErr := scanBatchImageJob(sqlq.QueryRowContext(ctx, batchImageJobSelectSQL+" WHERE batch_id = $1", params.BatchID))
if getErr != nil {
return translatePersistenceError(getErr, service.ErrBatchImageJobNotFound, nil)
}
if job.Status != service.BatchImageJobStatusSettling {
if job.Status == service.BatchImageJobStatusCompleted {
return service.ErrBatchImageAlreadySettled
}
return service.ErrBatchImageSettlementInvalidStatus
}
return service.ErrBatchImageSettlementManifestConflict
}
return appendBatchImageEventWithSQL(ctx, sqlq, params.BatchID, "settlement_completed", params.EventPayload)
}
func (r *batchImageRepository) SetBatchImageJobSettlementFailed(ctx context.Context, batchID, code, message string) (int, error) {
var retryCount int
err := r.sql.QueryRowContext(ctx, `
UPDATE batch_image_jobs
SET last_error_code = $2,
last_error_message = $3,
retry_count = retry_count + 1,
updated_at = $4
WHERE batch_id = $1
RETURNING retry_count`, batchID, code, message, time.Now()).Scan(&retryCount)
if err != nil {
return 0, translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
return retryCount, appendBatchImageEventWithSQL(ctx, r.sql, batchID, "settlement_failed", map[string]any{
"error_code": code,
})
}
func (r *batchImageRepository) transitionBatchImageJobStatusWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, batchID, toStatus string, opts service.BatchImageTransitionOptions) error {
var current string
if err := sqlq.QueryRowContext(ctx, `SELECT status FROM batch_image_jobs WHERE batch_id = $1 FOR UPDATE`, batchID).Scan(&current); err != nil {
return translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
if !service.CanTransitionBatchImageJob(current, toStatus) {
return service.ErrBatchImageInvalidTransition
}
now := time.Now()
if opts.Now != nil {
now = *opts.Now
}
if _, err := sqlq.ExecContext(ctx, `
UPDATE batch_image_jobs
SET
status = $2::varchar,
version = version + 1,
updated_at = $3,
last_error_code = CASE WHEN $2::varchar = 'failed' THEN $4 ELSE last_error_code END,
last_error_message = CASE WHEN $2::varchar = 'failed' THEN $5 ELSE last_error_message END,
submitted_at = CASE WHEN $2::varchar = 'submitted' AND submitted_at IS NULL THEN $3 ELSE submitted_at END,
started_at = CASE WHEN $2::varchar = 'running' AND started_at IS NULL THEN $3 ELSE started_at END,
finished_at = CASE WHEN $2::varchar IN ('completed', 'failed', 'cancelled') AND finished_at IS NULL THEN $3 ELSE finished_at END,
settled_at = CASE WHEN $2::varchar = 'completed' AND settled_at IS NULL THEN $3 ELSE settled_at END,
output_deleted_at = CASE WHEN $2::varchar = 'output_deleted' AND output_deleted_at IS NULL THEN $3 ELSE output_deleted_at END
WHERE batch_id = $1`, batchID, toStatus, now, opts.ErrorCode, opts.ErrorMessage); err != nil {
return err
}
if opts.EventType != "" {
return appendBatchImageEventWithSQL(ctx, sqlq, batchID, opts.EventType, opts.EventPayload)
}
return nil
}
func (r *batchImageRepository) CreateBatchImageItem(ctx context.Context, params service.CreateBatchImageItemParams) (*service.BatchImageItem, error) {
item, err := createBatchImageItemWithSQL(ctx, r.sql, params)
if err != nil {
return nil, translatePersistenceError(err, nil, service.ErrBatchImageItemExists)
}
return item, nil
}
func (r *batchImageRepository) BulkCreateBatchImageItems(ctx context.Context, params []service.CreateBatchImageItemParams) error {
if len(params) == 0 {
return nil
}
if r.db == nil {
for _, param := range params {
if _, err := createBatchImageItemWithSQL(ctx, r.sql, param); err != nil {
return translatePersistenceError(err, nil, service.ErrBatchImageItemExists)
}
}
return nil
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
for _, param := range params {
if _, err := createBatchImageItemWithSQL(ctx, tx, param); err != nil {
return translatePersistenceError(err, nil, service.ErrBatchImageItemExists)
}
}
return tx.Commit()
}
func (r *batchImageRepository) ReplaceBatchImageItemsForJob(ctx context.Context, batchID string, items []service.CreateBatchImageItemParams, counts service.BatchImageCounts) error {
if r.db == nil {
return r.replaceBatchImageItemsForJobWithSQL(ctx, r.sql, batchID, items, counts)
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
if err := r.replaceBatchImageItemsForJobWithSQL(ctx, tx, batchID, items, counts); err != nil {
return err
}
return tx.Commit()
}
func (r *batchImageRepository) replaceBatchImageItemsForJobWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, batchID string, items []service.CreateBatchImageItemParams, counts service.BatchImageCounts) error {
var id int64
if err := sqlq.QueryRowContext(ctx, `SELECT id FROM batch_image_jobs WHERE batch_id = $1 FOR UPDATE`, batchID).Scan(&id); err != nil {
return translatePersistenceError(err, service.ErrBatchImageJobNotFound, nil)
}
promptPreviews, err := r.batchImageItemPromptPreviews(ctx, sqlq, batchID)
if err != nil {
return err
}
if _, err := sqlq.ExecContext(ctx, `DELETE FROM batch_image_items WHERE job_id = $1`, batchID); err != nil {
return err
}
for _, item := range items {
item.JobID = batchID
if item.PromptPreview == nil {
if preview := promptPreviews[item.CustomID]; preview != "" {
item.PromptPreview = &preview
}
}
if _, err := createBatchImageItemWithSQL(ctx, sqlq, item); err != nil {
return translatePersistenceError(err, nil, service.ErrBatchImageItemExists)
}
}
_, err = sqlq.ExecContext(ctx, `
UPDATE batch_image_jobs
SET success_count = $2,
fail_count = $3,
updated_at = $4
WHERE batch_id = $1`, batchID, counts.SuccessCount, counts.FailCount, time.Now())
return err
}
func (r *batchImageRepository) batchImageItemPromptPreviews(ctx context.Context, sqlq batchImageSQLExecutor, batchID string) (map[string]string, error) {
rows, err := sqlq.QueryContext(ctx, `SELECT custom_id, prompt_preview FROM batch_image_items WHERE job_id = $1 AND prompt_preview IS NOT NULL`, batchID)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
out := make(map[string]string)
for rows.Next() {
var customID string
var preview sql.NullString
if err := rows.Scan(&customID, &preview); err != nil {
return nil, err
}
if preview.Valid && preview.String != "" {
out[customID] = preview.String
}
}
return out, rows.Err()
}
func (r *batchImageRepository) ListBatchImageItems(ctx context.Context, batchID string, filter service.BatchImageItemFilter) ([]*service.BatchImageItem, error) {
limit := filter.Limit
if limit <= 0 || limit > 500 {
limit = 100
}
if filter.Offset < 0 {
filter.Offset = 0
}
query := batchImageItemSelectSQL + " WHERE job_id = $1"
args := []any{batchID}
if filter.Status != "" {
query += " AND status = $2"
args = append(args, filter.Status)
}
query += " ORDER BY id ASC LIMIT $" + strconv.Itoa(len(args)+1) + " OFFSET $" + strconv.Itoa(len(args)+2)
args = append(args, limit, filter.Offset)
rows, err := r.sql.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
var items []*service.BatchImageItem
for rows.Next() {
item, err := scanBatchImageItem(rows)
if err != nil {
return nil, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
func (r *batchImageRepository) ListBatchImageItemsForOwner(ctx context.Context, userID, apiKeyID int64, batchID string, filter service.BatchImageItemFilter) ([]*service.BatchImageItem, error) {
if _, err := r.GetBatchImageJobByBatchIDForOwner(ctx, userID, apiKeyID, batchID); err != nil {
return nil, err
}
return r.ListBatchImageItems(ctx, batchID, filter)
}
func (r *batchImageRepository) GetBatchImageJobForDownload(ctx context.Context, userID, apiKeyID int64, batchID string) (*service.BatchImageJob, error) {
return r.GetBatchImageJobByBatchIDForOwner(ctx, userID, apiKeyID, batchID)
}
func (r *batchImageRepository) GetBatchImageItemForDownload(ctx context.Context, batchID, customID string) (*service.BatchImageItem, error) {
item, err := scanBatchImageItem(r.sql.QueryRowContext(ctx, batchImageItemSelectSQL+`
WHERE job_id = $1 AND custom_id = $2`, batchID, customID))
if err != nil {
return nil, translatePersistenceError(err, service.ErrBatchImageItemNotFound, nil)
}
return item, nil
}
func (r *batchImageRepository) ListBatchImageItemsForDownload(ctx context.Context, batchID string, status string, limit int) ([]*service.BatchImageItem, error) {
return r.ListBatchImageItems(ctx, batchID, service.BatchImageItemFilter{Status: status, Limit: limit})
}
func (r *batchImageRepository) ListBatchImageJobsDueForInputCleanup(ctx context.Context, cutoff time.Time, limit int) ([]*service.BatchImageJob, error) {
if limit <= 0 || limit > 1000 {
limit = 100
}
rows, err := r.sql.QueryContext(ctx, batchImageJobSelectSQL+`
WHERE input_deleted_at IS NULL
AND provider_input_ref IS NOT NULL
AND status IN ('completed', 'failed', 'cancelled', 'output_deleted')
AND COALESCE(finished_at, settled_at, updated_at, created_at) <= $1
ORDER BY id ASC
LIMIT $2`, cutoff, limit)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
return scanBatchImageJobs(rows)
}
func (r *batchImageRepository) ListBatchImageJobsDueForOutputCleanup(ctx context.Context, now time.Time, limit int) ([]*service.BatchImageJob, error) {
if limit <= 0 || limit > 1000 {
limit = 100
}
rows, err := r.sql.QueryContext(ctx, batchImageJobSelectSQL+`
WHERE output_deleted_at IS NULL
AND provider_output_ref IS NOT NULL
AND status = 'completed'
AND output_expires_at IS NOT NULL
AND output_expires_at <= $1
ORDER BY output_expires_at ASC, id ASC
LIMIT $2`, now, limit)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
return scanBatchImageJobs(rows)
}
func (r *batchImageRepository) ListStaleUnsubmittedBatchImageJobs(ctx context.Context, cutoff time.Time, limit int) ([]*service.BatchImageJob, error) {
if limit <= 0 || limit > 1000 {
limit = 100
}
rows, err := r.sql.QueryContext(ctx, batchImageJobSelectSQL+`
WHERE status IN ('created', 'uploading')
AND provider_job_name IS NULL
AND COALESCE(hold_amount, estimated_cost, 0) > 0
AND updated_at <= $1
ORDER BY updated_at ASC, id ASC
LIMIT $2`, cutoff, limit)
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
return scanBatchImageJobs(rows)
}
func (r *batchImageRepository) MarkBatchImageInputDeleted(ctx context.Context, batchID string, deletedAt time.Time) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET input_deleted_at = CASE WHEN input_deleted_at IS NULL THEN $2 ELSE input_deleted_at END,
updated_at = $2,
version = version + 1
WHERE batch_id = $1`, batchID, deletedAt)
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageJobNotFound
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, "input_cleanup_completed", map[string]any{
"batch_id": batchID,
"cleanup_target": "input",
"deleted_at": deletedAt.UTC().Format(time.RFC3339),
})
}
func (r *batchImageRepository) MarkBatchImageOutputDeleted(ctx context.Context, batchID string, deletedAt time.Time) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET status = CASE WHEN status = 'completed' THEN 'output_deleted' ELSE status END,
output_deleted_at = CASE WHEN output_deleted_at IS NULL THEN $2 ELSE output_deleted_at END,
finished_at = CASE WHEN status = 'completed' AND finished_at IS NULL THEN $2 ELSE finished_at END,
updated_at = $2,
version = version + 1
WHERE batch_id = $1`, batchID, deletedAt)
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageJobNotFound
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, "output_cleanup_completed", map[string]any{
"batch_id": batchID,
"cleanup_target": "output",
"deleted_at": deletedAt.UTC().Format(time.RFC3339),
})
}
func (r *batchImageRepository) MarkBatchImageDownloaded(ctx context.Context, batchID string, downloadedAt time.Time) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET downloaded_at = CASE WHEN downloaded_at IS NULL THEN $2 ELSE downloaded_at END,
updated_at = $2
WHERE batch_id = $1`, batchID, downloadedAt)
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageJobNotFound
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, "download_completed", map[string]any{
"batch_id": batchID,
"downloaded_at": downloadedAt.UTC().Format(time.RFC3339),
})
}
func (r *batchImageRepository) MarkBatchImageJobUserDeleted(ctx context.Context, userID, apiKeyID int64, batchID string, deletedAt time.Time) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET user_deleted_at = CASE WHEN user_deleted_at IS NULL THEN $4 ELSE user_deleted_at END,
updated_at = $4
WHERE batch_id = $1
AND user_id = $2
AND api_key_id = $3
AND user_deleted_at IS NULL
AND status IN ('completed', 'failed', 'cancelled', 'output_deleted')`, batchID, userID, apiKeyID, deletedAt)
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageRecordDeleteNotReady
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, "user_record_deleted", map[string]any{
"batch_id": batchID,
"deleted_at": deletedAt.UTC().Format(time.RFC3339),
"user_id": userID,
"api_key_id": apiKeyID,
})
}
func (r *batchImageRepository) SetBatchImageOutputExpiresAt(ctx context.Context, batchID string, expiresAt time.Time) error {
res, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET output_expires_at = CASE WHEN output_expires_at IS NULL THEN $2 ELSE output_expires_at END,
updated_at = $3
WHERE batch_id = $1`, batchID, expiresAt, time.Now())
if err != nil {
return err
}
if affected, err := res.RowsAffected(); err == nil && affected == 0 {
return service.ErrBatchImageJobNotFound
}
return nil
}
func (r *batchImageRepository) RecordBatchImageCleanupFailure(ctx context.Context, batchID, code, message string) error {
_, err := r.sql.ExecContext(ctx, `
UPDATE batch_image_jobs
SET last_error_code = $2,
last_error_message = $3,
retry_count = retry_count + 1,
updated_at = $4
WHERE batch_id = $1`, batchID, code, message, time.Now())
if err != nil {
return err
}
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, "output_cleanup_failed", map[string]any{"error_code": code})
}
func (r *batchImageRepository) AppendBatchImageEvent(ctx context.Context, batchID, eventType string, payload any) error {
return appendBatchImageEventWithSQL(ctx, r.sql, batchID, eventType, payload)
}
func createBatchImageJobWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, params service.CreateBatchImageJobParams) (*service.BatchImageJob, error) {
return scanBatchImageJob(sqlq.QueryRowContext(ctx, `
INSERT INTO batch_image_jobs (
batch_id, user_id, api_key_id, account_id, provider, model, task_name, parent_batch_id, status,
provider_job_name, provider_input_ref, provider_output_ref, gcs_input_uri, gcs_output_uri,
item_count, success_count, fail_count, cancelled_count,
estimated_cost, hold_amount, actual_cost,
base_unit_price, group_rate_multiplier, account_rate_multiplier,
batch_discount_multiplier, hold_multiplier, billable_unit_price, hold_unit_price,
pricing_snapshot_version,
currency, hold_id,
idempotency_key, request_hash, manifest_hash, retry_count, output_expires_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9,
$10, $11, $12, $13, $14,
$15, $16, $17, $18,
$19, $20, $21,
$22, $23, $24,
$25, $26, $27, $28,
$29,
$30, $31,
$32, $33, $34, $35, $36
)
RETURNING `+batchImageJobColumns,
params.BatchID, params.UserID, params.APIKeyID, params.AccountID, params.Provider, params.Model, params.TaskName, params.ParentBatchID, params.Status,
params.ProviderJobName, params.ProviderInputRef, params.ProviderOutputRef, params.GCSInputURI, params.GCSOutputURI,
params.ItemCount, params.SuccessCount, params.FailCount, params.CancelledCount,
params.EstimatedCost, params.HoldAmount, params.ActualCost,
params.BaseUnitPrice, params.GroupRateMultiplier, params.AccountRateMultiplier,
params.BatchDiscountMultiplier, params.HoldMultiplier, params.BillableUnitPrice, params.HoldUnitPrice,
params.PricingSnapshotVersion,
params.Currency, params.HoldID,
params.IdempotencyKey, params.RequestHash, params.ManifestHash, params.RetryCount, params.OutputExpiresAt,
))
}
func createBatchImageItemWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, params service.CreateBatchImageItemParams) (*service.BatchImageItem, error) {
return scanBatchImageItem(sqlq.QueryRowContext(ctx, `
INSERT INTO batch_image_items (
job_id, custom_id, status, request_hash, prompt_preview, provider_source_object,
source_line_number, source_byte_offset, source_byte_length,
mime_type, file_extension, image_count,
error_code, error_message, billed_amount, indexed_at
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9,
$10, $11, $12,
$13, $14, $15, $16
)
RETURNING `+batchImageItemColumns,
params.JobID, params.CustomID, params.Status, params.RequestHash, params.PromptPreview, params.ProviderSourceObject,
params.SourceLineNumber, params.SourceByteOffset, params.SourceByteLength,
params.MimeType, params.FileExtension, params.ImageCount,
params.ErrorCode, params.ErrorMessage, params.BilledAmount, params.IndexedAt,
))
}
func appendBatchImageEventWithSQL(ctx context.Context, sqlq batchImageSQLExecutor, batchID, eventType string, payload any) error {
var payloadArg any
if payload != nil {
payloadBytes, err := json.Marshal(payload)
if err != nil {
return err
}
payloadArg = string(payloadBytes)
}
_, err := sqlq.ExecContext(ctx, `
INSERT INTO batch_image_events (job_id, event_type, payload)
VALUES ($1, $2, $3)`, batchID, eventType, payloadArg)
return err
}
type rowScanner interface {
Scan(dest ...any) error
}
const batchImageJobColumns = `
id, batch_id, user_id, api_key_id, account_id, provider, model, task_name, parent_batch_id, status,
provider_job_name, provider_input_ref, provider_output_ref, gcs_input_uri, gcs_output_uri,
item_count, success_count, fail_count, cancelled_count,
estimated_cost, hold_amount, actual_cost,
base_unit_price, group_rate_multiplier, account_rate_multiplier,
batch_discount_multiplier, hold_multiplier, billable_unit_price, hold_unit_price,
pricing_snapshot_version,
currency, hold_id,
idempotency_key, request_hash, manifest_hash,
retry_count, version, output_expires_at, input_deleted_at, output_deleted_at, downloaded_at, user_deleted_at,
last_error_code, last_error_message,
created_at, updated_at, submitted_at, started_at, finished_at, settled_at`
const batchImageJobSelectSQL = `SELECT ` + batchImageJobColumns + ` FROM batch_image_jobs`
func scanBatchImageJob(row rowScanner) (*service.BatchImageJob, error) {
var job service.BatchImageJob
var apiKeyID, accountID sql.NullInt64
var providerJobName, providerInputRef, providerOutputRef, gcsInputURI, gcsOutputURI sql.NullString
var parentBatchID sql.NullString
var holdAmount, actualCost sql.NullFloat64
var holdID, idempotencyKey, requestHash, manifestHash sql.NullString
var outputExpiresAt, inputDeletedAt, outputDeletedAt, downloadedAt, userDeletedAt sql.NullTime
var lastErrorCode, lastErrorMessage sql.NullString
var submittedAt, startedAt, finishedAt, settledAt sql.NullTime
err := row.Scan(
&job.ID, &job.BatchID, &job.UserID, &apiKeyID, &accountID, &job.Provider, &job.Model, &job.TaskName, &parentBatchID, &job.Status,
&providerJobName, &providerInputRef, &providerOutputRef, &gcsInputURI, &gcsOutputURI,
&job.ItemCount, &job.SuccessCount, &job.FailCount, &job.CancelledCount,
&job.EstimatedCost, &holdAmount, &actualCost,
&job.BaseUnitPrice, &job.GroupRateMultiplier, &job.AccountRateMultiplier,
&job.BatchDiscountMultiplier, &job.HoldMultiplier, &job.BillableUnitPrice, &job.HoldUnitPrice,
&job.PricingSnapshotVersion,
&job.Currency, &holdID,
&idempotencyKey, &requestHash, &manifestHash,
&job.RetryCount, &job.Version, &outputExpiresAt, &inputDeletedAt, &outputDeletedAt, &downloadedAt, &userDeletedAt,
&lastErrorCode, &lastErrorMessage,
&job.CreatedAt, &job.UpdatedAt, &submittedAt, &startedAt, &finishedAt, &settledAt,
)
if err != nil {
return nil, err
}
job.APIKeyID = batchImageNullInt64Ptr(apiKeyID)
job.AccountID = batchImageNullInt64Ptr(accountID)
job.ProviderJobName = batchImageNullStringPtr(providerJobName)
job.ProviderInputRef = batchImageNullStringPtr(providerInputRef)
job.ProviderOutputRef = batchImageNullStringPtr(providerOutputRef)
job.ParentBatchID = batchImageNullStringPtr(parentBatchID)
job.GCSInputURI = batchImageNullStringPtr(gcsInputURI)
job.GCSOutputURI = batchImageNullStringPtr(gcsOutputURI)
job.HoldAmount = batchImageNullFloat64Ptr(holdAmount)
job.ActualCost = batchImageNullFloat64Ptr(actualCost)
job.HoldID = batchImageNullStringPtr(holdID)
job.IdempotencyKey = batchImageNullStringPtr(idempotencyKey)
job.RequestHash = batchImageNullStringPtr(requestHash)
job.ManifestHash = batchImageNullStringPtr(manifestHash)
job.OutputExpiresAt = batchImageNullTimePtr(outputExpiresAt)
job.InputDeletedAt = batchImageNullTimePtr(inputDeletedAt)
job.OutputDeletedAt = batchImageNullTimePtr(outputDeletedAt)
job.DownloadedAt = batchImageNullTimePtr(downloadedAt)
job.UserDeletedAt = batchImageNullTimePtr(userDeletedAt)
job.LastErrorCode = batchImageNullStringPtr(lastErrorCode)
job.LastErrorMessage = batchImageNullStringPtr(lastErrorMessage)
job.SubmittedAt = batchImageNullTimePtr(submittedAt)
job.StartedAt = batchImageNullTimePtr(startedAt)
job.FinishedAt = batchImageNullTimePtr(finishedAt)
job.SettledAt = batchImageNullTimePtr(settledAt)
return &job, nil
}
func scanBatchImageJobs(rows *sql.Rows) ([]*service.BatchImageJob, error) {
var jobs []*service.BatchImageJob
for rows.Next() {
job, err := scanBatchImageJob(rows)
if err != nil {
return nil, err
}
jobs = append(jobs, job)
}
if err := rows.Err(); err != nil {
return nil, err
}
return jobs, nil
}
const batchImageItemColumns = `
id, job_id, custom_id, status, request_hash, prompt_preview, provider_source_object,
source_line_number, source_byte_offset, source_byte_length,
mime_type, file_extension, image_count,
error_code, error_message, billed_amount,
created_at, indexed_at`
const batchImageItemSelectSQL = `SELECT ` + batchImageItemColumns + ` FROM batch_image_items`
func scanBatchImageItem(row rowScanner) (*service.BatchImageItem, error) {
var item service.BatchImageItem
var requestHash, promptPreview, providerSourceObject sql.NullString
var sourceLineNumber sql.NullInt64
var sourceByteOffset, sourceByteLength sql.NullInt64
var mimeType, fileExtension, errorCode, errorMessage sql.NullString
var billedAmount sql.NullFloat64
var indexedAt sql.NullTime
err := row.Scan(
&item.ID, &item.JobID, &item.CustomID, &item.Status, &requestHash, &promptPreview, &providerSourceObject,
&sourceLineNumber, &sourceByteOffset, &sourceByteLength,
&mimeType, &fileExtension, &item.ImageCount,
&errorCode, &errorMessage, &billedAmount,
&item.CreatedAt, &indexedAt,
)
if err != nil {
return nil, err
}
item.RequestHash = batchImageNullStringPtr(requestHash)
item.PromptPreview = batchImageNullStringPtr(promptPreview)
item.ProviderSourceObject = batchImageNullStringPtr(providerSourceObject)
item.SourceLineNumber = batchImageNullIntPtr(sourceLineNumber)
item.SourceByteOffset = batchImageNullInt64Ptr(sourceByteOffset)
item.SourceByteLength = batchImageNullInt64Ptr(sourceByteLength)
item.MimeType = batchImageNullStringPtr(mimeType)
item.FileExtension = batchImageNullStringPtr(fileExtension)
item.ErrorCode = batchImageNullStringPtr(errorCode)
item.ErrorMessage = batchImageNullStringPtr(errorMessage)
item.BilledAmount = batchImageNullFloat64Ptr(billedAmount)
item.IndexedAt = batchImageNullTimePtr(indexedAt)
return &item, nil
}
func batchImageNullStringPtr(v sql.NullString) *string {
if !v.Valid {
return nil
}
return &v.String
}
func batchImageNullInt64Ptr(v sql.NullInt64) *int64 {
if !v.Valid {
return nil
}
return &v.Int64
}
func batchImageNullIntPtr(v sql.NullInt64) *int {
if !v.Valid {
return nil
}
i := int(v.Int64)
return &i
}
func batchImageNullFloat64Ptr(v sql.NullFloat64) *float64 {
if !v.Valid {
return nil
}
return &v.Float64
}
func batchImageNullTimePtr(v sql.NullTime) *time.Time {
if !v.Valid {
return nil
}
return &v.Time
}
var _ service.BatchImageRepository = (*batchImageRepository)(nil)
@@ -0,0 +1,371 @@
//go:build integration
package repository
import (
"context"
"crypto/sha1"
"encoding/hex"
"errors"
"regexp"
"strings"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/stretchr/testify/require"
)
func newBatchImageRepositoryWithSQL(sqlq batchImageSQLExecutor) *batchImageRepository {
return &batchImageRepository{sql: sqlq}
}
func TestBatchImageRepository_CreateJobAndDuplicates(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "create")
job, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
ItemCount: 2,
EstimatedCost: 0.02,
})
require.NoError(t, err)
require.Equal(t, batchID, job.BatchID)
require.Equal(t, service.BatchImageJobStatusCreated, job.Status)
require.Equal(t, "USD", job.Currency)
_, err = repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageJobExists))
}
func TestBatchImageRepository_InvalidProvider(t *testing.T) {
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
_, err := repo.CreateBatchImageJob(context.Background(), service.CreateBatchImageJobParams{
BatchID: batchImageTestID(t, "provider"),
UserID: 1001,
Provider: "unknown",
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageInvalidProvider))
}
func TestBatchImageRepository_TransitionIncrementsVersionAndEvents(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "transition")
now := time.Date(2026, 7, 3, 8, 0, 0, 0, time.UTC)
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderVertex,
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.NoError(t, err)
err = repo.TransitionBatchImageJobStatus(ctx, batchID, service.BatchImageJobStatusUploading, service.BatchImageTransitionOptions{
EventType: "status_changed",
EventPayload: map[string]any{"to": service.BatchImageJobStatusUploading},
Now: &now,
})
require.NoError(t, err)
job, err := repo.GetBatchImageJobByBatchID(ctx, batchID)
require.NoError(t, err)
require.Equal(t, service.BatchImageJobStatusUploading, job.Status)
require.Equal(t, 1, job.Version)
var eventCount int
err = tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM batch_image_events WHERE job_id = $1 AND event_type = 'status_changed'`, batchID).Scan(&eventCount)
require.NoError(t, err)
require.Equal(t, 1, eventCount)
}
func TestBatchImageRepository_InvalidTransition(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "invalid-transition")
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.NoError(t, err)
err = repo.TransitionBatchImageJobStatus(ctx, batchID, service.BatchImageJobStatusRunning, service.BatchImageTransitionOptions{})
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageInvalidTransition))
}
func TestBatchImageRepository_TerminalStatusCannotMoveBack(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "terminal")
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
Status: service.BatchImageJobStatusCompleted,
ItemCount: 1,
})
require.NoError(t, err)
err = repo.TransitionBatchImageJobStatus(ctx, batchID, service.BatchImageJobStatusRunning, service.BatchImageTransitionOptions{})
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageInvalidTransition))
}
func TestBatchImageRepository_ItemCustomIDUniqueness(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
firstBatchID := batchImageTestID(t, "items-a")
secondBatchID := batchImageTestID(t, "items-b")
for _, batchID := range []string{firstBatchID, secondBatchID} {
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.NoError(t, err)
}
_, err := repo.CreateBatchImageItem(ctx, service.CreateBatchImageItemParams{
JobID: firstBatchID,
CustomID: "line-1",
Status: service.BatchImageItemStatusSuccess,
ImageCount: 1,
})
require.NoError(t, err)
_, err = tx.ExecContext(ctx, `SAVEPOINT batch_image_duplicate_item`)
require.NoError(t, err)
_, err = repo.CreateBatchImageItem(ctx, service.CreateBatchImageItemParams{
JobID: firstBatchID,
CustomID: "line-1",
Status: service.BatchImageItemStatusFailed,
})
require.Error(t, err)
require.True(t, errors.Is(err, service.ErrBatchImageItemExists))
_, rollbackErr := tx.ExecContext(ctx, `ROLLBACK TO SAVEPOINT batch_image_duplicate_item`)
require.NoError(t, rollbackErr)
_, err = repo.CreateBatchImageItem(ctx, service.CreateBatchImageItemParams{
JobID: secondBatchID,
CustomID: "line-1",
Status: service.BatchImageItemStatusSuccess,
ImageCount: 1,
})
require.NoError(t, err)
items, err := repo.ListBatchImageItems(ctx, firstBatchID, service.BatchImageItemFilter{})
require.NoError(t, err)
require.Len(t, items, 1)
}
func TestBatchImageRepository_ReplaceBatchImageItemsForJob(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "replace-items")
lineOne := 1
lineTwo := 2
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-2.5-flash-image",
ItemCount: 2,
})
require.NoError(t, err)
err = repo.ReplaceBatchImageItemsForJob(ctx, batchID, []service.CreateBatchImageItemParams{
{CustomID: "old", Status: service.BatchImageItemStatusSuccess, SourceLineNumber: &lineOne, ImageCount: 1},
}, service.BatchImageCounts{SuccessCount: 1})
require.NoError(t, err)
err = repo.ReplaceBatchImageItemsForJob(ctx, batchID, []service.CreateBatchImageItemParams{
{CustomID: "new-ok", Status: service.BatchImageItemStatusSuccess, SourceLineNumber: &lineOne, ImageCount: 1},
{CustomID: "new-fail", Status: service.BatchImageItemStatusFailed, SourceLineNumber: &lineTwo, ErrorCode: batchImageTestStringPtr("SAFETY_BLOCKED")},
}, service.BatchImageCounts{SuccessCount: 1, FailCount: 1})
require.NoError(t, err)
items, err := repo.ListBatchImageItems(ctx, batchID, service.BatchImageItemFilter{})
require.NoError(t, err)
require.Len(t, items, 2)
require.Equal(t, "new-ok", items[0].CustomID)
require.Equal(t, "new-fail", items[1].CustomID)
job, err := repo.GetBatchImageJobByBatchID(ctx, batchID)
require.NoError(t, err)
require.Equal(t, 1, job.SuccessCount)
require.Equal(t, 1, job.FailCount)
}
func TestBatchImageRepository_MarkBatchImageJobSettled(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "settled")
apiKeyID := int64(2001)
accountID := int64(3001)
providerJob := "providers/job"
outputRef := "files/output"
now := time.Date(2026, 7, 4, 10, 0, 0, 0, time.UTC)
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
APIKeyID: &apiKeyID,
AccountID: &accountID,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-image",
Status: service.BatchImageJobStatusSettling,
ProviderJobName: &providerJob,
ProviderOutputRef: &outputRef,
ItemCount: 3,
SuccessCount: 2,
FailCount: 1,
})
require.NoError(t, err)
err = repo.MarkBatchImageJobSettled(ctx, service.MarkBatchImageJobSettledParams{
BatchID: batchID,
ActualCost: 0.5,
ManifestHash: "manifest-hash",
EventPayload: map[string]any{"request_id": "batch_image_settlement:" + batchID},
Now: &now,
})
require.NoError(t, err)
job, err := repo.GetBatchImageJobByBatchID(ctx, batchID)
require.NoError(t, err)
require.Equal(t, service.BatchImageJobStatusCompleted, job.Status)
require.NotNil(t, job.ActualCost)
require.Equal(t, 0.5, *job.ActualCost)
require.Equal(t, "manifest-hash", batchImageDerefTest(job.ManifestHash))
require.NotNil(t, job.SettledAt)
require.Equal(t, now, *job.SettledAt)
var eventCount int
err = tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM batch_image_events WHERE job_id = $1 AND event_type = 'settlement_completed'`, batchID).Scan(&eventCount)
require.NoError(t, err)
require.Equal(t, 1, eventCount)
}
func TestBatchImageRepository_SetBatchImageJobSettlementFailed(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "settlement-failed")
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderGeminiAPI,
Model: "gemini-image",
Status: service.BatchImageJobStatusSettling,
ItemCount: 1,
SuccessCount: 1,
})
require.NoError(t, err)
retryCount, err := repo.SetBatchImageJobSettlementFailed(ctx, batchID, "SETTLEMENT_BILLING_FAILED", "temporary")
require.NoError(t, err)
require.Equal(t, 1, retryCount)
job, err := repo.GetBatchImageJobByBatchID(ctx, batchID)
require.NoError(t, err)
require.Equal(t, service.BatchImageJobStatusSettling, job.Status)
require.Equal(t, "SETTLEMENT_BILLING_FAILED", batchImageDerefTest(job.LastErrorCode))
require.Equal(t, "temporary", batchImageDerefTest(job.LastErrorMessage))
require.Equal(t, 1, job.RetryCount)
}
func TestBatchImageRepository_AppendEvent(t *testing.T) {
ctx := context.Background()
tx := testTx(t)
repo := newBatchImageRepositoryWithSQL(tx)
batchID := batchImageTestID(t, "event")
_, err := repo.CreateBatchImageJob(ctx, service.CreateBatchImageJobParams{
BatchID: batchID,
UserID: 1001,
Provider: service.BatchImageProviderVertex,
Model: "gemini-2.5-flash-image",
ItemCount: 1,
})
require.NoError(t, err)
err = repo.AppendBatchImageEvent(ctx, batchID, "job_created", map[string]any{"batch_id": batchID})
require.NoError(t, err)
var payload string
err = tx.QueryRowContext(ctx, `SELECT payload::text FROM batch_image_events WHERE job_id = $1 AND event_type = 'job_created'`, batchID).Scan(&payload)
require.NoError(t, err)
require.Contains(t, payload, batchID)
}
func batchImageTestID(t *testing.T, prefix string) string {
t.Helper()
safePrefix := batchImageSafeTestIDSegment(prefix, 20)
sum := sha1.Sum([]byte(t.Name()))
return "imgbatch_" + safePrefix + "_" + hex.EncodeToString(sum[:])[:16]
}
func batchImageSafeTestIDSegment(v string, maxLen int) string {
v = strings.ToLower(strings.TrimSpace(v))
v = regexp.MustCompile(`[^a-z0-9_-]+`).ReplaceAllString(v, "-")
v = strings.Trim(v, "-_")
if v == "" {
v = "job"
}
if len(v) > maxLen {
v = v[:maxLen]
v = strings.Trim(v, "-_")
}
if v == "" {
return "job"
}
return v
}
func batchImageTestStringPtr(v string) *string {
return &v
}
func batchImageDerefTest(v *string) string {
if v == nil {
return ""
}
return *v
}

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