Compare commits

...

1372 Commits

Author SHA1 Message Date
Wesley Liddick
70d0569f08 Merge pull request #1668 from tyqy12/main
修复 OpenAI 账号限流回流误判:7d 窗口可用时不因 5h 窗口为 0 回写 429
2026-04-15 16:48:48 +08:00
Wesley Liddick
1db32d692b Merge pull request #1666 from touwaeriol/feat/account-cost-display
feat(usage): add account cost display to admin dashboard and usage pages
2026-04-15 16:43:07 +08:00
Wesley Liddick
8fd29082c0 Merge pull request #1663 from touwaeriol/fix/test-dialog-close-during-stream
fix(ui): allow closing test dialog during active SSE stream
2026-04-15 16:40:40 +08:00
Wesley Liddick
9bf079b725 Merge pull request #1655 from touwaeriol/feat/payment-fee-multiplier
feat(payment): balance recharge multiplier and fee rate
2026-04-15 16:40:14 +08:00
erio
e180dd0710 fix(usage): remove label text from inline account cost, keep orange color 2026-04-15 16:09:58 +08:00
erio
a7dd535d47 fix(usage): show account cost inline under cost column, remove separate column
- Cost cell: change gray "A $xxx" to orange "成本 $xxx" with i18n
- Remove standalone account_cost column from column settings (redundant)
2026-04-15 15:59:51 +08:00
erio
db27e8f000 feat(usage): add account cost to breakdown sub-table and admin usage log
- UserBreakdownItem: add AccountCost field + SQL aggregation
- UserBreakdownSubTable: add orange account cost column
- Admin usage table: add account_cost column (after cost, default visible)
- Column settings: add account_cost toggle option
2026-04-15 15:40:40 +08:00
Wesley Liddick
7451b6f9ae 修复 OpenAI 账号限流回流误判:7d 窗口可用时不因 5h 窗口为 0 回写 429 2026-04-15 15:29:52 +08:00
erio
e0b12b7512 fix(usage): put cost label before value in usage stats card 2026-04-15 15:02:21 +08:00
erio
22680dc602 test(usage): add unit tests for account_cost and fix gofmt
- Fix mock for GetModelStatsWithFilters: add account_cost column
- Add assertion: GetStatsWithFilters always returns TotalAccountCost
- New test: GetModelStatsAccountCostColumn verifies scan of AccountCost
- New test: GetGroupStatsAccountCostColumn verifies scan of AccountCost
- New test: GetStatsWithFiltersAlwaysReturnsAccountCost (no AccountID filter)
- Integration test: add TotalAccountCost/TodayAccountCost assertions
- Fix gofmt alignment in usage_log_types.go
2026-04-15 15:02:21 +08:00
erio
6ade6d30a8 feat(usage): add account cost display to admin dashboard and usage pages
- Add account_cost column to dashboard aggregation tables (migration 107)
- DashboardStats: add TotalAccountCost/TodayAccountCost fields
- ModelStat/GroupStat: add AccountCost field with SQL aggregation
- GetStatsWithFilters: always return TotalAccountCost (remove accountID filter)
- Dashboard Token cards: show user(green)/cost(orange)/standard(gray)
- Usage stats card: show account cost and standard below main value
- Model/Group distribution tables: add orange cost column
2026-04-15 15:02:21 +08:00
erio
38c00872e1 fix(ui): allow closing test dialog during active SSE stream
Replace dead EventSource variable with AbortController to enable
cancelling fetch streams. Remove close-button disable during connecting
status so users can dismiss the dialog at any time.
2026-04-15 11:34:31 +08:00
erio
c2108421c2 fix: gofmt payment_service.go and payment_order.go 2026-04-15 01:50:19 +08:00
erio
342dbd2e19 fix(payment): use original recharge amount in product name, not pay_amount
Product name (e.g. "快代码科技工作室 100 元") should show the user's
original recharge amount (limitAmount), not the fee-inclusive pay amount.
The gateway receives payAmount separately for actual charging.
2026-04-15 01:43:56 +08:00
erio
21f22b5099 fix: remove accidentally staged Antigravity-Manager submodule 2026-04-15 01:39:27 +08:00
erio
60614e6f74 fix: gofmt formatting and update API contract test for new fields
- Fix gofmt alignment in setting_handler.go, settings.go, payment_config_service.go
- Add payment_balance_recharge_multiplier and payment_recharge_fee_rate
  to API contract test expected JSON
2026-04-15 01:39:00 +08:00
erio
3053c56cac fix(payment): show full amount breakdown on payment result page
- Show base amount (充值金额) as first line
- Show fee amount with percentage when fee_rate > 0
- Show pay_amount (实付金额) in bold primary color
- Show credited amount (到账金额) when different from pay_amount
- Compute baseAmount and feeAmount from backend order data
2026-04-15 01:27:25 +08:00
erio
d149dbc91f fix(payment): enhance fee rate input validation and UI
Backend:
- Validate recharge_fee_rate: 0 ≤ rate ≤ 100, max 2 decimal places

Frontend settings:
- Add % suffix icon to fee rate input
- Enforce max=100, min=0, step=0.01 with 2 decimal precision
2026-04-15 01:27:24 +08:00
erio
e761d38fd1 fix(payment): integrate recharge fee rate in order flow and fix UI display
Backend:
- Use cfg.RechargeFeeRate in order creation instead of hardcoded 0
- Remove dead getFeeRate stub method
- All amounts computed server-side: order_amount, pay_amount, fee_rate

Frontend - PaymentView:
- Read recharge_fee_rate from checkout-info API (not per-method)
- Show fee breakdown only when fee_rate > 0
- Show credited amount only when multiplier ≠ 1

Frontend - Order display (user + admin):
- Fix fee_rate * 100 bug (fee_rate is already a percentage)
- OrderTable: show pay_amount as primary, fee/credited as sub-lines
- AdminOrderDetail: full breakdown (base/fee/paid/credited)
- AdminRefundDialog: label "到账金额" for clarity
- PaymentResultView: show pay_amount with fee info

Types + i18n:
- Add recharge_fee_rate to CheckoutInfoResponse
- Add fee_rate to CreateOrderResult
- Add translations: creditedAmount, fee, baseAmount, includedInPayAmount
2026-04-15 01:27:24 +08:00
erio
98140f6cac feat(payment): add recharge fee rate setting and fix provider card UI
- Add recharge_fee_rate system setting (percentage fee on top of recharge amount)
- Full backend chain: config constant, PaymentConfig struct, update validation,
  read/write persistence, DTO, handler GET/PUT responses
- Frontend: settings input with preview, i18n (zh/en), API types
- Fix provider card toggle layout: labels above switches to save width
- Fix Chinese translation: "EasyPay" → "易支付" in provider description
2026-04-15 01:27:24 +08:00
erio
60a4b9316b feat(payment): balance recharge multiplier and refund amount separation
- Add balance_recharge_multiplier system setting (e.g. 1.2 = charge 100 get 120)
- Separate order_amount (credited balance) from pay_amount (actual payment)
- Refund calculates gateway amount proportionally from pay_amount
- Frontend shows both amounts in order details, payment status, refund dialog
- Admin settings UI for configuring recharge multiplier
2026-04-15 01:27:24 +08:00
Wesley Liddick
7c671b5373 Merge pull request #1635 from KnowSky404/fix-issue-1613-version-dropdown
fix(sidebar): prevent version dropdown clipping in expanded brand
2026-04-14 20:41:53 +08:00
Wesley Liddick
d402e722cf Merge pull request #1637 from touwaeriol/feat/websearch-notify-pricing
feat: web search emulation, balance/quota notify, account stats pricing, per-provider refund control, Stripe fix / Web 搜索模拟、余额配额通知、渠道统计计费、按服务商退款控制、Stripe 修复
2026-04-14 20:41:09 +08:00
erio
8548a130c7 fix: Messages() routing refactor and subscription group test coverage
- Refactor OpenAI Messages() routing: pre-compute dispatch model using
  resolveOpenAIMessagesDispatchMappedModel + NormalizeOpenAICompatRequestedModel
  instead of try-fail-retry pattern with gin context passing
- Remove openai_messages_fallback_model context anti-pattern
- Use effectiveMappedModel directly for forward default mapped model
- Add 3 subscription group tests covering all branch paths:
  _Blocked (no active subscription → SUBSCRIPTION_REQUIRED),
  _RequiresRepo (nil repo → SUBSCRIPTION_REPOSITORY_UNAVAILABLE),
  _AllowsActiveSubscription (valid subscription → success)
2026-04-14 20:34:53 +08:00
erio
3d2027227b fix: update wire_gen.go to use ProvideSchedulerCache with config injection
wire_gen.go was calling NewSchedulerCache(redisClient) but wire.go had
been updated to register ProvideSchedulerCache(redisClient, config),
which reads SnapshotMGetChunkSize and SnapshotWriteChunkSize from config.
Without this fix, those config values were silently ignored.
2026-04-14 20:22:45 +08:00
erio
3fa5b8bca5 fix: flaky WebSocket test, usage request queue, and test improvements
- Fix flaky WebSocket passthrough test: allow StatusNormalClosure after
  client close instead of requiring NoError (race condition fix)
- Fix ratelimit 401 test: use PlatformOpenAI instead of PlatformGemini
  for OAuth token cache invalidation scenario (more accurate)
- Add usageLoadQueue: Anthropic OAuth/setup-token accounts sharing the
  same proxy exit are serialized with 1-2s jitter to prevent upstream 429
- AccountUsageCell: add module-level usage cache (5min TTL), unmounted
  safety guard, and integrate enqueueUsageRequest for throttled fetching
2026-04-14 20:13:59 +08:00
erio
5240b44452 refactor(payment): inline payment flow, mobile support, renewal modal
Replace dialog-based payment with inline state flow (select → paying/stripe).
- PaymentStatusPanel replaces QR dialog for scan-to-pay
- StripePaymentInline replaces Stripe popup
- Subscription confirm as inline card instead of modal
- Payment button color follows payment method
- Renewal modal with URL parameter navigation (?tab=subscription&group=123)
- Mobile auto-redirect for H5 payment
- AmountInput uses global min/max instead of per-method
- Tab auto-hides during payment
- Restore CNY (¥) currency for upstream compatibility
2026-04-14 19:45:53 +08:00
erio
a56151fec9 refactor: extract CapacityBadge component from AccountCapacityCell
Extract repeated badge template (SVG icon + current/max display) into
a reusable CapacityBadge component. Reduces AccountCapacityCell from
~300 lines to ~180 lines with identical behavior.
2026-04-14 19:39:22 +08:00
erio
63f539b382 fix: merge general improvements from release branch
Backend:
- gateway_handler: pass subject.UserID instead of int64(0) for user-level routing
- setting_handler: add missing BalanceLowNotifyRechargeURL to UpdateSettings response
- openai_gateway_service: use applyAccountStatsCost for account stats pricing integration
- embed_on: add local file override (data/public/) for embedded frontend assets

Frontend:
- useTableSelection: add batchUpdate method for batch operations
- AccountsView: virtual scrolling params, Set-based isSelected, swipe virtualization
- ProxiesView: add batchUpdate to selection and swipe-select
- BulkEditAccountModal: fix submit handler to prevent event object as argument
- SettingsView: move payload construction outside try block
- i18n: add general translation keys (saved, deleted, view, validation, allowUserRefund)
- api/client: reorder error fields for consistency
- stores/payment: clarify pollOrderStatus JSDoc
2026-04-14 19:29:37 +08:00
erio
c14d739360 fix: resolve 3 code review issues in allow_user_refund
1. PrepareRefund: block refund on provider instance lookup failure
   instead of silently skipping permission check (medium severity)

2. UpdateProviderInstance: allow enabling refund_enabled and
   allow_user_refund in the same request by checking req.RefundEnabled
   value before falling back to DB read

3. ExecuteRefund: only revoke subscription on ErrAdjustWouldExpire,
   abort on other errors (DB failure, not found) instead of
   unconditionally revoking
2026-04-14 18:41:09 +08:00
erio
58677dd53f fix: merge 5 PR-related improvements
- gateway_handler: pass ParsedRequest to RecordUsage + set in gin.Context
- channel_handler: add FeaturesConfig to CRUD (WebSearch channel toggle)
- channel_repo: features_config JSONB persistence (Create/Get/Update/List)
- security_headers: add Stripe CSP domains (script-src + frame-src)
2026-04-14 18:34:57 +08:00
erio
6ac8ccde46 fix: merge 30 general improvements from release branch
Bug fixes:
- Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel)
- Filter soft-deleted users in GetByGroupID
- Stripe CSP policy (allow Stripe.js in script-src and frame-src)
- WebSearch API key validation on save
- RECHARGING status in payment result success check
- Windows test fixes (logger Sync deadlock, config path escaping)

Feature enhancements:
- Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider)
- EasyPay mobile H5 payment (device param + PayURL2)
- SSE error propagation in WebSearch emulation
- AccountStatsCost DTO field for admin usage logs
- Plans sort by sort_order instead of created_at
- UsageMapHook for streaming response usage data
- apicompat Instructions field passthrough
- EffectiveLoadFactor for ops concurrency/metrics
- Usage billing RETURNING balance for notify system
- BulkUpdate mixed channel warning with details
- println to slog migration in auth cache
- Wire ProviderSet cleanup
- CI cache-dependency-path optimization

Frontend:
- Refund eligibility check per provider (canRequestRefund)
- Plan sort_order editing
- Dead code cleanup (simulate_claude_max, client_affinity)
- GroupsView platform switch guard
- channels features_config API type
- UsageView account_stats_cost export
2026-04-14 17:35:27 +08:00
erio
f1297a3694 feat: add per-provider allow_user_refund control and align wildcard matching
allow_user_refund:
- Add allow_user_refund field to PaymentProviderInstance ent schema
- Migration 103: ALTER TABLE payment_provider_instances ADD COLUMN
- Cascade logic: disabling refund_enabled auto-disables allow_user_refund
- User refund validation: check provider instance allows user refund
- Admin refund validation: check provider instance allows admin refund
- Subscription refund: deduct days on refund, rollback on failure
- New endpoint: GET /payment/orders/refund-eligible-providers
- Frontend: ToggleSwitch in ProviderCard/Dialog, cascade in SettingsView

Wildcard matching:
- Change findPricingForModel from "longest prefix wins" to "config order
  priority (first match wins)", aligning with channel service behavior
2026-04-14 16:26:46 +08:00
erio
e8ee400a3f fix: resolve remaining lint errors for upstream CI
- Fix errcheck: brave.go resp.Body.Close, manager_test.go Encode
- Fix gofmt: payment_config_service.go
- Fix unused: use shouldFallbackGeminiModel (with modelName param) in handler
2026-04-14 12:19:44 +08:00
erio
6a08efeef9 fix: resolve upstream CI failures (lint, test, gofmt)
- Fix errcheck: handle Write/Encode return values in brave_test.go
- Fix errcheck: defer resp.Body.Close() with _ assignment in tavily.go
- Fix gofmt: payment.go, channel.go, payment_config_providers.go
- Fix unused: remove dead decodeURLValue in easypay.go
- Restore shouldFallbackGeminiModel function (deleted during cherry-pick)
- Add missing balanceNotifyService param to NewGatewayService in test
- Fix platform default test expectation (empty stays empty)
- Fix wildcard pricing test (longest prefix wins, not config order)
- Fix subscription group test (SUBSCRIPTION_REPOSITORY_UNAVAILABLE)
2026-04-14 12:11:08 +08:00
erio
4aa0070e3d fix: Stripe payment type matching in load balancer
Checkout page aggregates Stripe sub-types (card,link,alipay,wxpay) under
"stripe", but SelectInstance matched against supported_types literally,
which doesn't contain "stripe". Now matches by provider_key for Stripe.
2026-04-14 11:31:44 +08:00
erio
b42f34c359 fix: resolve test compilation errors and restore upstream VERSION
- Add missing interface methods to test stubs (RemoveGroupFromUserAllowedGroups,
  GetNotifyCodeUserRate, IncrNotifyCodeUserRate, UpdateGroupIDByUserAndGroup)
- Fix NewUserService call signatures (add 4th param)
- Fix GetAccountCount return signature (3 values)
- Update api_contract_test.go snapshots for balance_notify fields
- Restore resolveOpenAIMessagesDispatchMappedModel function
- Reset VERSION to upstream 0.1.112
2026-04-14 11:27:32 +08:00
erio
24e16b7f59 fix: restore resolveOpenAIMessagesDispatchMappedModel and reset VERSION
- Restore function deleted during cherry-pick conflict resolution
- Reset VERSION to upstream 0.1.112
2026-04-14 10:58:51 +08:00
erio
d6965b0676 fix: resolve cherry-pick conflicts and restore compilation
- Restore gateway_cache.go to upstream (no lua embeds)
- Restore payment_order.go to upstream (use out_trade_no lookup)
- Restore payment_fulfillment.go to upstream (same reason)
- Add FeaturesConfig field and IsWebSearchEmulationEnabled to Channel
- Add applyAccountStatsCost wrapper function
- Add SettingKeyWebSearchEmulationConfig constant
- Add WebSearchEmulationEnabled to SystemSettings
- Add notify code rate limiting methods to EmailCache interface
- Remove AllowUserRefund references (ent schema not present)
- Fix duplicate import in payment_handler.go
- Fix wire_gen.go argument mismatches
2026-04-14 10:18:39 +08:00
erio
9028d2085f test: add unit tests for billing, websearch, and notify systems
Billing (25 tests):
- CalculateCostUnified: nil resolver fallback, token/per_request/image modes
- GetModelPricingWithChannel: nil/partial/full channel overrides
- resolveAccountStatsCost: four-level priority chain integration tests

WebSearch (18 tests):
- PopulateWebSearchUsage: nil input, manager states, QuotaLimit nil/*int64
- ResetWebSearchUsage: nil manager error
- Manager.ResetUsage: nil Redis
- shouldEmulateWebSearch: full decision chain (8 scenarios)

Notify (36 tests):
- ParseNotifyEmails/MarshalNotifyEmails: old/new format, roundtrip
- crossedDownward: boundary values, threshold semantics
- checkQuotaDimCrossings: mixed dimensions, disabled/zero skip
2026-04-14 09:36:40 +08:00
erio
7c7292935e feat: websearch quota enhancements and balance notify hint
- QuotaLimit changed to *int64 (null=unlimited, >0=limited)
- Add reset-usage endpoint (POST /admin/settings/web-search-emulation/reset-usage)
- Show quota usage in header always (collapsed and expanded)
- Add reset quota button in expanded provider view
- Quota input: empty=unlimited with ∞ placeholder, must be >0 if set
- Add email verification hint on balance notify card
2026-04-14 09:36:40 +08:00
erio
1e6912ea2e fix: gofmt formatting across all Go source files 2026-04-14 09:36:26 +08:00
erio
9e0d12d3b0 fix: show websearch API key visibility/copy buttons for saved providers
The buttons were hidden because v-if only checked provider.api_key,
which is always empty for saved providers (backend sanitizes it).
Now also checks api_key_configured. Copy button is disabled when
no actual key is available (only configured placeholder shown).
2026-04-14 09:35:46 +08:00
erio
b402c367d3 fix: add opportunistic STARTTLS to sendMailPlain for 587 port compatibility
smtp.SendMail automatically upgrades to STARTTLS when the server
supports it. Our replacement sendMailPlain skipped this, causing
credentials to be sent in plaintext on port 587. Add STARTTLS
negotiation before Auth to restore the original security behavior.
2026-04-14 09:35:21 +08:00
erio
0a4ece5f5b fix: audit round-3 — proxy safety, intervals persistence, SMTP timeout, sort fix
- Skip websearch provider when ProxyID is set but proxy not found (prevent
  silent direct connection bypass)
- Fix sortByStableRandomWeight: pair factors with items so sort.Slice swap
  keeps weights aligned
- Allow empty platform in account_stats_pricing_rules (wildcard matching),
  only force anthropic default for main model_pricing
- Add channel_account_stats_pricing_intervals table and repo layer support
  for interval-based pricing in account stats rules
- calculateTokenStatsCost now uses interval pricing when available
- Replace smtp.SendMail/tls.Dial with net.Dialer timeout (10s dial, 20s IO)
  to prevent goroutine leak on SMTP hang
- Fix gofmt formatting issues
- Web Search label: black text with red warning hint
2026-04-14 09:35:20 +08:00
erio
9c09bd19b4 fix: websearch features_config cleanup and pricing rules validation
- Fix web_search_emulation toggle: explicitly write false for disabled
  platforms instead of leaving stale true from cloned features_config
- Extract validatePricingEntries from validateChannelConfig for reuse
- Validate account_stats_pricing_rules[].pricing in both Create and
  Update paths (negative prices, bad intervals, missing per_request price)
2026-04-14 09:35:20 +08:00
erio
a9880ee7b9 fix: round-2 audit fixes — security, code quality, and UI improvements
Security (HIGH):
- Normalize all Redis cache keys to lowercase (verifyCode, passwordReset)
- Fix verify code TTL renewal on failed attempts: use remaining TTL via
  ExpiresAt field instead of resetting to full 15-minute window
- Add 3 missing fields to diffSettings audit log (promo_code, invitation_code,
  custom_endpoints)

Code quality (MEDIUM):
- Extract filterVerifiedEmails shared helper (balance_notify_service.go)
- Add Pricing array non-empty validation for channel pricing rules
- Add platform token semantics comment in gateway_service.go
- Complete validatePlanPatch test coverage (+10 test cases)
- Replace string types with QuotaThresholdType/QuotaResetMode across frontend
- Remove duplicate getPlatformTextColor/getRateBadgeClass in ChannelsView
- Return EMAIL_NOT_FOUND error on RemoveNotifyEmail miss

UI improvements:
- Reorder cost tooltip: user billing above separator, account billing below
- Add NaN guard to accountBilled function
- Move timezone selector inline into reset-mode row (no longer standalone)
2026-04-14 09:35:05 +08:00
erio
74f8a30f86 fix: address audit findings for websearch, email verification, and pricing
- Fix websearch provider failover: proxy error from provider-specific proxy
  now continues to next provider instead of aborting the entire loop
- Fix SMTP failure locking users out: send email first, then write cache
  and increment rate counter
- Fix notify email cache key case sensitivity: normalize to lowercase
- Add OriginalPrice validation to validatePlanPatch and validatePlanRequired
- Add empty scope validation for channel pricing rules (group_ids/account_ids)
- Add platform color to account search dropdown in channel pricing rules
2026-04-14 09:33:53 +08:00
erio
1b7c295199 refactor: M5 useQuotaNotifyState composable + H14 Vue file splits
M5: New composable frontend/src/composables/useQuotaNotifyState.ts
  - Replaces 9 individual refs in both Create/Edit modals with reactive state
  - Provides loadFromExtra/writeToExtra/reset helpers
  - Eliminates ~120 lines of duplicated code across the two modals

H14: Vue file length violations fixed
  - AdminPaymentPlansView.vue: 325 → 183 lines (extracted PlanEditDialog.vue)
  - QuotaLimitCard.vue: 327 → 268 lines (extracted QuotaDimensionRow.vue)
  - PlanEditDialog.vue: 181 lines (new, plan create/edit form)
  - QuotaDimensionRow.vue: 108 lines (new, single quota dimension row)
2026-04-14 09:33:39 +08:00
erio
594f0d17d1 refactor: batch 3 — decompose CheckBalanceAfterDeduction, merge crossing checks, add QuotaNotifyConfig
M1: CheckBalanceAfterDeduction (63→18 lines) decomposed into:
    canNotifyBalance, resolveUserEffectiveThreshold, crossedDownward, dispatchBalanceLowEmail
M3: New Account.QuotaNotifyConfig(dim) method replaces 9 hardcoded getters
    (getters kept as thin wrappers for backward compatibility)
M4: checkQuotaDimCrossings + checkQuotaDimCrossingsFromState merged into one
    function taking pre-built []quotaDim; caller builds dims conditionally
2026-04-14 09:33:00 +08:00
erio
9d319cfa2d fix: batch 2 audit fixes — diffSettings notify fields, slog migration, frontend constants
H5: diffSettings now tracks 5 balance/quota notify fields in audit log
M15: log.Printf audit log migrated to slog.Info, removed "log" import
M14: New frontend/src/constants/account.ts with shared constants
     QuotaNotifyToggle.vue uses QUOTA_THRESHOLD_TYPE_FIXED/PERCENTAGE
L2: UsageTable.vue uses BILLING_MODE_TOKEN/IMAGE from billingMode.ts
2026-04-14 09:32:24 +08:00
erio
ed8a9d975b fix: batch 1 audit fixes — quota SQL fixed mode, public recharge URL, WebSearch bool fallback, UpdatePlan validation
H1: incrementUsageBillingAccountQuota now uses shared dailyExpiredExpr/weeklyExpiredExpr
    constants (supporting fixed reset mode) instead of hardcoded '24 hours'/'168 hours'
H4: public settings endpoint now maps balance_low_notify_recharge_url
H6: GetWebSearchEmulationMode tolerates legacy bool values (true→enabled)
H7: UpdatePlan validates non-nil patch fields (rejects negative price, empty name, etc.)
H8: UsageTable accountBilled() helper with total_cost ?? 0 null guard
H9: AdminUsageLog TS type adds channel_id + billing_tier
M2: account.go "fixed" literals replaced with thresholdTypeFixed constant
M13: SystemSettings TS type adds web_search_emulation_enabled
UI: QuotaLimitCard title labels now use flex-1 to align with flex-1 input boxes
2026-04-14 09:32:11 +08:00
erio
ca673f9899 test: add 66 unit tests for balance/quota notify + plan validation
balance_notify_service_test.go (27 tests):
- resolveBalanceThreshold: fixed/percentage/zero recharged/empty type
- quotaDim.resolvedThreshold: fixed normal/exceed/equal limit, percentage 0/30/100/>100, zero/negative limit
- sanitizeEmailHeader: CRLF/CR/LF/clean/empty/multiple newlines
- buildQuotaDims / buildQuotaDimsFromState: all dimensions, empty extra, state-vs-account precedence
- collectBalanceNotifyRecipients: empty, filter disabled/unverified, case-insensitive dedup, skip empty, trim

balance_notify_check_test.go (16 tests):
- CheckBalanceAfterDeduction guard clauses: nil user/disabled/global-off/threshold=0/user-override/no-crossing
- CheckAccountQuotaAfterIncrement guards: nil account/zero cost/negative cost/global-disabled
- getBalanceNotifyConfig: all fields, disabled, invalid threshold
- isAccountQuotaNotifyEnabled: missing/false/true
- getSiteName: default fallback + configured

balance_notify_email_body_test.go (10 tests):
- Guards against fmt.Sprintf arg-count mismatches in email templates
- Verifies HTML escaping of recharge URL
- Verifies CSS %% escape produces literal % in output
- Verifies unlimited/percentage/over-quota display branches

payment_config_plans_validation_test.go (13 tests):
- validatePlanRequired: all 5 validation branches + whitespace handling
2026-04-14 09:31:45 +08:00
erio
a43da62254 fix(accounts): unify modal width, add notify props to create, fix quota layout
- EditAccountModal width changed from "normal" to "wide" (match CreateAccountModal)
- CreateAccountModal now passes all quota notify props to QuotaLimitCard
- QuotaLimitCard: when global notify disabled, hide title row, input takes full width
- Quota alert email: show remaining quota + threshold (fixed/$, percentage/%) instead of usage trigger point
2026-04-14 09:31:32 +08:00
erio
6e9146e746 fix(notify): add recharge URL to admin settings GET response 2026-04-14 09:31:08 +08:00
erio
f571d8ffad fix(notify): write back auto-filled recharge URL to form on save 2026-04-14 09:31:08 +08:00
erio
48b6c4811f fix(notify): auto-fill recharge URL with current origin when empty 2026-04-14 09:31:08 +08:00
erio
c1eb79e4ba feat(notify): add platform/ID to quota alert email, add recharge URL to balance alert
- Quota alert email now shows account ID and platform
- Balance low email includes a "Top Up Now" button when recharge URL is configured
- New setting: balance_low_notify_recharge_url in admin settings
2026-04-14 09:30:51 +08:00
erio
e27335acdd fix(ui): widen notify type dropdown to show % fully, align quota input widths 2026-04-14 09:30:02 +08:00
erio
216bda58da fix: change quota notify threshold semantics to "remaining quota"
Threshold now represents remaining quota instead of usage amount:
- Fixed ($): threshold=400, limit=1000 → alert when remaining drops to $400
  (i.e., usage reaches $600)
- Percentage (%): threshold=30%, limit=1000 → alert when remaining drops
  to 30% (i.e., usage reaches $700)

Also:
- Rename 告警阈值 → 提醒阈值 in i18n
- Widen type dropdown to w-16 for proper $ / % display
2026-04-14 09:29:25 +08:00
erio
7141dceee2 fix(frontend): place quota notify toggle inline with limit input
Move QuotaNotifyToggle to the same row as the limit $ input for all
three dimensions (daily/weekly/total), significantly reducing card height.
2026-04-14 09:29:01 +08:00
erio
ac55443278 fix(frontend): collapsible quota card and compact notify layout
- QuotaLimitCard: add collapse/expand toggle (chevron icon + click header)
- QuotaNotifyToggle: show $ or % suffix in threshold input
- Reduce vertical spacing between reset mode hint and notify toggle
2026-04-14 09:28:48 +08:00
erio
2066c478ab fix(frontend): quota notify UI improvements
- QuotaNotifyToggle: add $ or % suffix to threshold input based on type
- QuotaLimitCard: combine reset mode and notify toggle on same row
  to reduce vertical height for daily/weekly sections
- Remove redundant ml-4 indentation from QuotaNotifyToggle
2026-04-14 09:28:24 +08:00
erio
98c9d51791 fix: correct account stats pricing priority order
Priority was wrong:
- Before: custom rules → LiteLLM (when ApplyPricingToAccountStats) → nil
- After:  custom rules → totalCost (when ApplyPricingToAccountStats) → LiteLLM → nil

When ApplyPricingToAccountStats is enabled, use the request's actual
client billing cost (before multiplier) as account_stats_cost, instead
of recalculating from LiteLLM per-token prices which produced incorrect
values for per-request billing mode.

LiteLLM model pricing is now the final fallback (priority 3), used only
when neither custom rules nor ApplyPricingToAccountStats apply.
2026-04-14 09:28:11 +08:00
erio
42f8ef3315 fix: add missing AccountQuotaNotifyEnabled to admin settings API
The field was present in SystemSettings response DTO and service layer
but missing from:
- UpdateSettingsRequest (admin handler) - saves were silently ignored
- GET/PUT response mapping in admin handler
- UpdateSettingsRequest (non-admin dto)

This caused the toggle to always revert to off after saving.
2026-04-14 09:27:47 +08:00
erio
245f47cebb fix(frontend): simplify websearch select labels and reduce width
- "默认(跟随渠道)" → "默认", "Default (follow channel)" → "Default"
- Move "follows channel config" info to description text
- Reduce select width from w-32 to w-24 in both Edit and Create modals
2026-04-14 09:27:46 +08:00
erio
48e8efe3e8 fix(frontend): hide quota notify toggle when global setting is disabled
QuotaLimitCard now requires quotaNotifyGlobalEnabled prop to control
visibility of QuotaNotifyToggle components. When the global account
quota notification is disabled in admin settings, per-account threshold
toggles are hidden in both Edit and Create account modals.
2026-04-14 09:27:33 +08:00
knowsky404
58c0f57647 fix(sidebar): prevent version dropdown clipping in expanded brand 2026-04-14 09:27:02 +08:00
erio
b1875f0b82 fix: round 3 audit fixes - SMTP header sanitization and goroutine safety
- Move sanitizeEmailHeader to SendEmailWithConfig entry point, covering all
  email senders (verify code, password reset, ops alerts, notifications)
- Add panic recovery to UpdateBalance goroutine
- Fix stale comment in getAccountQuotaNotifyEmails (email="" no longer used)
- Log error instead of silently discarding verifyNotifyCode cache update failure
2026-04-14 09:26:46 +08:00
erio
b7fb2e4387 fix: audit fixes for websearch, notifications, and channel pricing
P0: fix wildcard matching test assertion (config order, not longest prefix)
P0: add TotalRecharged to auth cache snapshot (v5) for percentage threshold
P1: move pricing rules into per-platform sections in ChannelsView
P1: populate account name cache when editing existing channel rules
P1: sanitize email subject headers to prevent SMTP injection
P1: make Redis INCR+EXPIRE idempotent for rate limiting
P1: deep copy FeaturesConfig in Channel.Clone()
P2: clean up stale email="" placeholder comments
P2: replace log.Printf with slog in email_service.go
2026-04-14 09:26:32 +08:00
erio
a68df457d8 fix: address audit findings across websearch, notify, and channel pricing
Backend fixes:
- Fix balance notify ignoring percentage threshold type (was treating
  percentage value as fixed USD amount)
- Remove dead code parseJSONStringArray
- Add ImageOutputTokens to tryModelFilePricing calculation
- Unify zero-value check: cost == 0 → cost <= 0 in calculateTokenStatsCost
- Use MarshalNotifyEmails instead of json.Marshal for consistency
- Rename quotaDim.oldUsed → currentUsed for clarity
- Extract HTML email templates to const variables (function ≤30 lines)

Test fixes:
- Rewrite account_websearch_test.go for GetWebSearchEmulationMode tri-state
- Add 6 tryModelFilePricing test cases

Frontend fixes:
- Replace hardcoded '未命名' with i18n key
- Extract getBillingModeLabel/getBillingModeBadgeClass to shared utils
- Replace inline type with imported NotifyEmailEntry
- Pass platform to AccountStats pricing rules via inferRulePlatform()
- Add billing mode constants (BILLING_MODE_TOKEN/PER_REQUEST/IMAGE)
2026-04-14 09:26:08 +08:00
erio
1262654d97 feat: WebSearch tri-state, account stats pricing fix, quota cache fix, usage tooltip
WebSearch tri-state switch:
- Account-level web_search_emulation changed from bool to tri-state
  string: "default" (follow channel) / "enabled" / "disabled"
- shouldEmulateWebSearch checks channel config when account is "default"
- SQL migration converts old bool values
- Frontend select replaces toggle in Edit/CreateAccountModal

Account stats pricing:
- resolveAccountStatsCost uses upstream model (post-mapping) for matching
- Priority: custom rules → model pricing file (when toggle on) → default
- Custom rules always configurable, independent of toggle
- Account ID field changed to searchable selector filtered by platform
- Description updated to reflect new behavior

Quota notification cache fix:
- CheckAccountQuotaAfterIncrement fetches real-time account from DB
- Reconstructs pre-increment usage for accurate threshold crossing detection
- New AccountQuotaReader interface (minimal: GetByID only)

Usage tooltip:
- Per-request/image billing shows per-request price instead of $0 token price
- Token billing continues to show input/output price per million tokens
2026-04-14 09:26:08 +08:00
erio
11c4606874 fix(channel): use upstream model for account stats pricing and remove channel pricing fallback
- resolveAccountStatsCost now uses the final upstream model (after
  account-level mapping) to match custom pricing rules, fixing the
  issue where requested model (e.g. claude-sonnet-4-5) didn't match
  rules configured for upstream model (e.g. claude-opus-4-6)
- Remove tryChannelPricing fallback — only custom rules are applied,
  unmatched requests use default formula (total_cost × rate)
- Remove unused billingService and serviceTier parameters
- Update description: "启用后将支持自定义账号统计的模型价格"
2026-04-14 09:26:08 +08:00
erio
95f9b27e70 fix(notify): add verification flow for saved unverified emails
- Add "verify" button next to saved unverified emails in
  ProfileBalanceNotifyCard (send code → enter code → verify)
- Backend: VerifyAndAddNotifyEmail now marks existing unverified
  emails as verified instead of returning "already exists"
- Inline verification UI with countdown timer and resend button
2026-04-14 09:26:08 +08:00
erio
31550a2c6a fix(notify): use real-time balance for crossing detection and simplify email logic
- Fix cached balance causing threshold crossing to never trigger:
  read real-time balance from billingCacheService instead of stale
  API key auth snapshot
- Remove email="" placeholder concept; all emails are user-managed
- Only send notifications to verified && non-disabled emails
- Frontend: pre-fill user's email in add input when list is empty
- Remove FilterEnabledEmails/IsPrimaryDisabled helpers (no longer needed)
2026-04-14 09:26:07 +08:00
erio
915b7a4a56 feat(notify): convert email lists to NotifyEmailEntry struct with toggle support
- Change balance_notify_extra_emails and account_quota_notify_emails
  from []string to []NotifyEmailEntry{email, disabled, verified}
- Add per-email enable/disable toggle for both user and admin notifications
- Add PUT /user/notify-email/toggle API endpoint
- Fix critical bug: API key auth cache snapshot missing balance notify
  fields (Email, Username, BalanceNotifyEnabled, etc.), causing
  notifications to never fire on cached request paths
- Bump cache snapshot version 3→4 to invalidate stale entries
- Add SQL migration 104 to convert old format data
- Backward compatible: parseNotifyEmails auto-detects old/new format
- User balance notify: max 3 emails (primary + 2 extra)
- Admin quota notify: unlimited emails, each with toggle
2026-04-14 09:26:07 +08:00
erio
61aa197b0b fix(notify): add explicit save button for balance threshold
Replace blur-based auto-save with an explicit Save button so users
know when their threshold is persisted. Shows success toast on save.
2026-04-14 09:26:07 +08:00
erio
422807514c fix(notify): add duplicate email check message and improve extra email UX 2026-04-14 09:26:07 +08:00
erio
81287e960c feat(notify): improve balance notify card UX
- Show system default threshold as placeholder in custom threshold input
- Display user's primary email with "Primary" badge
- Support adding multiple pending emails before verification
- Each pending email has independent send/verify/resend flow
- Expose balance_low_notify_threshold in PublicSettings API
- Clean up timers on unmount to prevent leaks
2026-04-14 09:25:50 +08:00
erio
79d154ed73 fix(notify): add balance/quota notify flags to PublicSettings DTO and handler
The service layer correctly populated BalanceLowNotifyEnabled and
AccountQuotaNotifyEnabled in PublicSettings, but the handler-to-DTO
mapping was missing. Users could not see the balance notify card because
the public settings API never returned these flags.
2026-04-14 09:25:50 +08:00
erio
49281bbe45 fix(websearch): hide show/copy buttons when API key is empty
Only show the inline eye/copy buttons when provider.api_key has a value.
When only api_key_configured is true (saved key, not loaded), buttons are
hidden since there's nothing to show/copy.
2026-04-14 09:25:50 +08:00
erio
5df7309979 fix(websearch): add 15s timeout for admin test search 2026-04-14 09:25:49 +08:00
erio
80fa484467 refactor(channels): move account stats pricing rules from basic to platform tabs
- Basic settings now only shows the global toggle
- Custom pricing rules appear inside each platform tab when toggle is on
- Group selector in rules scoped to the current platform's groups
- Remove unused allFormGroupIds computed
2026-04-14 09:25:49 +08:00
erio
4e96a6faec fix: address audit findings for notify, websearch and security
- Fix GetByKeyForAuth missing user.FieldEmail and user.FieldUsername (notifications sent to empty address)
- Guard against empty email in collectBalanceNotifyRecipients
- Remove non-atomic TotalRecharged read-modify-write in admin balance adjustment
- HTML-escape userName/siteName/accountName in notification email templates
- Fix timer leak in ProfileBalanceNotifyCard (add onUnmounted cleanup)
- Add warning log on websearch proxy URL resolution failure
2026-04-14 09:25:49 +08:00
erio
eba289a7ff feat(notify): add global toggles, percentage threshold, and visibility control
- Add global toggle for account quota notification in admin settings
- Add percentage-based threshold type for per-account quota alerts
- Hide balance notify card on user profile when global toggle is off
- Expose balance_low_notify_enabled and account_quota_notify_enabled in PublicSettings
- Add threshold type (fixed/percentage) to QuotaNotifyToggle with $ / % switcher
2026-04-14 09:25:49 +08:00
erio
889b5b4f3b fix(websearch): improve settings UI and hide config when globally disabled
- API Key show/copy buttons moved inside input field (inline icons)
- Proxy selector and test button on same row to save vertical space
- Test opens a dialog modal instead of inline display
- Hide all websearch config in channels/accounts when global toggle is off
2026-04-14 09:25:36 +08:00
erio
cef22c70ab fix(notify): remove percentage threshold from balance notification
Balance low notification only supports fixed USD amount threshold.
Percentage threshold is a quota concept, not applicable to balance.
Reverted threshold_type from admin settings, user profile, and all
backend/frontend layers. DB fields (balance_notify_threshold_type,
total_recharged) retained for potential future quota use.
2026-04-14 09:25:12 +08:00
erio
9e33d0c4c0 fix: address audit findings for websearch and balance notification
- Fix GetByKeyForAuth not selecting balance notify fields (notifications
  never triggered in gateway path)
- Fix provider-level ProxyURL never resolved: inject ProxyRepository into
  SettingService, resolve proxy URLs when building Manager
- Fix admin manual balance adjustment not updating total_recharged
- Add threshold_type input validation (reject invalid values)
- Fix user threshold_type inheritance: custom threshold defaults to "fixed"
  instead of inheriting global type (prevents $5 being treated as 5%)
- Add try-catch for clipboard.writeText (fails on non-HTTPS)
- Add SetTotalRecharged to user Update for admin balance operations
2026-04-14 09:24:58 +08:00
erio
f694afbbf4 feat(notify): add percentage threshold type for balance low notification
- Add threshold_type field (fixed/percentage) to system and user settings
- Add total_recharged field to users table, auto-incremented on balance credit
- Percentage mode: effective threshold = total_recharged × percentage / 100
- User-level threshold_type inherits from system default when not set
- Update admin settings UI with radio selector (fixed amount / percentage)
- Migration: 102_add_balance_notify_threshold_type.sql
2026-04-14 09:24:17 +08:00
erio
d0674e0ff9 feat(websearch): settings UI overhaul and quota improvements
- Remove Priority field, auto load-balance by quota remaining
- Replace QuotaRefreshInterval (daily/weekly/monthly) with SubscribedAt
  (subscription date, monthly lazy refresh via Redis TTL)
- Add collapsible provider cards, API key show/copy, usage progress bar
- Add test endpoint (POST /web-search-emulation/test) bypassing quota
- Wire WebSearchManagerBuilder on startup (was never called before)
- Fix nextMonthlyReset day-of-month overflow (Jan 31 → Feb 28)
- Fix non-deterministic sort in selectByQuotaWeight
- Map ProxyID in builder for provider-level proxy tracking
- Fix frontend timezone drift in subscribed_at date picker
- Fix provider deletion index shift for expandedProviders state
2026-04-14 09:23:40 +08:00
erio
30b926add4 fix(notify): per-recipient timeout and return user on email removal
- Use per-recipient context timeout in sendEmails to prevent later
  recipients from failing due to shared timeout exhaustion
- Return updated user object from RemoveNotifyEmail handler for
  frontend state consistency (matching VerifyNotifyEmail pattern)
2026-04-14 09:23:16 +08:00
erio
c3812ce1e3 fix(notify): address review findings - accountCost formula, dedup, refactor
- Fix accountCost calculation in finalizePostUsageBilling to match
  postUsageBilling (always multiply by AccountRateMultiplier)
- Use strings.EqualFold for email dedup in collectBalanceNotifyRecipients
- Extract CheckAccountQuotaAfterIncrement into smaller functions:
  buildQuotaDims + asyncSendQuotaAlert (< 30 lines each)
- Add "not splittable" comments for HTML template functions
- Extract QuotaNotifyToggle.vue sub-component to reduce
  QuotaLimitCard.vue from 404 to 339 lines
2026-04-14 09:23:16 +08:00
erio
b32d1a2c9f feat(notify): add balance low & account quota notification system
- User balance low notification: email alert when balance drops below
  configurable threshold (user email + verified extra emails)
- Account quota notification: broadcast email to admin-configured
  recipients when daily/weekly/total quota usage exceeds alert threshold
- Admin settings: global enable/disable, default threshold, quota
  notification email list (Email Settings tab)
- User profile: enable/disable, custom threshold, add/remove extra
  notification emails with verification code flow
- Account quota: per-dimension alert toggle and threshold in quota
  control card
- Trigger logic: first-crossing only (old >= threshold && new < threshold
  for balance; old < threshold && new >= threshold for quota), naturally
  prevents duplicate notifications without Redis dedup
2026-04-14 09:23:02 +08:00
erio
60b0fa81ec fix(websearch): improve isProxyError detection and add manager tests
- Add TLS error detection to isProxyError (RecordHeaderError, handshake)
- Case-insensitive error string matching
- Add 19 unit tests for: isProviderAvailable, resolveProxyID,
  isProxyError, isProxyAvailable, selectByQuotaWeight, newHTTPClient
2026-04-14 09:22:26 +08:00
erio
499159870c fix: gofmt websearch manager 2026-04-14 09:22:25 +08:00
erio
fda61b067c feat(websearch): proxy failover, timeout, quota-weighted load balancing
- Use proxyutil.ConfigureTransportProxy for unified proxy protocol support
  (HTTP/HTTPS/SOCKS5/SOCKS5H), replacing ad-hoc HTTP-only proxy code
- Proxy errors return ErrProxyUnavailable → gateway triggers account switch
  via UpstreamFailoverError instead of fallback to direct connection
- Timeout: proxy dial 3s, TLS handshake 3s, data transfer 60s
- Mark proxy unavailable for 5 minutes in Redis on connectivity failure
- Quota-weighted load balancing: providers with quota_limit>0 are selected
  by remaining quota (weighted random); quota_limit=0 providers treated as
  0% weight and placed last
2026-04-14 09:22:25 +08:00
erio
7535e312e0 feat(channels): add custom account stats pricing rules
Allow channels to configure independent model pricing for account
statistics cost calculation, decoupled from user billing.

Backend:
- Migration 101: channels.apply_pricing_to_account_stats toggle,
  channel_account_stats_pricing_rules/model_pricing tables,
  usage_logs.account_stats_cost column
- resolveAccountStatsCost: match rules by group/account, then channel
  pricing, fallback to original formula when unconfigured
- Integrate into both GatewayService.recordUsageCore and
  OpenAIGatewayService.RecordUsage
- Update 8 account stats SQL queries to use
  COALESCE(account_stats_cost, total_cost) * account_rate_multiplier
- 23 unit tests for matching, pricing lookup, and cost calculation

Frontend:
- Channel edit dialog: toggle + custom rules UI with group/account
  multi-select and pricing entry cards
- API types and i18n (zh/en)
2026-04-14 09:22:12 +08:00
erio
7fad9f604f fix(test): add web_search_emulation_enabled to API contract test
The settings API response now includes the new field; update the
expected snapshot in TestAPIContracts to match.
2026-04-14 09:21:28 +08:00
erio
1b53ffcac7 feat(gateway): add web search emulation for Anthropic API Key accounts
Inject web search capability for Claude Console (API Key) accounts that
don't natively support Anthropic's web_search tool. When a pure
web_search request is detected, the gateway calls Brave Search or Tavily
API directly and constructs an Anthropic-protocol-compliant SSE/JSON
response without forwarding to upstream.

Backend:
- New `pkg/websearch/` SDK: Brave and Tavily provider implementations
  with io.LimitReader, proxy support, and Redis-based quota tracking
  (Lua atomic INCR + TTL, DECR rollback on failure)
- Global config via `settings.web_search_emulation_config` (JSON) with
  in-process cache + singleflight, input validation, API key merge on
  save, and sanitized API responses
- Channel-level toggle via `channels.features_config` JSONB column
  (DB migration 101)
- Account-level toggle via `accounts.extra.web_search_emulation`
- Request interception in `Forward()` with SSE streaming response
  construction using json.Marshal (no manual string concatenation)
- Manager hot-reload: `RebuildWebSearchManager()` called on config save
  and startup via `SetWebSearchRedisClient()`
- 70 unit tests covering providers, manager, config validation,
  sanitization, tool detection, query extraction, and response building

Frontend:
- Settings → Gateway tab: Web Search Emulation config card with global
  toggle, provider list (add/remove, API key, priority, quota, proxy)
- Channels → Anthropic tab: web search emulation toggle with global
  state linkage (disabled when global off)
- Account Create/Edit modals: web search emulation toggle for API Key
  type with Toggle component
- Full i18n coverage (zh + en)
2026-04-14 09:20:39 +08:00
erio
c738cfec93 fix(payment): critical audit fixes for security, idempotency and correctness
Backend fixes:
- #1: doSub subscription idempotency via audit log check
- #2: markFailed only when status=RECHARGING (prevents overwriting COMPLETED)
- #3: ExpireTimedOutOrders checks upstream payment before expiring
- #4: Public verify endpoint for payment result page (no auth required)
- #5: EasyPay QueryOrder returns amount, confirmPayment handles zero amount
- #6: WxPay notifyUrl priority: request-first, config-fallback
- #7: EasyPay remove double URL decode in VerifyNotification
- #8: checkPaid/cancelUpstreamPayment use order's provider instance
- #9: Amount NaN/Inf/negative validation in order creation and refund
- #10: Refund amount comparison uses tolerance instead of float64 ==
- #11: Skip balance deduction on retry when previous rollback failed
- #12: checkPaid logs fulfillment errors instead of silently ignoring
- #13: WxPay certSerial added to required config fields

Frontend fixes:
- Payment result page no longer requires authentication
- Public verify API fallback for expired sessions
2026-04-14 09:19:33 +08:00
erio
56e4a9a914 fix: audit fixes - magic strings to constants, frontend any/catch, LB tests
Backend:
- Define OrderTypeBalance/Subscription, EntityStatusActive, DeductionType*,
  NotificationStatus* constants in payment/types.go
- Replace all magic strings in payment_order, payment_fulfillment, payment_refund
- Add local constants in easypay.go (tradeStatusSuccess, signTypeMD5)
- Add 27 unit tests for load balancer (filterByLimits, pickLeastAmount,
  getInstanceChannelLimits, startOfDay)

Frontend:
- Remove all `any` types in SettingsView.vue (18 catch blocks + 1 payload)
- Fix bare catch blocks in PaymentResultView, PaymentView
- Add `unknown` type annotation to all catch blocks

chore: bump version to 0.1.108.140
2026-04-14 09:18:58 +08:00
erio
3c884f8e30 test(payment): add unit tests for payment audit fixes + allow empty supported_types
Tests (1033 new lines, 100% coverage on modified functions):
- amount.go: YuanToFen/FenToYuan with precision edge cases
- wxpay: mapWxState, wxSV, formatPEM, NewWxpay validation
- alipay: isTradeNotExist, NewAlipay validation
- webhook: writeSuccessResponse (wxpay JSON, stripe empty, others text)
- config: validateProviderRequest, isSensitiveConfigField, joinTypes
- fulfillment: resolveRedeemAction idempotency logic

Business logic changes:
- Allow empty supported_types on provider instances
- Block removing payment types when instance has pending orders
- Extract resolveRedeemAction as testable pure function
2026-04-14 09:18:22 +08:00
erio
5bae3b0577 fix(payment): audit fixes for alipay/wxpay/stripe payment providers
Backend:
- Extract YuanToFen/FenToYuan to payment/amount.go using shopspring/decimal
- Require alipay publicKey in config validation
- Fix wxpay webhook response to return JSON per V3 spec
- Remove wxpay certSerial fallback to publicKeyId
- Define magic strings as named constants in wxpay/alipay providers
- Add slog warning for wxpay H5→Native payment downgrade
- Make EncryptionKey validation return error on invalid (non-empty) key
- Make decryptConfig propagate errors instead of returning nil
- Add idempotency check in doBalance to prevent stuck FAILED retries

Frontend:
- Fix dashboard currency symbol from $ to ¥
- Fix AdminPaymentPlansView any type to proper SubscriptionPlan type
- Make quick amount buttons follow selected payment method limits
- Center help image with larger height and text below
2026-04-14 09:17:06 +08:00
erio
1c63ea1448 fix(channel): add missing features column to List query
The paginated List query was selecting 9 columns but scanning 10 fields,
missing c.features. GetByID and ListAll already included it correctly.
2026-04-14 09:16:13 +08:00
erio
3d4d960d60 fix: gofmt formatting after merge 2026-04-14 09:15:49 +08:00
erio
794e817208 refactor: remove PaymentChannel, reuse upstream Channel with features field
- Delete payment_channels table and PaymentChannel Ent schema
- Add `features` column to upstream channels table (migration 095)
- Add Features field to Channel struct, input types, handler request/response
- Payment user/admin handlers now use ChannelService directly
- Remove Channel CRUD from PaymentConfigService and admin payment routes
- Remove "渠道管理" tab from admin orders page (use /admin/channels)
2026-04-14 09:15:29 +08:00
erio
37c23eccfe fix: gofmt formatting 2026-04-14 09:14:29 +08:00
erio
e374874125 feat(channel): improve cache strategy and add restriction logging
- Change channel cache TTL from 60s to 10min (reduce unnecessary DB queries)
- Actively rebuild cache after CRUD instead of lazy invalidation
- Add slog.Warn logging for channel pricing restriction blocks (4 places)
2026-04-14 09:13:53 +08:00
erio
160903fce7 fix: address review findings for channel restriction refactoring
- Fix 7 stale comments still mentioning "限制检查" in handlers/services
- Make billingModelForRestriction explicitly list channel_mapped case
- Add slog.Warn for error swallowing in ResolveChannelMapping and
  needsUpstreamChannelRestrictionCheck
- Document sticky session upstream check exemption
2026-04-14 09:12:42 +08:00
erio
2dce4306b4 refactor: move channel model restriction from handler to scheduling phase
Move the model pricing restriction check from 8 handler entry points
to the account scheduling phase (SelectAccountForModelWithExclusions /
SelectAccountWithLoadAwareness), aligning restriction with billing:

- requested: check original request model against pricing list
- channel_mapped: check channel-mapped model against pricing list
- upstream: per-account check using account-mapped model

Handler layer now only resolves channel mapping (no restriction).
Scheduling layer performs pre-check for requested/channel_mapped,
and per-account filtering for upstream billing source.
2026-04-14 09:12:29 +08:00
erio
3de7713017 fix(channel): splice替换model_pricing条目 + 增强调试日志 2026-04-14 09:08:58 +08:00
erio
1cd033e521 style: apply gofmt formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 09:08:00 +08:00
github-actions[bot]
e534e9bae8 chore: sync VERSION to 0.1.112 [skip ci] 2026-04-13 15:24:14 +00:00
shaw
f9f57e9505 fix(migrations): add 097 to restore settings.updated_at default
Legacy instances created the settings table via ent auto-migration,
which emits Go-level defaults only. Migration 005 uses CREATE TABLE
IF NOT EXISTS, so the missing SQL DEFAULT was never backfilled. This
caused 098's raw INSERT to fail with a NOT NULL violation on
updated_at. The new migration is idempotent and safe for fresh
installs (no-op) and historical instances (backfills the default).
2026-04-13 23:09:26 +08:00
shaw
92f4a6bb94 chore: update readme 2026-04-13 22:28:44 +08:00
Wesley Liddick
66bea2b5ed Merge pull request #1624 from KnowSky404/fix-issue-1613-version-dropdown
fix(sidebar): prevent version update dropdown clipping
2026-04-13 22:03:02 +08:00
Wesley Liddick
ad6c328135 Merge pull request #1575 from shuanbao0/fix/cursor-responses-body-compat
fix(gateway): 兼容 Cursor /v1/chat/completions 的 Responses API body
2026-04-13 22:02:44 +08:00
Wesley Liddick
d949acb1f2 Merge pull request #1603 from Zqysl/qingyu/fix-datatable-mobile-double-render
fix(frontend): reduce account usage request fan-out on pagination
2026-04-13 21:48:00 +08:00
Wesley Liddick
759088002d Merge pull request #1612 from touwaeriol/fix/qrcode-density
fix(frontend): lower QR code error correction level to reduce density
2026-04-13 21:44:38 +08:00
Wesley Liddick
7d80b5ad28 Merge pull request #1610 from touwaeriol/fix/alipay-wxpay-type-mapping
fix(payment): register Alipay/Wxpay providers for base payment types
2026-04-13 21:44:19 +08:00
Wesley Liddick
e70812f03f Merge pull request #1623 from sakurawztlt/fix/anthropic-buffered-empty-output
fix: anthropic 非流式路径丢失 delta 内容(镜像 b2e379cf)
2026-04-13 21:11:48 +08:00
knowsky404
b9b52e74c6 fix(sidebar): prevent version dropdown clipping 2026-04-13 19:24:33 +08:00
sakurawztlt
a1e299a355 fix: Anthropic 非流式路径在上游终态事件 output 为空时从 delta 事件重建响应内容
b2e379cf 引入的 BufferedResponseAccumulator 已修复了 chat_completions
非流式路径和 responses OAuth 非流式路径,但遗漏了 Anthropic /v1/messages
非流式路径 (handleAnthropicBufferedStreamingResponse)。

当客户端请求 stream=false 且模型开启思考时,上游 response.completed
终态事件的 output 字段可能为空,实际 message 内容通过
response.output_text.delta 增量事件下发。旧代码只读终态事件的 Response,
导致客户端收到的 content 字段为空 ([{"type":"text"}])。

本 commit 将 b2e379cf 的相同修复模式镜像到 Anthropic 路径:在 SSE 扫描
过程中用 BufferedResponseAccumulator 累积 delta 内容,终态 output 为空
时通过 SupplementResponseOutput 补充重建。

同时修复 handleAnthropicBufferedStreamingResponse 遗漏 response.done
事件类型的问题,与 chat completions 路径保持一致,避免上游发送
response.done 时 handler 认不出终态事件、最终返回 502 的潜在问题。

BufferedResponseAccumulator 已在 chatcompletions_responses_test.go 有
完整单元测试覆盖(TextOnly/ToolCalls/Reasoning/Mixed/SupplementEmpty/
NoSupplementWhenOutputExists/EmptyDeltas/IgnoresNonFunctionCallItems),
本次复用相同累加器无需新增测试。
2026-04-13 18:51:49 +08:00
erio
24f0eebcf7 fix(frontend): lower QR code error correction level to reduce density
Closes #1607
2026-04-13 14:07:12 +08:00
erio
f498eb8fde fix(payment): fix Alipay/Wxpay direct provider type mapping and enable cross-provider load balancing
Two issues fixed:

1. Alipay.SupportedTypes() returned ["alipay_direct"] and Wxpay returned
   ["wxpay_direct"], but the frontend sends payment_type="alipay"/"wxpay".
   The registry lookup failed with "payment method (alipay) is not
   configured". Fix: return the base types ["alipay"]/["wxpay"].

2. When multiple providers support the same payment type (e.g. EasyPay
   and Alipay direct both handle "alipay"), only the last-registered
   provider's instances were reachable — the registry mapped one type to
   one provider key, and SelectInstance queried by that single key.

   Fix: bypass the registry in invokeProvider and let SelectInstance
   query across all providers when providerKey is empty. The selected
   instance's own ProviderKey (now included in InstanceSelection) is
   used to create the correct provider, enabling true cross-provider
   load balancing.

Closes #1592
2026-04-13 14:07:12 +08:00
qingyuzhang
abe4267553 fix(frontend): lazy load mobile account usage cells 2026-04-13 07:34:07 +08:00
qingyuzhang
3a11348119 fix(frontend): avoid mounting hidden mobile table 2026-04-13 06:55:57 +08:00
github-actions[bot]
ad64190bec chore: sync VERSION to 0.1.111 [skip ci] 2026-04-12 10:17:16 +00:00
shaw
9648c4323f fix(frontend): resolve TS2352 type assertion error in API client 2026-04-12 18:06:40 +08:00
shaw
a1a283688c chore: update sponsors 2026-04-12 17:19:44 +08:00
Wesley Liddick
82b840c16e Merge pull request #1587 from tkpdx01/fix/anthropic-credit-balance-400
fix: handle Anthropic credit balance exhausted (400) as account error
2026-04-12 16:42:02 +08:00
Wesley Liddick
16126a2c9c Merge pull request #1545 from Zqysl/qingyu/fix-smooth-sidebar-collapse
fix(sidebar): smooth sidebar collapse behavior
2026-04-12 16:36:00 +08:00
Wesley Liddick
9b7b3755fe Merge pull request #1543 from IanShaw027/fix/messages-dispatch-i18n
fix(frontend): 补全 messages 调度国际化文案
2026-04-12 16:35:34 +08:00
Wesley Liddick
54490cf65e Merge pull request #1576 from touwaeriol/feat/payment-docs
docs(payment): add built-in payment configuration guide and settings links
2026-04-12 16:34:53 +08:00
bot
cb016ad861 fix: handle Anthropic credit balance exhausted (400) as account error
When an Anthropic API key's credit balance is depleted, the upstream
returns HTTP 400 with message containing "credit balance". Previously,
the 400 handler only checked for "organization has been disabled",
so credit-exhausted accounts kept being scheduled — every request
returned the same error.

Treat this case identically to 402 (Payment Required): call
handleAuthError → SetError to stop scheduling the account until
an admin manually recovers it after topping up credits.

Closes #1586
2026-04-12 13:30:15 +08:00
qingyuzhang
c520de11de Merge branch 'main' of github.com:Wei-Shaw/sub2api into qingyu/fix-smooth-sidebar-collapse
# Conflicts:
#	frontend/src/components/layout/AppSidebar.vue
2026-04-12 06:04:34 +08:00
shuanbao0
422e25c99f fix(gateway): 剥离 Cursor raw body 透传路径中 Codex 不支持的 Responses API 参数
在前一个 commit 的 isResponsesShape 短路路径基础上,补充对 Cursor 云端
带过来的、Codex 上游统一不支持的顶层 Responses API 参数的剥离:

  - prompt_cache_retention
  - safety_identifier
  - metadata
  - stream_options

根因补充:这条 raw-body 透传路径为了保留 Cursor 的 input 数组整体结构,
不再经过 ChatCompletionsRequest 的反序列化过滤,所以这些 Go 结构体里
没有对应字段的参数会被原样发到上游,上游返回:
    Unsupported parameter: <field>
常规 Chat Completions 转换路径天然通过 ChatCompletionsRequest 丢弃未知字段,
不受影响;此处仅在 isResponsesShape 分支内用 sjson.DeleteBytes 显式过滤,
作用域最小。剥离列表与 openai_gateway_service.go:2034 的
unsupportedFields 语义对齐。

另外在 applyCodexOAuthTransform 的 OAuth 兜底 strip 列表里同步追加
prompt_cache_retention,作为对该函数所有其他 OAuth 调用点的 defense
in depth(当前只有 Cursor 路径的短路已在前面剥过,但保留这一层更稳)。

测试:
- TestCursorMixedShape_StripsUnsupportedFields — 验证所有 4 个字段都被剥
- TestApplyCodexOAuthTransform_StripsPromptCacheRetention — OAuth 兜底路径

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:48:45 +08:00
erio
1def627443 feat(settings): add EasyPay provider recommendation link below enabled providers 2026-04-11 21:13:46 +08:00
erio
64cee0b633 feat(settings): add payment config guide link in payment settings header 2026-04-11 21:13:28 +08:00
erio
24f95767b4 docs: align English ZPay referral description with Chinese version 2026-04-11 21:13:08 +08:00
erio
ed001d94da docs: clarify ZPay referral code belongs to Sub2ApiPay author @touwaeriol 2026-04-11 21:13:07 +08:00
erio
f3e8e5e057 docs: show ZPay URL explicitly in parentheses 2026-04-11 21:13:07 +08:00
erio
3b0dc92952 docs: move ZPay recommendation to overview section 2026-04-11 21:12:47 +08:00
erio
1a686239ed fix(docs): correct webhook paths and provider config in payment guide
- Fix webhook URLs: /payment/webhook/<provider> not /payment/<provider>/notify
- Remove notifyUrl/returnUrl from provider config tables (auto-generated by UI)
- Adjust wxpay publicKeyId/certSerial to match frontend optional marking
2026-04-11 21:11:19 +08:00
erio
c777fe5471 docs: add built-in payment configuration guide and update README
- Add docs/PAYMENT.md and docs/PAYMENT_CN.md with full payment setup guide
- Mark Sub2ApiPay as deprecated in ecosystem tables (payment is now built-in)
- Add built-in payment system to features list in all 3 READMEs
2026-04-11 21:10:55 +08:00
shuanbao0
b7edc3ed82 fix(gateway): 兼容 Cursor /v1/chat/completions 的 Responses API body
Cursor 云端 (User-Agent: Go-http-client/2.0) 发往 /v1/chat/completions 的
body 使用 Responses API 格式:
    {"model":"gpt-5.4","input":[{"role":"system","content":"..."}],"stream":true}

原代码用 ChatCompletionsRequest 反序列化,该结构体没有 Input 字段,
Cursor 的 input 数组被静默丢弃,ChatCompletionsToResponses 转换后产出
input: null,Codex 上游以 "Invalid type for 'input': expected a string,
but got an object" 拒绝请求(上游 typeof null === 'object')。

修复:在 ForwardAsChatCompletions 里用 gjson 检测 body shape,当 input
存在且 messages 缺失时,跳过 Chat→Responses 转换,用 sjson 仅改写 model
字段后原样透传 body。billing 所需的 ServiceTier 和 Reasoning.Effort 通过
gjson 从 raw body 提取,下游 codex OAuth transform 路径保持不变。

测试:新增 openai_cursor_warmup_pipeline_test.go,覆盖 5 个 shape 检测
用例(正向/标准请求不误伤/两字段共存/空 body/JSON 回读)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:22:18 +08:00
Wesley Liddick
97f14b7a08 Merge pull request #1572 from touwaeriol/feat/payment-system-v2
feat(payment): add complete payment system with multi-provider support
2026-04-11 19:07:31 +08:00
erio
6793503ed0 chore: remove sora_client_enabled residuals from frontend types and store 2026-04-11 19:02:25 +08:00
erio
fa833f7684 Merge remote-tracking branch 'upstream/main' into feat/payment-system-v2
# Conflicts:
#	frontend/src/api/admin/settings.ts
#	frontend/src/stores/app.ts
#	frontend/src/types/index.ts
#	frontend/src/views/admin/SettingsView.vue
2026-04-11 18:25:06 +08:00
erio
d67ecf893d chore: remove all sora dead code and fork-specific sora_client_enabled
Upstream removed sora feature (090_drop_sora.sql) but left i18n keys
and wire.go references. Clean up:
- Remove entire sora i18n block from en.ts and zh.ts (~190 lines)
- Remove sora nav key and unused 'data' settings tab key
- Remove sora_client_enabled from settings (fork-specific)
- Remove SoraMediaCleanupService from wire.go
2026-04-11 18:15:24 +08:00
erio
faee59ee15 fix(payment): propagate reason/metadata in API error responses
The API client's error interceptor was dropping the reason and metadata
fields from backend error responses. This caused PaymentView to miss
specific error codes (TOO_MANY_PENDING, CANCEL_RATE_LIMITED) and fall
back to generic error messages.
2026-04-11 17:51:06 +08:00
erio
217b7ea68c fix(deps): upgrade axios to 1.15.0 to fix GHSA-fvcv-3m26-pcqx 2026-04-11 16:46:46 +08:00
erio
a020fc52c9 fix(payment): pass expires_at for Stripe countdown timer
Stripe payment path was setting expiresAt to empty string, causing
PaymentStatusPanel to fall back to hardcoded 30-minute default when
the popup redirect switches to the waiting view.
2026-04-11 16:30:34 +08:00
Wesley Liddick
1ef3782dd4 Merge pull request #1538 from IanShaw027/fix/bug-cleanup-main
fix: 修复多个 UI 和功能问题 - 表格排序搜索、导出逻辑、分页配置和状态筛选
2026-04-11 16:29:55 +08:00
erio
7515590324 feat(payment): add H5/mobile payment support
Backend:
- Parse EasyPay `payurl2` field, prefer H5 link on mobile
- Add `device=mobile` to EasyPay submit.php (popup) mode
- Expand isMobile detection keywords (add ipad/ipod)

Frontend:
- Add `isMobileDevice()` utility (userAgentData + UA regex)
- Mobile + pay_url: direct redirect instead of QR/popup
- Popup blocked fallback: auto-redirect when window.open fails
- Stripe WeChat Pay: dynamic client param (mobile_web vs web)
2026-04-11 13:16:35 +08:00
erio
e3a000e0d4 refactor(payment): code standards fixes and regression repairs
Backend:
- Split payment_order.go (546→314 lines) into payment_order_lifecycle.go
- Replace magic strings with constants in factory, easypay, webhook handler
- Add rate limit/validity unit constants in payment_order_lifecycle, payment_service
- Fix critical regression: add PaymentEnabled to GetPublicSettings response
- Add missing migration 099_fix_migrated_purchase_menu_label_icon.sql

Frontend:
- Fix StripePopupView.vue: replace `as any` with typed interface, use extractApiErrorMessage
- Fix AdminOrderTable.vue: replace hardcoded column labels with i18n t() calls
- Fix SubscriptionsView.vue: replace hardcoded Today/Tomorrow with i18n
- Extract duplicate statusBadgeClass/canRefund/formatOrderDateTime to orderUtils.ts
- Add missing i18n keys: common.today, common.tomorrow, payment.orders.orderType/actions
- Remove dead PurchaseSubscriptionView.vue (replaced by PaymentView)
2026-04-11 13:16:35 +08:00
erio
27cd2f8e96 fix(payment): remove purchase_subscription fields replaced by payment system
The built-in payment system replaces the old external purchase subscription
iframe approach. Remove purchase_subscription_enabled/url from admin settings
interface and form defaults, as the Payment tab now handles this functionality.
Kept in stores/app.ts fallback to match backend DTO response structure.
2026-04-11 13:16:35 +08:00
erio
e1547d7835 fix(payment): resolve PR audit issues
- Add payment navigation to AppSidebar (user orders + admin payment menu group with collapse)
- Add 5 missing nav i18n keys (myOrders, orderManagement, paymentDashboard, paymentConfig, paymentPlans)
- Renumber payment migrations 090-100 → 092-102 to avoid conflict with upstream 090/091
- Remove non-payment sora_client_enabled change, restore upstream purchase_subscription fields
- Remove extra 'data' from SettingsTab type union
2026-04-11 13:16:35 +08:00
erio
63d1860dc0 feat(payment): add complete payment system with multi-provider support
Add a full payment and subscription system supporting EasyPay (Alipay/WeChat),
Stripe, and direct Alipay/WeChat Pay providers with multi-instance load balancing.
2026-04-11 13:16:35 +08:00
IanShaw027
f480e57344 fix: align table defaults and preserve sidebar svg colors 2026-04-10 18:27:53 +08:00
IanShaw027
7dc7ff22d2 fix: preserve messages dispatch config in repository hydration 2026-04-10 18:13:18 +08:00
IanShaw027
67a05dfccd fix: honor table defaults and preserve dispatch mappings 2026-04-10 17:55:37 +08:00
IanShaw027
b6bc042302 fix(frontend): 升级 axios 修复审计高危漏洞 2026-04-10 09:28:32 +08:00
IanShaw027
1312405966 fix(settings): 补齐公开设置中的表格分页字段返回 2026-04-10 09:18:48 +08:00
qingyuzhang
07d2add674 fix(sidebar): smooth collapse transitions 2026-04-10 05:16:20 +08:00
IanShaw027
57d0f9794f fix(frontend): 补全 messages 调度国际化文案 2026-04-10 00:56:23 +08:00
IanShaw027
269c7a065c fix(test): restore integration expectations 2026-04-09 22:08:42 +08:00
IanShaw027
b6946e78a2 fix(lint): restore repository sort helpers 2026-04-09 21:49:10 +08:00
IanShaw027
2b70d1d332 merge upstream main into fix/bug-cleanup-main 2026-04-09 21:35:48 +08:00
IanShaw027
b37afd68ec fix(lint): format setting service 2026-04-09 21:31:48 +08:00
Wesley Liddick
00c08c574e Merge pull request #1539 from warksu/fix/loadfactor-not-synced-to-scheduler-cache
fix: 同步 LoadFactor 到调度快照缓存
2026-04-09 21:15:40 +08:00
Wesley Liddick
bbc79796dc Merge pull request #1529 from IanShaw027/feat/group-messages-dispatch-redo
feat: 为openai分组增加messages调度模型映射并支持instructions模板注入
2026-04-09 21:14:38 +08:00
Wesley Liddick
760cc7d6be Merge pull request #1481 from alfadb/fix/increase-error-log-body-limit
fix(ops): 将错误日志请求体存储限制从 10KB 提升至 256KB
2026-04-09 21:14:13 +08:00
Wesley Liddick
9a72025afb Merge pull request #1523 from octo-patch/fix/issue-1519-home-content-csp-frame-src
fix: include home_content URL in CSP frame-src origins
2026-04-09 21:13:46 +08:00
Wesley Liddick
74302f60ab Merge pull request #1010 from Glorhop/pr/oidc-login
feat(auth): support OIDC login and prefer IdP real email on sign-in
2026-04-09 21:13:22 +08:00
IanShaw027
62962c05f1 fix(lint): 修复 CI 中的 ineffassign 和 unused 代码告警,修正 group 排序集成测试兼容性 2026-04-09 19:25:08 +08:00
swark2006
118ff85fbf fix: 同步 LoadFactor 到调度快照缓存
LoadFactor 字段在构建调度快照缓存时缺失,导致调度算法
EffectiveLoadFactor() 无法使用配置的负载因子,回退到 Concurrency。
这会影响账号的负载率计算,进而影响调度优先级。

修复:在 buildSchedulerMetadataAccount 中添加 LoadFactor 字段。
2026-04-09 18:50:11 +08:00
IanShaw027
5f8e60a1b7 feat(table): 表格排序与搜索改为后端处理 2026-04-09 18:14:28 +08:00
IanShaw027
66e15a54a4 fix(export): 导出逻辑与当前筛选条件对齐 2026-04-09 18:14:28 +08:00
IanShaw027
ad80606a44 feat(settings): 增加全局表格分页配置,支持自定义 2026-04-09 18:14:28 +08:00
IanShaw027
d8fa38d55a fix(account): 修复账号管理中的状态筛选 2026-04-09 18:14:28 +08:00
alfadb
6401dd7cc7 fix(ops): increase error log request body limit from 10KB to 256KB
10KB is too aggressive for modern LLM API requests where conversation
context routinely exceeds 1MB. This causes error logs to contain only
a minimal placeholder, making it impossible to debug upstream failures.

256KB retains enough context for effective debugging while the existing
multi-pass trimming logic handles larger payloads gracefully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:49:02 +08:00
IanShaw027
fe211fc563 fix(ui): 修复在 macOS 系统下数据表格横向滚动条闪隐和消失的问题
问题原因:
原本的 `style.css` 代码全局使用了 W3C 标准属性 (`scrollbar-width`)。而在 Chrome 121+ 以及 Safari 环境下,一旦匹配到 W3C 标准属性,浏览器就会放弃 WebKit 的定制样式,全面交由操作系统原生渲染。因为 macOS 原生的滚动条特性就是“不滚动时自动隐藏”,加之原本又配置了全局 hover 时才显色的透明度逻辑,最终导致在苹果系统下数据表常常无法明显看出横向滚动条。

修复方式:
1. 在 `style.css` 中增加 `@supports (-moz-appearance:none)`,将对全局 `scrollbar-width` 的干预严格隔离限制在 Firefox 浏览器内,防止误伤 Chrome 等 WebKit 系浏览器。
2. 移除旧版代码中对 `.table-wrapper` 直接定义的 `scrollbar-width` 和透明覆盖。
3. 在 `DataTable.vue` 内部,通过 `!important` 将 Webkit 专属定制外观(12px高度,实心圆角灰色轨道)的优先权推至最高,强制覆盖透明隐身规则。
4. 为底层 table 添加了 `min-w-max` 属性,强制阻止内容宽度受限于屏幕边界带来的收缩,充分保证合理超出范围而触发常驻横向溢出。
2026-04-09 15:13:16 +08:00
IanShaw027
7d008bd5b6 fix(test): 修正 admin service 分组测试平台字段赋值 2026-04-09 12:42:37 +08:00
IanShaw027
66ff2def8c fix(test): 补充 admin service 分组测试字符串指针辅助函数 2026-04-09 12:39:05 +08:00
IanShaw027
de9b9c9dfb feat(admin): 增加分组 messages 调度映射配置界面 2026-04-09 12:30:25 +08:00
IanShaw027
d765359f4b test(admin): 增加messages调度表单状态转换测试 2026-04-09 12:30:06 +08:00
IanShaw027
4de4823a65 feat(openai): 支持messages模型映射与instructions模板注入 2026-04-09 12:29:49 +08:00
IanShaw027
23c4d592f8 feat(group): 增加messages调度模型映射配置 2026-04-09 12:29:28 +08:00
Glorhop
311f06745a chore: clean up deprecated Sora settings after rebase 2026-04-09 03:06:53 +00:00
Wesley Liddick
1b79f6a7cf Merge pull request #1522 from xvhuan/fix/redis-snapshot-meta-fix
优化调度快照缓存,避免 1.5 万账号场景下 Redis 大 MGET
2026-04-09 10:25:33 +08:00
Glorhop
8e1a7bdfff fix: fixed an issue where OIDC login consistently used a synthetic email address 2026-04-09 02:20:51 +00:00
ruiqurm
02a66a01c3 feat: support OIDC login. 2026-04-09 02:20:51 +00:00
octo-patch
ce833d91ce fix: include home_content URL in CSP frame-src origins (fixes #1519) 2026-04-09 09:47:27 +08:00
shaw
155d3474d6 chore: update Sponsors 2026-04-09 09:21:55 +08:00
ius
265687b56d fix: 优化调度快照缓存以避免 Redis 大 MGET 2026-04-08 10:39:15 -07:00
github-actions[bot]
0d69c0cd64 chore: sync VERSION to 0.1.110 [skip ci] 2026-04-08 08:41:32 +00:00
shaw
f54e9d0b1c chore: update readme 2026-04-08 16:37:00 +08:00
shaw
b982076e52 fix: resolve errcheck lint and add missing enable_cch_signing to test
- Suppress errcheck for xxhash Digest.Write (never returns error)
- Add enable_cch_signing field to settings API contract test
2026-04-08 16:23:02 +08:00
shaw
7060596a30 fix: bump Go from 1.26.1 to 1.26.2 to resolve 6 stdlib CVEs
Fixes GO-2026-4947, GO-2026-4946, GO-2026-4870, GO-2026-4869,
GO-2026-4866, GO-2026-4865 in crypto/x509, crypto/tls, archive/tar,
and html/template.
2026-04-08 16:17:15 +08:00
shaw
e51c9e50b5 feat: sync billing header cc_version with User-Agent and add opt-in CCH signing
- Sync cc_version in x-anthropic-billing-header with the fingerprint
  User-Agent version, preserving the message-derived suffix
- Implement xxHash64-based CCH signing to replace the cch=00000
  placeholder with a computed hash
- Add admin toggle (enable_cch_signing) under gateway forwarding settings,
  disabled by default
2026-04-08 16:11:19 +08:00
Wesley Liddick
5088e91566 Merge pull request #1417 from YanzheL/fix/openai-empty-base64-image-payloads
fix: sanitize empty base64 image payloads for OpenAI requests
2026-04-08 14:20:38 +08:00
Wesley Liddick
276f499c82 Merge pull request #1418 from YanzheL/fix/1161-gemini-google-search-grounding
fix(gemini): preserve google search grounding tools
2026-04-08 14:19:57 +08:00
Wesley Liddick
5c203ce6c6 Merge pull request #1428 from YanzheL/fix/openai-gateway-content-session-hash-fallback
fix(gateway): add content-based session hash fallback for non-Codex clients
2026-04-08 14:17:49 +08:00
Wesley Liddick
47cd1c5286 Merge pull request #1467 from touwaeriol/refactor/channel-service-cleanup
refactor(channel): split long functions, extract shared validation, move billing validation to service
2026-04-08 14:16:28 +08:00
Wesley Liddick
06e2756ee4 Merge pull request #1501 from StarryKira/fix/1493-non-streaming-empty-output
fix: 非流式响应路径扩展SSE检测至所有账号类型
2026-04-08 14:11:47 +08:00
shaw
1c9a2128cf fix: 修复非CC客户端OAuth伪装被Anthropic检测为第三方应用的问题
commit f3aa54b 的 rewriteSystemForNonClaudeCode 未能通过 Anthropic 第三方检测,
根因是两个关键信号与真实 Claude Code 不一致:

1. anthropic-beta 头缺少 claude-code-20250219:伪装路径主动将该 beta
   加入 drop set 并移除,但 Anthropic 依赖此 beta 识别 Claude Code 请求。
   修复:非 haiku 模型的伪装请求强制包含 claude-code beta。

2. system 字段使用 string 格式而非 array+cache_control:真实 Claude Code
   始终以 [{type,text,cache_control:{type:"ephemeral"}}] 发送 system,
   string 格式成为第三方检测信号。
   修复:rewriteSystemForNonClaudeCode 改为注入 array 格式。

附带调整:stripSystemCacheControl 按 system 是否被重写动态决定,
重写时保留 CC prompt 的 cache_control,未重写时(haiku/已含CC前缀)
保持原有剥离行为。
2026-04-08 14:06:06 +08:00
Elysia
9e515ea7c4 fix: 非流式响应路径扩展SSE检测至所有账号类型 (#1493)
当上游返回SSE格式响应(如sub2api链路)时,API Key账号的非流式路径
未检测SSE,导致终态事件中空output直接透传给客户端。

- 将Content-Type SSE检测从仅OAuth扩展至所有账号类型
- 重命名handleOAuthSSEToJSON为handleSSEToJSON(无OAuth专属逻辑)
- 为透传路径新增handlePassthroughSSEToJSON,支持SSE转JSON及空output重建

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:49:14 +08:00
Wesley Liddick
00aaf0f796 Merge pull request #1382 from StarryKira/fix/refresh-token-race-condition
fix: resolve refresh token race condition causing false invalid_grant errors fix issue#1381
2026-04-07 20:50:08 +08:00
github-actions[bot]
3694811d06 chore: sync VERSION to 0.1.109 [skip ci] 2026-04-07 12:49:46 +00:00
Wesley Liddick
81b96ae123 Merge pull request #1498 from aiexz/main
do not normalize model for openai API token based accounts
2026-04-07 20:37:10 +08:00
shaw
7c60ee3c85 feat: Beta策略支持按模型区分处理(模型白名单) 2026-04-07 20:33:09 +08:00
shaw
b2e379cf7a fix: 非流式路径在上游终态事件output为空时从delta事件重建响应内容
上游API近期更新后,response.completed终态SSE事件的output字段可能为空,
实际内容仅通过response.output_text.delta等增量事件下发。流式路径不受影响,
但chat_completions非流式路径和responses OAuth非流式路径只依赖终态事件的
output,导致返回空响应。

新增BufferedResponseAccumulator累积器,在SSE扫描过程中收集delta事件内容
(文本、function_call、reasoning),当终态output为空时补充重建。

同时修复handleChatBufferedStreamingResponse遗漏response.done事件类型的问题。
2026-04-07 19:35:56 +08:00
shaw
08b454423b chore: renew expired xlsx audit exceptions to 2026-07-06 2026-04-07 17:22:17 +08:00
shaw
f3aa54b770 fix: 非Claude Code客户端system prompt迁移至messages以绕过第三方应用检测
Anthropic近期引入基于system参数内容的第三方应用检测机制,原有的前置追加
Claude Code提示词策略无法通过检测(后续内容仍为非Claude Code格式触发429)。

新策略:对非Claude Code客户端的OAuth/SetupToken账号请求,将system字段
完整替换为Claude Code标识提示词,原始system内容作为user/assistant消息对
注入messages开头,模型仍接收完整指令。

仅影响/v1/messages路径,chat_completions和responses路径保持原有逻辑不变。
真正的Claude Code客户端请求完全不受影响(原样透传)。
2026-04-07 17:06:47 +08:00
Alex
3a07e92b60 fix(openai): do not normalize /completion API token based accounts 2026-04-07 11:40:41 +03:00
Alex
7eecc49c3a fix(openai): do not normalize API token based accounts 2026-04-07 11:27:57 +03:00
Wesley Liddick
9ab2fd7f9e Merge pull request #1391 from Zqysl/qingyu/fix-openai-passthrough-failover-429-529
fix(openai): fail over passthrough 429 and 529
2026-04-07 15:18:52 +08:00
Wesley Liddick
bf2b590273 Merge pull request #1397 from weak-fox/fix/active-filter-excludes-rate-limited
解决账号管理中“正常”筛选包含限流中账号
2026-04-07 08:26:51 +08:00
erio
9151d34d40 refactor(channel): split long functions, extract shared validation, move billing validation to service
- Split Update (98→25 lines), buildCache (54→20 lines), Create (51→25 lines)
  into focused sub-functions: applyUpdateInput, checkGroupConflicts,
  fetchChannelData, populateChannelCache, storeErrorCache, getOldGroupIDs,
  invalidateAuthCacheForGroups
- Extract validateChannelConfig to eliminate duplicated validation calls
  between Create and Update
- Move validatePricingBillingMode from handler to service layer for
  proper separation of concerns
- Add error logging to IsModelRestricted (was silently swallowing errors)
- Add 12 new tests: ToUsageFields, billing mode validation, antigravity
  wildcard mapping isolation, Create/Update mapping conflict integration
2026-04-05 22:32:49 +08:00
shaw
339d906e54 chore: update readme 2026-04-05 22:31:01 +08:00
shaw
f47c865555 chore: update readme 2026-04-05 22:27:13 +08:00
github-actions[bot]
58df2f0bdc chore: sync VERSION to 0.1.108 [skip ci] 2026-04-05 14:12:15 +00:00
Wesley Liddick
c71b1d63e5 Merge pull request #1460 from imfusheng/main
解决issue#1453提到的反重力不可用问题
2026-04-05 22:01:08 +08:00
shaw
a07296770c fix: remove remaining Sora references from frontend
The previous Sora removal missed several frontend references, causing
TypeScript build errors for sora_client_enabled and a missing SoraView.vue
import. Clean up all remaining Sora code from types, router, sidebar,
settings, store, and accounts API.
2026-04-05 21:26:47 +08:00
Wesley Liddick
8154575d70 Merge pull request #1464 from touwaeriol/fix/channel-platform-isolation
fix(channel): remove cross-platform pricing/mapping leakage for antigravity groups
2026-04-05 21:09:10 +08:00
Wesley Liddick
d757df8a4b Merge pull request #1463 from touwaeriol/feat/remove-sora
revert: completely remove Sora platform
2026-04-05 21:08:48 +08:00
erio
c5688fef9a fix: remove cross-platform pricing/mapping leakage for antigravity groups
Antigravity groups were incorrectly matching pricing and model mapping
entries from anthropic/gemini platform tabs. Each platform should be
strictly isolated — antigravity groups only use antigravity-tagged pricing.
2026-04-05 20:42:24 +08:00
erio
19655a15f1 fix: gofmt formatting 2026-04-05 18:56:40 +08:00
erio
f345b0f595 fix: use upstream versions of shared files and remove only Sora code
Restore gateway_service.go, setting_handler.go, routes/admin.go,
dto/settings.go, group_repo.go, api_key_repo.go, wire_gen.go to
upstream/main versions and surgically remove only Sora references.

This preserves upstream-only features (RequireOauthOnly, RequirePrivacySet,
GroupResolution, etc.) that were missing when using release branch versions.
2026-04-05 18:48:41 +08:00
erio
58707f8a2a fix: restore upstream api_contract_test and remove Sora fields 2026-04-05 18:18:58 +08:00
erio
c6089ccb33 fix: remove Sora DI from wire_gen.go and clean remaining upstream Sora references 2026-04-05 17:50:01 +08:00
shaw
f585a15eff fix(billing): prevent channel_mapped override from reverting BillingModel when channel did not map
When a channel has no model mapping for the requested model, ChannelMappedModel
equals OriginalModel (the user's arbitrary input). Combined with the default
BillingModelSource="channel_mapped", this incorrectly overrides the BillingModel
set by the OpenAI format conversion layer (e.g., gpt-5.4 from DefaultMappedModel)
back to the unmapped original model (e.g., glm) which has no pricing — resulting
in zero-cost billing.

Add guard condition so the channel_mapped override only fires when the channel
actually changed the model (ChannelMappedModel != OriginalModel).
2026-04-05 17:31:18 +08:00
erio
08e69af572 fix: remove empty DataManagement tab from settings (was Sora S3 storage) 2026-04-05 17:22:22 +08:00
erio
294b4bcbac fix: remove Sora S3 profile code from DataManagementView, keep backup functionality 2026-04-05 17:22:22 +08:00
erio
67008b5d15 fix: restore DataManagementView.vue — incorrectly deleted during Sora removal
This component is used by SettingsView.vue for backup/data management.
It was mistakenly deleted as "Sora storage management" but contains
non-Sora backup functionality from upstream.
2026-04-05 17:22:22 +08:00
erio
a29f5a4849 fix: gofmt formatting 2026-04-05 17:22:22 +08:00
erio
1b1c08f7fb fix: remove media_type from usage_log SQL queries and test stubs
- Remove media_type column from all INSERT/SELECT/SCAN in usage_log_repo
- Remove media_type mock arg from request_type and integration tests
- Adjust scan stub value arrays from 47 to 46 elements
2026-04-05 17:22:22 +08:00
erio
0c72be0403 fix: gofmt alignment and remove media_type from usage_log repo queries 2026-04-05 17:22:22 +08:00
erio
b4bd89b96b fix: remove media_type from usage_log SQL queries + gofmt + inline test helpers 2026-04-05 17:22:22 +08:00
erio
5bb8b2add6 fix: resolve CI failures — gofmt, unused functions, test parameter mismatches
- gofmt: user.go, config_test.go, group_handler.go, smart_retry_test.go
- Remove unused: mergeGroupIDs, resolveProxyURL, "time" import
- Fix api_contract_test.go: remove extra Sora args from NewAdminService,
  NewSettingHandler, NewAccountHandler; remove Sora field expectations
- Fix account_test_service_openai_test.go: restore test helpers
2026-04-05 17:22:22 +08:00
erio
93b42ccfea fix: resolve CI failures — gofmt, unused functions, missing test helpers
- Run gofmt on user schema, config test, group handler
- Remove unused mergeGroupIDs function
- Restore shared test helpers (newJSONResponse, queuedHTTPUpstream)
  that were in deleted Sora test file
2026-04-05 17:21:36 +08:00
erio
ff86154a03 refactor: remove unused OpenAIOAuthOptions after Sora platform removal
The options parameter only served to switch between 'openai' and 'sora'
platforms. With Sora removed, the parameter is unnecessary.
2026-04-05 17:19:27 +08:00
erio
fcee67e317 fix: remove remaining unused Sora variables causing TypeScript build failure
- Remove unused accessTokenInput ref from OAuthAuthorizationFlow
- Remove unused parsedSessionToken* computed and handleValidateSessionToken
- Prefix unused options parameter in useOpenAIOAuth
2026-04-05 17:19:27 +08:00
erio
155900e62f fix: remove unused Sora references causing TypeScript build failure
- Remove handleImportAccessToken event binding from CreateAccountModal
- Remove unused imports/variables from OAuthAuthorizationFlow (useAppStore,
  parsedAccessToken*, handleImportAccessToken)
- Remove unused oauthPlatform variable from useOpenAIOAuth composable
2026-04-05 17:19:27 +08:00
erio
9c514c9808 chore: drop Sora database schema and regenerate ent code 2026-04-05 17:19:07 +08:00
erio
62e80c602d revert: completely remove all Sora functionality 2026-04-05 17:11:01 +08:00
github-actions[bot]
dbb248df52 chore: sync VERSION to 0.1.107 [skip ci] 2026-04-05 06:02:08 +00:00
偷得浮生
66779f1c5f 同步ag版本号 2026-04-05 13:01:28 +08:00
偷得浮生
2c856b67ca Update oauth.go 2026-04-05 13:00:23 +08:00
Wesley Liddick
bf45581104 Merge pull request #1455 from touwaeriol/feat/channel-management
feat(channel): add channel management with multi-mode pricing and billing integration
2026-04-04 23:42:33 +08:00
erio
e88b2890d1 refactor: unify interval filtering and eliminate redundant Resolve calls
- applyRequestTierOverrides now uses filterValidIntervals consistently
  with applyTokenOverrides (per_request/image modes were not filtering)
- CostInput accepts optional pre-resolved pricing via Resolved field,
  eliminating duplicate Resolver.Resolve() calls in gateway billing paths
2026-04-04 15:15:33 +08:00
erio
1b5ae71d1f fix: resolve golangci-lint issues — remove unused constants and functions, fix gofmt
- Remove unused claudeMax*Tokens constants (Claude Max feature not included)
- Remove unused UsageMapHook type, SetUsageMapHook method, and usageToMap function
- Fix gofmt formatting in channel_service.go, openai_model_mapping_test.go,
  chatcompletions_to_responses.go
2026-04-04 14:58:20 +08:00
erio
d4ff835bf1 revert: remove antigravity credits precheck logic (not part of channel feature)
Restore account_usage_service.go, antigravity_gateway_service.go,
antigravity_credits_overages.go and its test to upstream/main state.
These credits balance precheck changes were accidentally included
during cherry-pick of channel management commits.
2026-04-04 14:32:26 +08:00
erio
e27b0adbc8 refactor: remove resolveOpenAIUpstreamModel, use normalizeCodexModel directly
Eliminates unnecessary indirection layer. The wrapper function only
called normalizeCodexModel with a special case for "gpt 5.3 codex spark"
(space-separated variant) that is no longer needed.

All call sites now use normalizeCodexModel directly.
2026-04-04 14:07:19 +08:00
erio
e59fa8637a fix: resolve cherry-pick compilation and test issues
- Add int64(0) param to SelectAccountWithLoadAwareness callers (signature change from channel scheduling refactor)
- Add UsageMapHook type and struct field to StreamingProcessor
- Revert Claude Max cache billing code to upstream/main (not part of channel feature)
- Revert credits overages logic to upstream/main (non-channel change)
- Remove Instructions field reference (non-channel OpenAI feature)
- Restore sora_client_handler_test.go from upstream + add channel service nil params
2026-04-04 12:38:50 +08:00
erio
58f758c816 feat(channel): improve cache strategy and add restriction logging
- Change channel cache TTL from 60s to 10min (reduce unnecessary DB queries)
- Actively rebuild cache after CRUD instead of lazy invalidation
- Add slog.Warn logging for channel pricing restriction blocks (4 places)
2026-04-04 11:25:01 +08:00
erio
feb6999d9a fix: channel cache fail-close, group conflict check across pages, status toggle stale data
- GetGroupPlatforms failure now stores error-TTL cache and returns error (fail-close)
- Frontend group-to-channel conflict map loads all channels instead of current page only
- Toggle channel status reloads list when active filter would hide the changed item
2026-04-04 11:25:01 +08:00
erio
71f61bbc47 fix: resolve 5 audit findings in channel/credits/scheduling
P0-1: Credits degraded response retry + fail-open
- Add isAntigravityDegradedResponse() to detect transient API failures
- Retry up to 3 times with exponential backoff (500ms/1s/2s)
- Invalidate singleflight cache between retries
- Fail-open after exhausting retries instead of 5h circuit break

P1-1: Fix channel restriction pre-check timing conflict
- Swap checkClaudeCodeRestriction before checkChannelPricingRestriction
- Ensures channel restriction is checked against final fallback groupID

P1-2: Add interval pricing validation (frontend + backend)
- Backend: ValidateIntervals() with boundary, price, overlap checks
- Frontend: validateIntervals() with Chinese error messages
- Rules: MinTokens>=0, MaxTokens>MinTokens, prices>=0, no overlap

P2: Fix cross-platform same-model pricing/mapping override
- Store cache keys using original platform instead of group platform
- Lookup across matching platforms (antigravity→anthropic→gemini)
- Prevents anthropic/gemini same-name models from overwriting each other
2026-04-04 11:25:01 +08:00
erio
6d3ea64a35 test: add unit tests for channel pricing restriction in scheduling phase
20 test cases covering:
- billingModelForRestriction: 4 cases (requested/channel_mapped/upstream/empty)
- resolveAccountUpstreamModel: 3 cases (antigravity/unsupported/non-antigravity)
- checkChannelPricingRestriction: 10 cases (nil guards, 3 billing sources,
  RestrictModels disabled, no channel)
- isUpstreamModelRestrictedByChannel: 3 cases (restricted/allowed/unsupported)
2026-04-04 11:25:01 +08:00
erio
1fca2bfab1 fix: address review findings for channel restriction refactoring
- Fix 7 stale comments still mentioning "限制检查" in handlers/services
- Make billingModelForRestriction explicitly list channel_mapped case
- Add slog.Warn for error swallowing in ResolveChannelMapping and
  needsUpstreamChannelRestrictionCheck
- Document sticky session upstream check exemption
2026-04-04 11:25:01 +08:00
erio
ce41afb756 refactor: move channel model restriction from handler to scheduling phase
Move the model pricing restriction check from 8 handler entry points
to the account scheduling phase (SelectAccountForModelWithExclusions /
SelectAccountWithLoadAwareness), aligning restriction with billing:

- requested: check original request model against pricing list
- channel_mapped: check channel-mapped model against pricing list
- upstream: per-account check using account-mapped model

Handler layer now only resolves channel mapping (no restriction).
Scheduling layer performs pre-check for requested/channel_mapped,
and per-account filtering for upstream billing source.
2026-04-04 11:24:48 +08:00
erio
b4a42a640d refactor: extract helpers to reduce duplication and function length in gateway billing
- Extract resolveChannelPricing to DRY the resolver pattern shared by calculateImageCost/calculateTokenCost
- Remove unnecessary IIFE wrapper and pass accountRateMultiplier as parameter
- Extract resolveBillingMode, resolveMediaType, optionalSubscriptionID to simplify buildRecordUsageLog (104→65 lines)
- Extract shouldDeductAPIKeyQuota/shouldUpdateRateLimits/shouldUpdateAccountQuota methods on postUsageBillingParams to unify duplicated billing conditions
2026-04-04 11:23:01 +08:00
erio
58b26cb4c8 refactor: merge RecordUsage and RecordUsageWithLongContext into shared core
- Extract recordUsageCore with recordUsageOpts for parameterized differences
- RecordUsage (276 lines) → thin wrapper (~40 lines)
- RecordUsageWithLongContext (251 lines) → thin wrapper (~20 lines)
- Split billing logic into calculateSoraMediaCost, calculateImageCost,
  calculateTokenCost sub-functions
- Extract buildRecordUsageLog for usage log construction
- Net reduction: -79 lines, eliminated ~170 lines of duplication
2026-04-04 11:22:46 +08:00
erio
b453c32743 refactor: split channelToResponse into pricingToResponse + intervalToResponse 2026-04-04 11:21:12 +08:00
erio
3cd398b098 refactor: extract computeTokenBreakdown to deduplicate billing logic
- calculateTokenCost reduced from 80 to 15 lines
- calculateCostInternal reduced from 91 to 15 lines
- Shared logic in computeTokenBreakdown + computeCacheCreationCost
- Unified rateMultiplier <= 0 protection in both paths
2026-04-04 11:21:12 +08:00
erio
d3127b8eb1 refactor: use structured error responses in channel handler
Replace response.BadRequest with response.ErrorFrom + infraerrors.BadRequest
to provide machine-readable reason codes (VALIDATION_ERROR, INVALID_CHANNEL_ID,
MISSING_PARAMETER) for frontend i18n support.
2026-04-04 11:21:11 +08:00
erio
6de1d0cb33 refactor: split buildCache into sub-functions, reduce nesting 5→2
- Extract newEmptyChannelCache() factory to deduplicate map init
- Extract expandPricingToCache() for model pricing expansion
- Extract expandMappingToCache() for model mapping expansion
- buildCache reduced from 110 to 50 lines
2026-04-04 11:21:11 +08:00
erio
6c718578a5 refactor(ui): extract formatCacheTokens and formatMultiplier to shared utils 2026-04-04 11:21:11 +08:00
erio
0d241d52eb refactor: replace magic strings with named constants
- PricingSourceChannel/LiteLLM/Fallback for resolver source
- MediaTypeImage/Video/Prompt for result.MediaType
- Reuse BillingModeToken/BillingModeImage for billing mode
- Reuse BillingModelSourceChannelMapped/PlatformAnthropic in handler
2026-04-04 11:20:43 +08:00
erio
212eaa3a05 fix(ui): show token breakdown when image model uses token billing
Only display image count format when billing_mode is "image".
When channel has token pricing, show input/output/cache token details.
2026-04-04 11:19:55 +08:00
erio
f3ab3fe5e2 fix: billing mode display follows cost calculation result
Instead of hardcoding BillingMode="image" when ImageCount>0,
let cost.BillingMode (set by CalculateCostUnified/CalculateImageCost)
take priority. This ensures channel token pricing shows "token" mode.
2026-04-04 11:19:36 +08:00
erio
b8c56ff940 fix: validate prices must be >= 0, remove debug logs 2026-04-04 11:17:49 +08:00
erio
38da737e6c feat: channel token pricing takes priority over per-image billing
When ImageCount > 0, check if channel has token pricing configured:
- YES (source=channel, mode=token) → use token billing with image_output_tokens
- NO → fall back to CalculateImageCost (original per-image billing)

This allows channels to configure $/MTok pricing for image generation
models while maintaining backward compatibility for setups without
channel pricing.
2026-04-04 11:17:49 +08:00
erio
1b2ea7a1df fix(ui): also fix floating point precision in mTokToPerToken 2026-04-04 11:17:49 +08:00
erio
a9e5fc8539 fix(ui): floating point precision in perTokenToMTok conversion 2026-04-04 11:17:49 +08:00
erio
9b213115e7 fix: address audit findings - cache sync, validation, consistency
- clearCreditsExhausted: sync Redis scheduler cache after DB update
- Image billing mode UI: write to per_request_price instead of image_output_price
- OpenAI RecordUsage: use BillingModelSourceRequested constant, add s.cfg nil guard
- Fix i18n key path: admin.channels.perRequestPriceRequired → admin.channels.form.perRequestPriceRequired
2026-04-04 11:17:49 +08:00
erio
5534347328 test: add unit tests for channel platform matching, interval validation, credits check
- TestIsPlatformPricingMatch: 12 cases covering all platform combinations
- TestMatchingPlatforms: 4 cases for platform expansion
- TestGetChannelModelPricing_AntigravityCrossPlatform: antigravity sees anthropic pricing
- TestGetChannelModelPricing_AnthropicCannotSeeAntigravityPricing: no reverse leakage
- TestResolveChannelMapping_AntigravityCrossPlatform: antigravity uses anthropic mapping
- TestFilterValidIntervals: 8 cases for empty interval filtering
- TestHasEnoughCredits: 10 cases for credits balance threshold logic
- Extract hasEnoughCredits() pure function for testability
2026-04-04 11:17:49 +08:00
erio
2355029dc1 fix: validate empty intervals + antigravity platform pricing match
- Backend: reject intervals with all-null price fields on save
- Backend: filterValidIntervals skips empty intervals in pricing resolver
- Frontend: red border + asterisk on empty interval rows
- Backend: antigravity groups now match anthropic/gemini channel pricing
2026-04-04 11:17:49 +08:00
erio
8d25335b01 fix: antigravity groups now match anthropic/gemini channel pricing
Antigravity platform serves both Claude and Gemini models, but channel
pricing/mapping is configured under Anthropic/Gemini tabs. The cache
builder was using strict platform equality, causing antigravity groups
to miss all channel pricing entries, resulting in $0 billing.

Add isPlatformPricingMatch() to treat antigravity as superset of
anthropic+gemini for pricing and mapping cache indexing.
2026-04-04 11:17:48 +08:00
erio
c0b5900a37 feat(ui): display three-level model mapping chain in usage logs
- Show channel + account mapping steps using model_mapping_chain field
- Add model_mapping_chain to AdminUsageLog TypeScript type
- Fallback to two-level display when chain is not available
- Fix cost nil guard in Anthropic/Antigravity RecordUsage paths
- Bump version to 0.1.105.31
2026-04-04 11:17:48 +08:00
erio
35a9290528 fix: add cost nil guard to Anthropic/Antigravity RecordUsage paths
- Apply same nil-pointer protection as OpenAI path
- Remove unused accessToken/proxyURL params from checkAccountCredits
2026-04-04 11:17:48 +08:00
erio
c9145ad4d8 fix: golangci-lint test assertion and gofmt 2026-04-04 11:17:48 +08:00
erio
3851628a43 fix: resolve golangci-lint issues
- Fix errcheck: defer rows.Close() with nolint
- Fix errcheck: type assertion with ok check in channel cache
- Fix staticcheck ST1005: lowercase error string
- Fix staticcheck SA5011: nil check cost before use in openai gateway
- Fix gofmt: format chatcompletions_to_responses.go
2026-04-04 11:17:24 +08:00
erio
d72ac92694 feat: image output token billing, channel-mapped billing source, credits balance precheck
- Parse candidatesTokensDetails from Gemini API to separate image/text output tokens
- Add image_output_tokens and image_output_cost to usage_log (migration 089)
- Support per-image-token pricing via output_cost_per_image_token from model pricing data
- Channel pricing ImageOutputPrice override works in token billing mode
- Auto-fill image_output_price in channel pricing form from model defaults
- Add "channel_mapped" billing model source as new default (migration 088)
- Bills by model name after channel mapping, before account mapping
- Fix channel cache error TTL sign error (115s → 5s)
- Fix Update channel only invalidating new groups, not removed groups
- Fix frontend model_mapping clearing sending undefined instead of {}
- Credits balance precheck via shared AccountUsageService cache before injection
- Skip credits injection for accounts with insufficient balance
- Don't mark credits exhausted for "exhausted your capacity on this model" 429s
2026-04-04 11:15:59 +08:00
erio
2555951be4 feat(channel): 渠道管理全链路集成 — 模型映射、定价、限制、用量统计
- 渠道模型映射:支持精确匹配和通配符映射,按平台隔离
- 渠道模型定价:支持 token/按次/图片三种计费模式,区间分层定价
- 模型限制:渠道可限制仅允许定价列表中的模型
- 计费模型来源:支持 requested/upstream 两种计费模型选择
- 用量统计:usage_logs 新增 channel_id/model_mapping_chain/billing_tier/billing_mode 字段
- Dashboard 支持 model_source 维度(requested/upstream/mapping)查看模型统计
- 全部 gateway handler 统一接入 ResolveChannelMappingAndRestrict
- 修复测试:同步 SoraGenerationRepository 接口、SQL INSERT 参数、scan 字段
2026-04-04 11:13:58 +08:00
erio
669bff78c4 fix(ui): 模型映射改用平台色字体,删除多余的边框色函数 2026-04-04 11:13:57 +08:00
erio
c90d1f2527 fix(ui): 模型映射输入框改为平台色字体,保持默认边框 2026-04-04 11:13:57 +08:00
erio
40cebc250f feat(ui): 渠道表单模型标签和映射输入框显示平台对应颜色
- PricingEntryCard 折叠态模型 tag 按平台着色
- ModelTagInput 模型标签按平台着色
- 模型映射输入框边框按平台着色
2026-04-04 11:13:57 +08:00
erio
ddd495fb48 feat(ui): 渠道列表状态列改为 Toggle 开关,支持直接启用/禁用 2026-04-04 11:13:57 +08:00
erio
58f2044637 fix(ui): 渠道定价折叠态模型名完整展示,不再截断
去掉 max-w-[120px] truncate 限制,改用 flex-wrap 允许换行,
充分利用空白区域展示完整模型名。
2026-04-04 11:13:57 +08:00
erio
dfe3fdc1cc fix(channel): 模型限制以原始请求模型检查定价列表,而非映射后模型
开启 restrict_models 时,应用原始模型名查定价列表;
定价列表未命中即拒绝,不因通配符映射而绕过限制。
2026-04-04 11:13:57 +08:00
erio
705131e172 fix(channel): 前端重复模型校验改为按平台检查
后端 validateNoDuplicateModels 使用 platform:model 复合键,
前端之前跨平台扁平化检查导致不同平台下的同名模型误报重复。
2026-04-04 11:13:57 +08:00
erio
88759407c7 feat(channel): 模型映射源支持通配符匹配
与定价通配符一致,映射源支持 * 后缀通配符(最长前缀优先):
- `*` 匹配所有模型
- `claude-*` 匹配 claude- 开头的模型
- 精确匹配优先于通配符
2026-04-04 11:13:57 +08:00
erio
6c99cc611c fix(channel): 渠道表单校验增强 — 空模型定价报错 + 必填标记
- 保存时校验:定价条目有但模型列表为空时报错并跳转到对应平台 tab,
  不再静默跳过导致数据丢失
- 保存时校验:启用的平台必须至少选择一个分组
- 分组关联标签增加红色 * 必填标记
2026-04-04 11:13:57 +08:00
erio
3457bcbfcd fix(channel): 修复 invalidateCache 存入 typed nil 导致 loadCache panic
invalidateCache 存入 (*channelCache)(nil),类型断言 ok=true 但
指针为 nil,后续 cached.loadedAt 导致 nil pointer dereference。
在 loadCache 双重检查处增加 cached != nil 防御。
2026-04-04 11:13:56 +08:00
erio
eb385457b2 fix(channel): 全平台渠道映射覆盖 + 公共函数抽取 + 死代码清理
- 4个缺失handler入口添加渠道映射+限制检查(ChatCompletions/Responses/Gemini)
- 模型限制错误信息优化,区分"模型不可用"和"无账号"
- OpenAI RecordUsage RequestedModel 改用 OriginalModel
- ResolveChannelMappingAndRestrict/ReplaceModelInBody 抽取到 ChannelService 消除跨service重复
- validateNoDuplicateModels 按 platform:model 去重
- 删除 Channel.ResolveMappedModel 死代码和 CalculateCostWithChannel Deprecated方法
- 移除冗余nil检查,抽取 validatePricingBillingMode 公共校验
2026-04-04 11:13:56 +08:00
erio
4ea8b4cb4f refactor(channel): 抽取渠道映射公共函数 + OpenAI映射到body + 空响应修复 + 清理日志
- 抽取 ResolveChannelMappingAndRestrict 统一入口(5处→1个方法)
- 抽取 BuildModelMappingChain 到 ChannelMappingResult 方法(5处→1行调用)
- OpenAI 三入口 Forward 前应用渠道映射到请求体
- OpenAI Responses/Messages 限制检查添加错误响应
- 清理前端 3 处 console.log 调试日志
2026-04-04 11:13:56 +08:00
erio
91bdcf8994 fix(channel): 模型限制用映射后模型检查 + 平台开关保留配置不删除
- OpenAI 网关三处 IsModelRestricted 改用 channelMapping.MappedModel
- 前端平台勾选改为 enabled 开关,取消勾选不清空配置数据
- formToAPI/校验只处理 enabled 的平台
2026-04-04 11:13:56 +08:00
erio
8d03c52e15 feat(channel): 通配符定价匹配 + OpenAI BillingModelSource + 按次价格校验 + 用户端计费模式展示
- 定价查找支持通配符(suffix *),最长前缀优先匹配
- 模型限制(restrict_models)同样支持通配符匹配
- OpenAI 网关接入渠道映射/BillingModelSource/模型限制
- 按次/图片计费模式创建时强制要求价格或层级(前后端)
- 用户使用记录列表增加计费模式 badge 列
2026-04-04 11:13:43 +08:00
erio
0fbc9a44d3 fix(billing): 按次计费回退到默认 PerRequestPrice
ResolvedPricing 新增 DefaultPerRequestPrice,当无层级匹配时使用渠道的默认按次价格
2026-04-04 11:12:47 +08:00
erio
632035aabd feat(billing): 网关计费迁移到 CalculateCostUnified + 模型限制错误统一
- GatewayService/OpenAIGatewayService 注入 ModelPricingResolver
- RecordUsage 从旧路径迁移到 CalculateCostUnified(支持 per_request/image 模式)
- 无渠道时自动回退旧路径,保持原有行为
- 长上下文双倍计费仅在无渠道定价时生效
- CostBreakdown 新增 BillingMode 字段,使用日志记录实际计费模式
- 模型限制错误改为与"无可用账号"相同的 503 响应
2026-04-04 11:12:21 +08:00
erio
a51e0047b7 feat(usage): 使用记录增加计费模式字段 — 记录/展示/筛选 token/按次/图片
- DB: usage_logs 表新增 billing_mode VARCHAR(20) 列
- 后端: RecordUsage 写入时根据 image_count 判定计费模式
- 前端: 使用记录表格新增计费模式 badge 列 + 筛选下拉
2026-04-04 11:11:06 +08:00
erio
726730bb0e fix(channel): splice替换model_pricing条目 + 增强调试日志 2026-04-04 11:09:59 +08:00
erio
faff1771c4 debug(channel): 添加 formToAPI 调试日志 + 移除 Sora 平台 2026-04-04 11:09:58 +08:00
erio
b06cd06ec1 feat(channel): 平台配置改为勾选式,勾选后出现 Tab 但不自动跳转 2026-04-04 11:09:58 +08:00
erio
95751d8009 feat(channel): 对话框 Tab 布局 — 基础设置 + 平台独立 Tab + 固定高度 2026-04-04 11:09:58 +08:00
erio
14e565a004 fix(channel): 分组加载时序修复 — 预加载 + await 确保分组数据就绪 2026-04-04 11:09:58 +08:00
erio
ce694701a9 fix(i18n): 渠道管理页面标题国际化 2026-04-04 11:09:57 +08:00
erio
12d03e4030 feat(channel): 模型价格自动填充 + 默认定价 API
- 新增 GET /admin/channels/model-pricing?model=xxx API
- 从 BillingService 查询 LiteLLM/Fallback 默认定价
- 前端添加模型时自动查询并填充价格($/MTok)
- 仅在所有价格字段为空时才自动填充,不覆盖手动配置
2026-04-04 11:09:28 +08:00
erio
0b1ce6be8f feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制
- 缓存按 (groupID, platform, model) 三维 key 扁平化,避免跨平台同名模型冲突
- buildCache 批量查询 group platform,按平台过滤展开定价和映射
- model_mapping 改为嵌套格式 {platform: {src: dst}}
- channel_model_pricing 新增 platform 列
- 前端按平台维度重构:每个平台独立配置分组/映射/定价
- 迁移 086: platform 列 + model_mapping 嵌套格式迁移
2026-04-04 11:09:28 +08:00
erio
28a6adaaa4 fix(channel): 分组紧凑布局+平台颜色名称+计费基准重命名+表单排序调整 2026-04-04 11:09:28 +08:00
erio
36990a0514 fix: revert ent schema change to fix runtime panic 2026-04-04 11:09:27 +08:00
erio
ebac0dc628 feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制
- 缓存重构为 O(1) 哈希结构 (pricingByGroupModel, mappingByGroupModel)
- 渠道模型映射接入网关流程 (Forward 前应用, a→b→c 映射链)
- 新增 billing_model_source 配置 (请求模型/最终模型计费)
- usage_logs 新增 channel_id, model_mapping_chain, billing_tier 字段
- 每种计费模式统一支持默认价格 + 区间定价
- 渠道模型限制开关 (restrict_models)
- 分组按平台分类展示 + 彩色图标
- 必填字段红色星号 + 模型映射 UI
- 去除模型通配符支持
2026-04-04 11:09:01 +08:00
erio
29d58f2414 feat(channel): 模型映射 + 分组搜索 + 卡片折叠 + 冲突校验
- 渠道模型映射:新增 model_mapping JSONB 字段,在账号映射之前执行
- 分组选择:添加搜索过滤 + 平台图标
- 定价卡片:支持折叠/展开,已有数据默认折叠
- 模型冲突校验:前后端均禁止同一渠道内重复模型
- 迁移 083: channels 表添加 model_mapping 列
2026-04-04 11:06:36 +08:00
erio
dca0054e93 feat(channel): 模型标签输入 + $/MTok 价格单位 + 左开右闭区间 + i18n
- 模型输入改为标签列表(输入回车添加,支持粘贴批量导入)
- 价格显示单位改为 $/MTok(每百万 token),提交时自动转换
- Token 模式增加图片输出价格字段(适配 Gemini 图片模型按 token 计费)
- 区间边界改为左开右闭 (min, max],右边界包含
- 默认价格作为未命中区间时的回退价格
- 添加完整中英文 i18n 翻译
2026-04-04 11:01:22 +08:00
erio
983fe58959 fix: CI lint/test fixes — gofmt, errcheck, handler test args 2026-04-04 11:01:22 +08:00
erio
91c9b8d062 feat(channel): 渠道管理系统 — 多模式定价 + 统一计费解析
Cherry-picked from release/custom-0.1.106: a9117600
2026-04-04 11:00:55 +08:00
Wesley Liddick
b384570de3 Merge pull request #1439 from touwaeriol/feat/redeem-negative-value
feat(redeem): support negative values for refund/deduction
2026-04-03 22:22:54 +08:00
Wesley Liddick
0507852a34 Merge pull request #1437 from DaydreamCoding/fix/openai-oauth-improvements
feat(openai): OpenAI OAuth 账号管理增强:订阅状态、隐私设置、Token 刷新修复
2026-04-03 09:22:10 +08:00
Wesley Liddick
7b6ff135fb Merge pull request #1424 from DaydreamCoding/fix/antigravity-batch-privacy
fix(antigravity): 修复批量刷新令牌不设置隐私模式的问题
2026-04-03 09:21:39 +08:00
erio
bf24de88ed style: fix gofmt struct tag alignment 2026-04-03 02:00:21 +08:00
erio
ff6d4ab39a chore: add lodash/lodash-es audit exception for GHSA-r5fr-rjxr-66jc 2026-04-03 01:53:17 +08:00
erio
66fde7a2e6 feat(redeem): support negative values for refund/deduction
Allow redeem codes with negative values to enable refund scenarios:
- Balance: negative value deducts balance (clamped to 0, never negative)
- Concurrency: negative value reduces concurrency (clamped to 0)
- Subscription: negative validity_days reduces remaining days; if
  remaining days <= 0, the subscription is canceled (set to expired)

All deductions generate standard redeem code records for audit trail.
2026-04-03 01:50:26 +08:00
QTom
e8efaa4cd9 style: gofmt struct field alignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 20:50:38 +08:00
QTom
00947d6492 feat(openai): 显示订阅到期时间
从 /backend-api/accounts/check 的 entitlement.expires_at 提取订阅
到期日期,每次 token 刷新时更新并存入 credentials,前端账号列表
的订阅类型和隐私下方以灰色小字显示(仅非 Free 账号)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 20:44:28 +08:00
QTom
cf70fb1b4e fix(openai): Mobile RT 账号隐私设置失败
1. CreateAccount 补齐 OpenAI OAuth 隐私入口(与 BatchCreate 对齐)
2. disableOpenAITraining 请求头修正:覆盖 ImpersonateChrome() 的
   浏览器导航默认头(accept: text/html, sec-fetch-mode: navigate),
   改为 API 请求语义(Accept: application/json, sec-fetch-mode: cors),
   避免 Cloudflare 将 PATCH API 请求误判为异常导航流量而拦截

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 20:44:22 +08:00
QTom
ef1a992cf0 fix(openai): refresh token when expires_at missing and account is rate-limited
Prevents token silent expiry during 7-day rate limit periods.

Made-with: Cursor
2026-04-02 20:44:12 +08:00
QTom
1f6a73f0db fix(openai): treat 401 {"detail":"Unauthorized"} as permanent auth failure
- ratelimit_service: detect non-standard OpenAI 401 format and permanently disable account
- account_test_service: mark account error on 401 during connection test

Made-with: Cursor
2026-04-02 20:44:05 +08:00
QTom
f2e596f6ec fix(oauth): 每次刷新都通过 backend-api 获取最新 plan_type
账号订阅类型可能每月变化,id_token 中的 plan_type 是签发时的快照,
不一定反映当前状态。移除 plan_type == "" 前置条件,确保每次刷新都
调用 ChatGPT backend-api 获取实时订阅类型并覆盖旧值。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:43:56 +08:00
Yanzhe Lee
77ba9e728d Merge branch 'Wei-Shaw:main' into fix/openai-gateway-content-session-hash-fallback 2026-04-02 01:56:18 +08:00
YanzheL
cf9efefd96 fix(lint): satisfy errcheck for strings.Builder.WriteString calls 2026-04-02 01:03:22 +08:00
YanzheL
4fb1603001 test(gateway): add tests for content-based session hash fallback
- 20 unit tests for deriveOpenAIContentSessionSeed covering:
  - Empty/nil inputs, model-only, stable across turns
  - Different model/system/first-user produce different seeds
  - Tools, functions, developer role, structured content
  - Responses API: input string, input array, instructions, input_text typed items
  - JSON canonicalization (whitespace/key-order insensitive)
  - Prefix presence, empty tools ignored, messages preferred over input
- 3 integration tests for GenerateSessionHash content fallback:
  - Content fallback produces stable hash
  - Explicit signals override content fallback
  - Empty body still returns empty hash
2026-04-02 00:11:17 +08:00
YanzheL
c5aac1251d fix(gateway): add content-based session hash fallback for non-Codex clients
When no explicit session signals (session_id, conversation_id, prompt_cache_key)
are provided, derive a stable session seed from the request body content
(model + tools + system prompt + first user message) to enable sticky routing
and prompt caching for non-Codex clients using the Chat Completions API.

This mirrors the content-based fallback already present in GatewayService.
GenerateSessionHash, adapted for the OpenAI gateway's request formats (both
Chat Completions messages and Responses API input).

JSON fragments are canonicalized via normalizeCompatSeedJSON to ensure
semantically identical requests produce the same seed regardless of
whitespace or key ordering.

Closes #1421
2026-04-02 00:11:06 +08:00
QTom
b155bc564b fix(antigravity): 修复批量刷新令牌不设置隐私模式的问题
- refreshSingleAccount ProjectIDMissing 提前返回前补上 EnsureAntigravityPrivacy 调用
- EnsureAntigravityPrivacy 跳过条件从"有任何值"改为"仅 privacy_set 成功时跳过",
  privacy_set_failed 允许重试,对齐 OpenAI shouldSkipOpenAIPrivacyEnsure 的行为
- 后台 TokenRefreshService.ensureAntigravityPrivacy 同步修改
- ExchangeCode/ValidateRefreshToken 获得令牌后立即调用 setAntigravityPrivacy,
  不依赖后续账号创建流程

Made-with: Cursor
2026-04-01 12:24:52 +08:00
Wesley Liddick
055c48ab33 Merge pull request #1262 from InCerryGit/main
fix(openai): preserve bare gpt-5.3-codex-spark across forwarding paths
2026-04-01 08:31:12 +08:00
Wesley Liddick
6663e1eda6 Merge pull request #1420 from YanzheL/fix/1202-gemini-customtools-404
Fix Gemini CLI 404s for gemini-3.1-pro-preview-customtools
2026-04-01 08:30:40 +08:00
YanzheL
649afef512 fix(handler): fallback known gemini models on v1beta 404 2026-04-01 02:20:13 +08:00
YanzheL
4514f3fc11 fix(gemini): resolve customtools alias in mapping lookup 2026-04-01 02:19:42 +08:00
YanzheL
095bef9554 fix(gemini): add customtools fallback metadata 2026-04-01 02:19:10 +08:00
YanzheL
f00351c106 fix(openai): sanitize empty base64 input images 2026-04-01 00:46:38 +08:00
YanzheL
936fce68d0 fix(apicompat): skip empty base64 image URLs 2026-04-01 00:46:16 +08:00
YanzheL
d978ac97f1 test(antigravity): cover mixed web search transforms 2026-04-01 00:46:14 +08:00
YanzheL
dd5978f222 fix(gemini): normalize ai studio google search tools 2026-04-01 00:45:56 +08:00
YanzheL
0ebe0ce585 fix(gemini): preserve google search in Claude compat tools 2026-04-01 00:33:39 +08:00
YanzheL
c8cfad7c00 fix(antigravity): preserve google search with function tools 2026-04-01 00:33:16 +08:00
Wesley Liddick
83a16dec19 Merge pull request #1407 from DaydreamCoding/feat/cache-driven-rpm-buffer
feat(gateway): Cache-Driven RPM Buffer
2026-03-31 14:01:23 +08:00
Wesley Liddick
820c531814 Merge pull request #1406 from DaydreamCoding/feat/group-account-filter
feat(group-filter): 分组账号过滤控制 — require_oauth_only + require_privacy_set
2026-03-31 14:01:05 +08:00
Wesley Liddick
1727b8df3b Merge pull request #1404 from DaydreamCoding/feat/antigravity-privacy-on-refresh-fail
feat(antigravity): 令牌刷新失败及创建账号时也设置隐私
2026-03-31 14:00:53 +08:00
shaw
a025a15f5d feat: add refresh button to admin and user dashboard pages 2026-03-31 13:53:49 +08:00
QTom
72e5876c64 feat(gateway): Cache-Driven RPM Buffer
- buffer 公式从 baseRPM/5 改为 concurrency + maxSessions
  保留 baseRPM/5 作为 floor 向后兼容
- 粘性路径 fallback 新增 [StickyCacheMiss] 结构化日志
  reason: rpm_red / gate_check / session_limit / wait_queue_full / account_cleared
- session_limit 路径跳过 wait queue 重试(RegisterSession 拒绝无副作用)
- 典型配置 buffer 从 3 提升至 13,大幅减少高峰期 Prompt Cache Miss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 13:24:22 +08:00
QTom
aeed2eb9ad feat(group-filter): 分组账号过滤控制 — require_oauth_only + require_privacy_set
为 OpenAI/Antigravity/Anthropic/Gemini 分组新增两个布尔控制字段:
- require_oauth_only: 创建/更新账号绑定分组时拒绝 apikey 类型加入
- require_privacy_set: 调度选号时跳过 privacy 未成功设置的账号并标记 error

后端:Ent schema 新增字段 + 迁移、Group CRUD 全链路透传、
      gateway_service 与 openai_account_scheduler 两套调度路径过滤
前端:创建/编辑表单 toggle 开关(OpenAI/Antigravity/Anthropic/Gemini 平台可见)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 13:04:55 +08:00
QTom
46bc5ca73b feat(antigravity): 令牌刷新失败及创建账号时也设置隐私
- token_refresh: 不可重试错误和重试耗尽两条路径添加 ensureAntigravityPrivacy
- admin_service: CreateAccount 为 Antigravity OAuth 账号异步设置隐私

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:42:23 +08:00
InCerry
0b3feb9d4c fix(openai): resolve Anthropic compat mapping from normalized model
Anthropic compat requests normalize reasoning suffixes before forwarding, but the account mapping step was still using the raw request model. Resolve billing and upstream models from the normalized compat model so explicit account mappings win over fallback defaults.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-31 10:33:28 +08:00
InCerry
ca8692c747 Merge remote-tracking branch 'upstream/main'
# Conflicts:
#	backend/internal/service/openai_gateway_messages.go
2026-03-31 09:38:40 +08:00
weak-fox
a61d58716f fix(admin): exclude rate-limited accounts from active filter 2026-03-31 00:00:46 +08:00
qingyuzhang
6b646b6127 fix(openai): fail over passthrough 429 and 529 2026-03-30 22:29:26 +08:00
shaw
318aa5e0d3 feat: add cache hit rate line to token usage trend chart
Add a purple dashed line showing cache hit rate percentage
(cache_read / (cache_read + cache_creation)) on a secondary
right Y-axis (0-100%). Applies to both user and admin dashboards.
2026-03-30 21:43:07 +08:00
haruka
49e99e9d51 fix: resolve errcheck lint for sync.Map type assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:44:15 +08:00
shaw
1dfd974432 chore: update readme 2026-03-30 16:28:31 +08:00
shaw
cc396f59cf chore: update readme 2026-03-30 16:24:29 +08:00
haruka
ad2cd97618 fix: resolve refresh token race condition causing false invalid_grant errors
When multiple goroutines/workers concurrently refresh the same OAuth token,
the first succeeds but invalidates the old refresh_token (rotation). Subsequent
attempts using the stale token get invalid_grant, which was incorrectly treated
as non-retryable, permanently marking the account as ERROR.

Three complementary fixes:
1. Race-aware recovery: after invalid_grant, re-read DB to check if another
   worker already refreshed (refresh_token changed) — return success instead
   of error
2. In-process mutex (sync.Map of per-account locks): prevents concurrent
   refreshes within the same process, complementing the Redis distributed lock
3. Increase default lock TTL from 30s to 60s to reduce TTL-expiry races

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:23:38 +08:00
github-actions[bot]
aa8b9cc508 chore: sync VERSION to 0.1.106 [skip ci] 2026-03-30 08:13:49 +00:00
Wesley Liddick
6a2cf09ee0 Merge pull request #1349 from touwaeriol/feat/antigravity-internal500-penalty
feat(antigravity): progressive penalty for consecutive INTERNAL 500 errors
2026-03-30 15:54:04 +08:00
Wesley Liddick
c6fd88116b Merge pull request #1354 from wucm667/fix/billing-use-requested-model
fix(billing): 计费始终使用用户请求的原始模型,而非映射后的上游模型
2026-03-30 15:52:31 +08:00
Wesley Liddick
8f0dbdeaba Merge pull request #1343 from yilinyo/fix/api-key-unique-conflict-after-soft-delete
fix(api-key):软删除apikey后key没有被释放后续无法再自定义相同的key
2026-03-30 15:47:28 +08:00
Wesley Liddick
007c09b84e Merge pull request #1338 from LvyuanW/fix/safari-ops-log-select
fix(admin): fix Safari system log select height
2026-03-30 15:45:35 +08:00
Wesley Liddick
73f3c068ef Merge pull request #1344 from 7836246/fix/i18n-sora-storage-missing-keys
fix(i18n): 修复 Sora 存储配置页面表格列头「存储桶」翻译缺失
2026-03-30 15:45:03 +08:00
Wesley Liddick
9a92fa4a60 Merge pull request #1370 from YanzheL/fix/1320-openai-messages-gpt54-xhigh
fix(gateway): normalize gpt-5.4-xhigh for /v1/messages
2026-03-30 15:44:34 +08:00
Wesley Liddick
576af710be Merge pull request #1352 from StarryKira/feat/add-file-upload-oauth-scope
Feat/add file upload oauth scope
2026-03-30 15:41:18 +08:00
Wesley Liddick
b5642bd068 Merge pull request #1377 from DaydreamCoding/fix/lifecycle-stop-duplicate-close
fix(lifecycle): TokenRefreshService Stop() 防重复 close
2026-03-30 15:38:39 +08:00
Wesley Liddick
128f322252 Merge pull request #1376 from weak-fox/fix/privacy-without-refresh-token
修复缺少 refresh_token 时被临时停调度
2026-03-30 15:38:27 +08:00
Wesley Liddick
17d7e57a2e Merge pull request #1375 from weak-fox/fix/batch-reset-temp-unsched
修复重置状态时未清理临时停调度
2026-03-30 15:37:58 +08:00
shaw
50288e6b01 fix: 修复模型定价文件更新url 2026-03-30 15:36:53 +08:00
shaw
ab3e44e4bd fix: 适配X-Claude-Code-Session-Id头 2026-03-30 11:43:07 +08:00
QTom
61607990c8 fix(lifecycle): TokenRefreshService Stop() 防重复 close
使用 sync.Once 包裹 close(stopCh),避免多次调用 Stop() 时
触发 panic: close of closed channel。
2026-03-30 10:33:06 +08:00
shaw
b65275235f feat: Anthropic oauth/setup-token账号支持自定义转发URL 2026-03-30 09:10:57 +08:00
weak-fox
e298a71834 fix: clear temp unsched when resetting account status 2026-03-30 00:22:02 +08:00
weak-fox
3f6fa1e3db fix: avoid temp unsched when refresh token is missing 2026-03-30 00:21:51 +08:00
YanzheL
f2c2abe628 fix(openai): keep xhigh normalization scoped to messages 2026-03-29 21:09:19 +08:00
YanzheL
ff5b467fbe fix(handler): normalize compat model for message routing 2026-03-29 20:53:14 +08:00
YanzheL
8c10941142 fix(openai): normalize gpt-5.4-xhigh compat mapping 2026-03-29 20:52:29 +08:00
wucm667
f5764d8dc6 fix(billing): 计费始终使用用户请求的原始模型,而非映射后的上游模型
当账号配置了模型映射(如 claude-sonnet-4-6 → glm-5.0)时,系统错误地
使用映射后的上游模型名计算费用。由于上游模型(如 glm-5.0)在定价系统中
没有价格配置,导致计费失败后被静默置为 0,用户不被扣费。

修改 forwardResultBillingModel 优先返回请求模型名,并移除 OpenAI 路径
中 BillingModel 字段对计费模型的覆盖逻辑。
2026-03-28 16:22:06 +08:00
Elysia
81ca4f12dd 修复误删的url 2026-03-28 00:55:55 +08:00
Elysia
941c469ab9 fix: use standard PKCE code verifier generation
Replace charset→base64url double-encoding with standard random
bytes→base64url approach to match official client behavior and avoid
risk control detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 00:47:31 +08:00
Elysia
8fcd819e6f feat: add user:file_upload OAuth scope
Align OAuth scopes with upstream Claude Code client which now includes
the user:file_upload scope for file upload support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 00:40:36 +08:00
erio
9abdaed20c style: gofmt antigravity_internal500_penalty.go 2026-03-27 20:18:07 +08:00
erio
eb94342f78 chore: adjust internal500 penalty durations to 30m / 2h 2026-03-27 20:11:24 +08:00
erio
d563eb2336 test: add unit tests for INTERNAL 500 progressive penalty
Cover isAntigravityInternalServerError body matching,
applyInternal500Penalty tier escalation, handleInternal500RetryExhausted
nil-safety and error handling, and resetInternal500Counter paths.
2026-03-27 20:11:24 +08:00
erio
3ee6f085db refactor: extract internal500 penalty logic to dedicated file
Move constants, detection, and penalty functions from
antigravity_gateway_service.go to antigravity_internal500_penalty.go.
Fix gofmt alignment and replace hardcoded duration strings with
constant references.
2026-03-27 20:11:24 +08:00
erio
7cca69a136 fix: move internal500 counter reset to cover all success paths
Move the reset logic after urlFallbackLoop so it covers both direct
success and smart retry (429/503) success paths.
2026-03-27 20:11:24 +08:00
erio
093a5a260e feat(antigravity): progressive penalty for consecutive INTERNAL 500 errors
When an antigravity account returns 500 "Internal error encountered."
on all 3 retry attempts, increment a Redis counter and apply escalating
penalties:
- 1st round: temp unschedulable 10 minutes
- 2nd round: temp unschedulable 10 hours
- 3rd round: permanently mark as error

Counter resets on any successful response (< 400).
2026-03-27 20:11:24 +08:00
InCerryGit
b6d46fd52f Merge branch 'Wei-Shaw:main' into main 2026-03-27 17:35:47 +08:00
小海
2c072c0ed6 fix(i18n): add missing bucket column translation key for Sora S3 storage settings
The `admin.settings.soraS3.columns.bucket` key was used in
DataManagementView.vue but missing from both en.ts and zh.ts locale
files, causing the raw translation key to be displayed as a column
header instead of the localized text.
2026-03-27 16:44:14 +08:00
YilinMacAir
1f39bf8a78 fix:修复由于数据库唯一键导致软删除apikey后key没有被释放后续无法再自定义相同的key 2026-03-27 16:37:10 +08:00
github-actions[bot]
fdd8499ffc chore: sync VERSION to 0.1.105 [skip ci] 2026-03-27 08:04:27 +00:00
Wesley Liddick
9398ea7af5 Merge pull request #1340 from DaydreamCoding/fix/privacy-and-system-prompt
fix(openai): OpenAI 隐私模式全场景覆盖 & 修复转发路径 system prompt 丢失
2026-03-27 15:03:57 +08:00
Wesley Liddick
29dce1a59c Merge pull request #1266 from eltociear/add-ja-doc
docs: add Japanese README
2026-03-27 14:51:37 +08:00
QTom
c729ee425f fix(gateway): 修复 OpenAI→Anthropic 转换路径 system prompt 被静默丢弃的 bug
injectClaudeCodePrompt 和 systemIncludesClaudeCodePrompt 的 type switch
无法匹配 json.RawMessage 类型(Go typed nil 陷阱),导致 ForwardAsResponses
和 ForwardAsChatCompletions 路径中用户 system prompt 被替换为仅 Claude Code
banner。新增 normalizeSystemParam 将 json.RawMessage 转为标准 Go 类型。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:51:36 +08:00
QTom
c489f23810 feat(privacy): 创建/批量创建 OpenAI OAuth 账号时异步设置隐私模式
参照 Antigravity 的模式,单个创建时同步调用 ForceOpenAIPrivacy,
批量创建时收集 OpenAI OAuth 账号后异步 goroutine 设置,避免阻塞请求。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:51:36 +08:00
QTom
47a544230a fix(privacy): 刷新令牌失败时也尝试设置 OpenAI 隐私模式
刷新失败不代表 access_token 无效,在后台定时刷新(不可重试错误 +
重试耗尽)和前端批量/单次刷新的失败路径中,均利用可能仍有效的
access_token 调用隐私设置。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:51:36 +08:00
QTom
c13c81f09d feat(privacy): 为 OpenAI OAuth 账号添加前端手动设置隐私按钮
复用已有的 set-privacy API 端点,Handler 通过 platform 分发到
ForceOpenAIPrivacy / ForceAntigravityPrivacy,前端 AccountActionMenu
扩展隐私按钮支持 OpenAI OAuth 账号。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:51:36 +08:00
Wesley Liddick
20544a4447 Merge pull request #1300 from xilu0/fix/forward-failed-log-missing-account-proxy-info
fix: add account and proxy details to forward_failed log
2026-03-27 14:47:51 +08:00
Wesley Liddick
b688ebeefa Merge pull request #1215 from weak-fox/fix/privacy-retry-failed-mode
fix: 允许 OpenAI privacy_mode修改失败后能在 token 刷新时重试
2026-03-27 14:46:38 +08:00
shaw
1854050df3 feat(tls-fingerprint): 新增 TLS 指纹 Profile 数据库管理及代码质量优化
新增功能:
- 新增 TLS 指纹 Profile CRUD 管理(Ent schema + 迁移 + Admin API + 前端管理界面)
- 支持账号绑定数据库中的自定义 TLS Profile,或随机选择(profile_id=-1)
- HTTPUpstream.DoWithTLS 接口从 bool 改为 *tlsfingerprint.Profile,支持按账号指定 Profile
- AccountUsageService 注入 TLSFingerprintProfileService,统一 usage 场景与网关的 Profile 解析逻辑

代码优化:
- 删除已被 TLSFingerprintProfileService 完全取代的 registry.go 死代码(418 行)
- 提取 3 个 dialer 的重复 TLS 握手逻辑为 performTLSHandshake() 共用函数
- 修复 GetTLSFingerprintProfileID 缺少 json.Number 处理的 bug
- gateway_service.Forward 中 ResolveTLSProfile 从重试循环内重复调用改为预解析局部变量
- 删除冗余的 buildClientHelloSpec() 单行 wrapper 和 int64(e.ID) 无效转换
- tls_fingerprint_profile_cache.go 日志从 log.Printf 改为 slog 结构化日志
- dialer_capture_test.go 添加 //go:build integration 标签,防止 CI 失败
- 去重 TestProfileExpectation 类型至共享 test_types_test.go
- 修复 9 个测试文件缺少 tlsfingerprint import 的编译错误
- 修复 error_policy_integration_test.go 中 handleError 回调签名被错误替换的问题
2026-03-27 14:33:05 +08:00
Wang Lvyuan
c7f4a649df fix(admin): use custom select for ops log filters 2026-03-27 14:07:12 +08:00
Wesley Liddick
ef5c8e6839 Merge pull request #1231 from LvyuanW/bulk-openai-passthrough-worktree
Support bulk editing for OpenAI passthrough
2026-03-26 16:47:49 +08:00
shaw
d571f300e5 feat(rectifier): 请求整流器增加 API Key 账号签名整流支持
新增独立开关控制 API Key 账号的签名整流功能,支持配置自定义
匹配关键词以捕获不同格式的上游错误响应。

- 新增 apikey_signature_enabled 开关(默认关闭)
- 新增 apikey_signature_patterns 自定义关键词配置
- 内置签名检测规则对 API Key 账号同样生效
- 自定义关键词对完整响应体做不区分大小写匹配
- 重试二阶段检测仅做模式匹配,不重复校验开关
- Handler 层校验关键词数量(≤50)和长度(≤500)
- API 响应 nil patterns 统一序列化为空数组
- OAuth/SetupToken/Upstream/Bedrock 账号行为不变
2026-03-26 16:43:38 +08:00
Wesley Liddick
ce96527dd9 Merge pull request #1302 from DaydreamCoding/fix/openai-error-handling
fix(ratelimit): OpenAI 401 token_invalidated/token_revoked 及 402 deac…
2026-03-26 11:30:52 +08:00
Wesley Liddick
f8b8b53985 Merge pull request #1299 from DaydreamCoding/feat/antigravity-privacy-and-subscription
feat(antigravity): 自动隐私设置 + 订阅状态检测
2026-03-26 11:30:24 +08:00
shaw
b20e142249 feat: 网关请求头 wire casing 保持、转发行为开关、调试日志增强及 accept-encoding 恢复
- 新增 header_util.go,通过 setHeaderRaw/getHeaderRaw/addHeaderRaw 绕过
  Go 的 canonical-case 规范化,保持真实 Claude CLI 抓包的请求头大小写
  (如 "x-app" 而非 "X-App","X-Stainless-OS" 而非 "X-Stainless-Os")
- 新增管理后台开关:指纹统一化(默认开启)和 metadata 透传(默认关闭),
  使用 atomic.Value + singleflight 缓存模式,60s TTL
- 调试日志从控制台 body 打印升级为文件级完整快照
  (按真实 wire 顺序输出 headers + 格式化 JSON body + 上下文元数据)
- 恢复 accept-encoding 到白名单,在 http_upstream.go 新增 decompressResponseBody
  处理 gzip/brotli/deflate 解压(Go 显式设置 Accept-Encoding 时不会自动解压)
- OAuth 服务 axios UA 从 1.8.4 更新至 1.13.6
- 测试断言改用 getHeaderRaw 适配 raw header 存储方式
2026-03-26 11:17:25 +08:00
Dave King
7c6dc9dda8 fix: add account and proxy details to gateway.forward_failed log
The forward_failed error log only included account_id, making it
difficult to identify which account and proxy caused the failure
without querying the database. Add account_name, account_platform,
and proxy details (id, name, host, port) to the log fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:19:17 +00:00
QTom
5875571215 fix(ratelimit): OpenAI 401 token_invalidated/token_revoked 及 402 deactivated_workspace 标记账号异常
- 401 token_invalidated / token_revoked: OAuth token 被永久作废,跳过临时不可调度逻辑,直接 SetError
- 402 deactivated_workspace: 解析 detail.code 字段,标记工作区已停用

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:46:17 +08:00
QTom
975e6b1563 fix: 修复 golangci-lint 报告的 5 个问题
- gofmt: 修复 admin_service/antigravity_oauth_service/token_refresh_service 格式
- staticcheck S1009: 移除 SetUserSettingsResponse.IsSuccess 中冗余的 nil 检查
- unused: 将仅测试使用的 applyAntigravitySubscriptionResult 移至测试文件

Made-with: Cursor
2026-03-25 19:03:12 +08:00
QTom
f6fd7c83e3 feat(antigravity): 从 LoadCodeAssist 复用 TierInfo 提取 plan_type
复用已有 GetTier() 返回的 tier ID(free-tier / g1-pro-tier /
g1-ultra-tier),通过 TierIDToPlanType 映射为 Free / Pro / Ultra,
在 loadProjectIDWithRetry 中顺带提取并写入 credentials.plan_type;
前端增加 Abnormal 异常套餐红色标记。

Made-with: Cursor
2026-03-25 17:38:41 +08:00
QTom
c2965c0fb0 feat(antigravity): 自动设置隐私并支持后台手动重试
新增 Antigravity OAuth 隐私设置能力,在账号创建、刷新、导入和后台
Token 刷新路径自动调用 setUserSettings + fetchUserInfo 关闭遥测;
持久化后同步内存 Extra,错误处理改为日志记录。

Made-with: Cursor
2026-03-25 17:38:41 +08:00
Ikko Ashimine
fdad55956e docs: add Japanese README 2026-03-25 00:34:56 +09:00
Wang Lvyuan
bb399e56b0 merge: resolve upstream main conflicts for bulk OpenAI passthrough 2026-03-24 19:27:51 +08:00
InCerryGit
fa68cbad1b Merge branch 'Wei-Shaw:main' into main 2026-03-24 19:21:30 +08:00
InCerry
995ef1348a refactor: improve model resolution and normalization logic for OpenAI integration 2026-03-24 19:20:15 +08:00
Wesley Liddick
0f03393010 Merge pull request #1234 from Zqysl/qingyu/fix-ops-runtime-log-controls-layout
fix(ops): prevent runtime log controls UI overflow
2026-03-24 19:13:47 +08:00
Wesley Liddick
4b1ffc23f5 Merge pull request #1240 from Zqysl/qingyu/fix-openai-passthrough-429-rate-limits
fix(openai): persist passthrough 429 rate limits
2026-03-24 19:02:40 +08:00
Wesley Liddick
c7137dffa8 Merge pull request #1218 from LvyuanW/openai-runtime-recheck
fix(openai): prevent rescheduling rate-limited accounts
2026-03-24 15:21:18 +08:00
Wesley Liddick
5a3375ce52 Merge pull request #1227 from liruiluo/codex/dockerignore-deploy-data
Ignore deploy runtime data in Docker context
2026-03-24 15:20:10 +08:00
Wesley Liddick
8e834fd9f5 Merge pull request #1204 from Eilen6316/fix/smtp-config-stability-and-refresh-test
fix(settings): prevent SMTP config overwrite and stabilize SMTP test after refresh
2026-03-24 15:19:24 +08:00
Wesley Liddick
02046744eb Merge pull request #1212 from alfadb/fix/filter-empty-text-blocks-nested
fix(gateway): 修复 tool_result 嵌套内容中空 text block 导致上游 400 错误
2026-03-24 15:19:01 +08:00
Wesley Liddick
68d7ec9155 Merge pull request #1220 from weak-fox/feat/account-privacy-mode-filter
feat: 管理员账号列表支持按 Privacy 状态筛选
2026-03-24 15:18:30 +08:00
Wesley Liddick
7537dce0f0 Merge pull request #1230 from LvyuanW/bulk-openai-oauth-ws-mode-pr
Add bulk OpenAI OAuth WS mode editing
2026-03-24 15:17:13 +08:00
Wesley Liddick
5f41b74707 Merge pull request #1242 from Ethan0x0000/feat/anthropic-openai-endpoint-compat
支持 Anthropic Responses / Chat Completions 兼容端点并完善会话一致性与错误可观测性
2026-03-24 15:16:26 +08:00
Wesley Liddick
25d961d4e0 Merge pull request #1252 from DaydreamCoding/feat/openai-mobile-rt
feat(openai): 支持 Mobile Refresh Token 导入,自动补全 plan_type
2026-03-24 15:12:25 +08:00
InCerry
08c4e514f8 Merge branch 'main' of github.com:InCerryGit/sub2api
# Conflicts:
#	backend/internal/service/billing_service.go
2026-03-24 15:08:55 +08:00
QTom
91b1d812ce feat(openai): Mobile RT 补全 plan_type、精确匹配账号、刷新时自动设置隐私
1. accounts/check 补全 plan_type:当 id_token 缺少 plan_type(如 Mobile RT),
   自动调用 accounts/check 端点获取订阅类型
2. orgID 精确匹配账号:从 JWT 提取 poid 匹配正确账号,避免 Go map
   遍历顺序随机导致 plan_type 不稳定
3. RT 刷新时设置隐私:调用 disableOpenAITraining 关闭训练数据共享,
   结果存入 extra.privacy_mode,后续跳过重复设置

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:50:03 +08:00
QTom
1f05d9f79d fix(openai): buildCredentials 对齐后端 BuildAccountCredentials 字段
补齐前端 buildCredentials 缺失的 id_token、email、plan_type 字段,
与后端 BuildAccountCredentials 保持一致。修复手动 RT 创建的账号
缺少订阅类型等关键信息的问题。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:50:03 +08:00
QTom
9f8cffe887 feat(openai): 新增"手动输入 Mobile RT"入口,使用 SoraClientID 刷新
在 OpenAI 平台添加独立的"手动输入 Mobile RT"选项,使用
client_id=app_LlGpXReQgckcGGUo2JrYvtJK 刷新 token,与现有
"手动输入 RT"(Codex CLI client_id)互不影响。
共享同一 UI 和批量创建逻辑,通过 clientId 参数区分。
同时修复空名称触发 ent NotEmpty() 校验导致 500 的问题。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:50:03 +08:00
shaw
995bee143a feat: 支持自定义端点配置与展示 2026-03-24 10:22:08 +08:00
Ethan0x0000
f10e56be7e refactor(test): improve type assertions in ops endpoint context tests 2026-03-24 09:52:56 +08:00
Ethan0x0000
2f8e10db46 fix(service): preserve anthropic usage fields across compat endpoints
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 09:32:34 +08:00
Ethan0x0000
5418e15e63 fix(service): normalize user agent for gemini session reuse
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 09:32:01 +08:00
Ethan0x0000
bcf84cc153 fix(service): normalize user agent for sticky session hashes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-24 09:31:32 +08:00
qingyuzhang
ce8520c9e6 fix(openai): persist passthrough 429 rate limits 2026-03-24 01:48:25 +08:00
qingyuzhang
0b3928c33e fix(ops): prevent runtime log controls overflow 2026-03-23 18:54:45 +08:00
Wang Lvyuan
73d72651b4 feat: support bulk OpenAI passthrough toggle 2026-03-23 17:17:42 +08:00
Wang Lvyuan
adbedd488c Add bulk OpenAI OAuth WS mode editing 2026-03-23 17:11:01 +08:00
Ethan0x0000
13b72f6bc2 Merge remote-tracking branch 'origin/feat/ops-error-observability-transfer' into feat/anthropic-openai-endpoint-compat
# Conflicts:
#	frontend/src/api/admin/ops.ts
#	frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
#	frontend/src/views/admin/ops/components/OpsErrorLogTable.vue
2026-03-23 16:31:59 +08:00
Ethan0x0000
c5aa96a3aa feat(frontend): display error observability fields in ops admin panel
Show endpoint, model mapping, and request type in the ops error log
table and detail modal:
- Endpoint column with inbound/upstream tooltip
- Model column showing requested→upstream mapping with arrow
- Request type badge (sync/stream/ws) in status column
- New detail cards for inbound endpoint, upstream endpoint, request type
2026-03-23 16:24:59 +08:00
Ethan0x0000
d927c0e45f feat(routes): add platform-based routing split for /v1/responses and /v1/chat/completions
Mirror the existing /v1/messages platform split pattern:
- OpenAI groups → OpenAIGateway handlers (existing, unchanged)
- Non-OpenAI groups → Gateway handlers (new Anthropic-upstream path)

Updated both /v1 prefixed routes and non-prefixed alias routes
(/responses, /chat/completions). WebSocket route (/v1/responses GET)
remains OpenAI-only as Anthropic has no WebSocket equivalent.
2026-03-23 16:24:47 +08:00
Ethan0x0000
31660c4c5f feat(handler): add Responses/ChatCompletions handlers on GatewayHandler
New HTTP handlers for Anthropic platform groups accepting OpenAI-format
endpoints:

- GatewayHandler.Responses: /v1/responses for non-OpenAI groups
- GatewayHandler.ChatCompletions: /v1/chat/completions for non-OpenAI groups

Both handlers include:
- Claude Code only restriction (403 reject when claude_code_only enabled,
  since these endpoints are never Claude Code clients)
- Full auth → billing → user/account concurrency → failover loop
- Ops error/endpoint context propagation
- Async usage recording via worker pool

Error responses use each endpoint's native format (Responses API format
for /v1/responses, CC format for /v1/chat/completions).
2026-03-23 16:24:35 +08:00
Ethan0x0000
4321adab71 feat(service): add ForwardAsResponses/ForwardAsChatCompletions on GatewayService
New forwarding methods on GatewayService for Anthropic platform groups:

- ForwardAsResponses: accept Responses body → convert to Anthropic →
  forward to upstream → convert response back to Responses format.
  Supports both streaming (SSE event-by-event conversion) and buffered
  (accumulate then convert) response modes.
- ForwardAsChatCompletions: chain CC→Responses→Anthropic for request,
  Anthropic→Responses→CC for response. Streaming uses dual state machine
  chain with [DONE] marker.

Both methods reuse existing GatewayService infrastructure:
buildUpstreamRequest, Claude Code mimicry, cache control enforcement,
model mapping, and return UpstreamFailoverError for handler-level retry.
2026-03-23 16:24:22 +08:00
Ethan0x0000
68f151f5c0 feat(apicompat): add Responses↔Anthropic bidirectional format conversion
Add reverse-direction converters for Anthropic platform groups to accept
OpenAI-format requests:

- ResponsesToAnthropicRequest: Responses API input → Anthropic Messages
  request with system extraction, tool/toolChoice mapping, reasoning
  effort conversion, image data URI↔base64, and consecutive role merging
- AnthropicToResponsesResponse: Anthropic response → Responses response
  with content block→output item mapping, usage, stop_reason→status
- AnthropicEventToResponsesEvents: stateful SSE stream converter
  (Anthropic streaming protocol → Responses streaming protocol)
- FinalizeAnthropicResponsesStream: synthetic termination for
  incomplete streams
2026-03-23 16:24:09 +08:00
Ethan0x0000
ecad083ffc fix(ops): prefer upstream_model in ops error displays 2026-03-23 15:50:12 +08:00
liruiluo
fee43e8474 Ignore deploy runtime data in docker context 2026-03-23 12:35:38 +08:00
weak-fox
4838ab74b3 feat(admin): add account privacy mode filter 2026-03-23 10:16:52 +08:00
Wang Lvyuan
fef9259aaa fix(openai): recheck runtime state from db before final account selection 2026-03-23 03:50:03 +08:00
Wang Lvyuan
ad7c10727a fix(account): preserve runtime state during credentials-only updates 2026-03-23 03:49:28 +08:00
weak-fox
ccd42c1d1a Retry OpenAI privacy opt-out after failed states 2026-03-23 00:10:22 +08:00
Ethan0x0000
bd8eadb75b feat(ops): enhance error observability with additional context fields and UI updates 2026-03-22 19:56:29 +08:00
alfadb
70a9d0d3a2 fix(gateway): strip empty text blocks from nested tool_result content
Empty text blocks inside tool_result.content were not being filtered,
causing upstream 400 errors: 'text content blocks must be non-empty'.

Changes:
- Add stripEmptyTextBlocksFromSlice helper for recursive content filtering
- FilterThinkingBlocksForRetry now recurses into tool_result nested content
- Add StripEmptyTextBlocks pre-filter on initial request path to avoid
  unnecessary 400+retry round-trips
- Add unit tests for nested empty text block scenarios
2026-03-22 17:26:44 +08:00
Ethan0x0000
7cd3824863 test(ops): add tests for setOpsEndpointContext and safeUpstreamURL 2026-03-21 23:49:50 +08:00
Ethan0x0000
db9021f9c1 feat(ops): propagate endpoint/request-type context in handlers; add UpstreamURL to upstream error events 2026-03-21 23:47:39 +08:00
Ethan0x0000
a2418c6040 feat(ops): adapt repository INSERT/SELECT + add setOpsEndpointContext in error logger middleware 2026-03-21 23:38:00 +08:00
Eilen6316
1fb29d59b7 fix(settings): prevent SMTP config overwrite and stabilize test after refresh 2026-03-21 23:36:30 +08:00
Ethan0x0000
8c4a217f03 feat(ops): add endpoint/model/request_type fields to error log structs + safeUpstreamURL 2026-03-21 23:30:13 +08:00
Wesley Liddick
bda7c39e55 Merge pull request #1196 from Eilen6316/fix/settings-form-url-validation
fix: prevent silent save failure in admin settings form
2026-03-21 20:55:23 +08:00
Wesley Liddick
3583283ebb Merge pull request #1197 from mutuyihao/fix/apicompat-array-content
fix(apicompat): support array content for system and tool messages
2026-03-21 20:53:27 +08:00
mutuyihao
4feacf2213 fix(apicompat): support array content for system and tool messages 2026-03-21 15:34:28 +08:00
Eilen6316
73eb731881 fix: prevent silent save failure in admin settings form
The settings form contains multiple <input type="url"> fields that lack
a name attribute. When a field value fails browser URL validation, the
browser silently blocks form submission without showing an error — no
network request is made, and the user sees no feedback.

Root cause: HTML5 form validation requires a focusable element with a
name attribute to surface errors. Without it, validation fails silently.

Fix:
- Add novalidate to the <form> to disable browser-native URL validation
- Add an isValidHttpUrl() helper in saveSettings() to replicate the
  same checks the backend performs
- Optional URL fields (frontend_url, doc_url): auto-clear invalid values
  instead of blocking the save, matching backend behaviour (these fields
  accept empty string without error)
- purchase_subscription_url: block save with a clear error message when
  enabled + invalid; auto-clear when disabled to prevent the backend 400
  "Purchase Subscription URL must be an absolute http(s) URL" error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:03:18 +08:00
Wesley Liddick
186e36752d Merge pull request #1194 from Ethan0x0000/feat/requested-upstream-model-semantics
feat(usage): 统一使用记录中的请求模型与上游模型语义
2026-03-21 14:02:10 +08:00
Wesley Liddick
421728a985 Merge pull request #1193 from xilu0/worktree-fix-thinking-block-log-level
fix: correct log levels for thinking block signature retry flow
2026-03-21 13:57:30 +08:00
Wesley Liddick
39a5701184 Merge pull request #1182 from DaydreamCoding/fix/ops-alert-wg-race-and-context-leak
fix(ops_alert): wg.Add 竞态修复 + leader lock release context 泄漏
2026-03-21 13:52:14 +08:00
Ethan0x0000
27948c777e fix(dto): fallback to legacy model in usage mapping
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 11:10:40 +08:00
Dave King
c64ed46d05 fix: correct log levels for thinking block signature retry flow
LegacyPrintf uses inferStdLogLevel() to infer log level from message
text. Any message containing the word "error" is classified as ERROR
level, causing the entire signature-retry recovery flow (which succeeds)
to produce spurious ERROR log entries.

Changes:
- Remove noisy [SignatureCheck] debug logs inside isThinkingBlockSignatureError
  that were logging every detected signature check as ERROR
- Change retry-start log to WARN level via [warn] prefix
- Change retry-success log to INFO level by removing "error" from message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:38:07 +00:00
Ethan0x0000
c64465ff7e test(frontend): align admin usage typing with upstream model
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:26:03 +08:00
Ethan0x0000
095200bd16 refactor(dto): split admin usage upstream model exposure
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:25:34 +08:00
Ethan0x0000
2c667a159c fix(provider): retain upstream model for gemini compat and ws
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:24:59 +08:00
Ethan0x0000
bac408044f fix(provider): preserve requested model in antigravity and sora
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:24:30 +08:00
Ethan0x0000
4edcfe1f7c fix(usage): preserve requested model in gateway billing paths
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:23:54 +08:00
Ethan0x0000
9259dcb6f5 test(repo): cover requested model repository semantics
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:23:20 +08:00
Ethan0x0000
7ef933c7cf feat(repo): persist requested model in usage log queries
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:22:49 +08:00
Ethan0x0000
7d312822c1 feat(usage): add requested model usage metadata helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:22:16 +08:00
Ethan0x0000
1b3e5c6ea6 chore(go): sync backend go.sum
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:21:48 +08:00
Ethan0x0000
efe8401e92 chore(ent): regenerate usage log requested model artifacts
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:21:21 +08:00
Ethan0x0000
0b845c2532 feat(ent): add requested model to usage log schema
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:20:56 +08:00
Ethan0x0000
fe60412a17 feat(db): add requested model usage log migrations
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-21 01:20:23 +08:00
QTom
5c39e6f2fb fix(ops_alert): wg.Add 竞态修复 + leader lock release context 泄漏
1. Start() 中 wg.Add(1) 从 run() goroutine 内部移到 go s.run() 之前,
   防止 Stop().wg.Wait() 在 Add 之前返回导致孤儿 goroutine。
2. tryAcquireLeaderLock 返回的 release 闭包改用独立的
   context.Background()+5s 超时,避免捕获的 evaluateOnce ctx
   在 defer 执行时已过期导致锁释放失败(最长阻塞 90s TTL)。
2026-03-20 18:22:00 +08:00
Wesley Liddick
a225a241d7 Merge pull request #1162 from remxcode/main
feat(openai): 增加 gpt-5.4-mini/nano 模型支持与定价配置
2026-03-20 13:57:47 +08:00
Wesley Liddick
553a486d17 Merge pull request #1171 from wucm667/fix/quota-display-stale-after-reset
fix: quota display shows stale cumulative usage after daily/weekly reset
2026-03-20 13:54:18 +08:00
Wesley Liddick
c73374a221 Merge pull request #1176 from learnerLj/fix-bugs
fix: 修复 OpenAI 转发路径未应用分组默认模型映射
2026-03-20 13:53:20 +08:00
Wesley Liddick
94e26dee4f Merge pull request #1172 from alfadb/fix/openai-messages-effort-max-to-xhigh
fix(apicompat): 修正 Anthropic→OpenAI 推理级别映射
2026-03-20 13:48:41 +08:00
Jiahao Luo
4617ef2bb8 Fix OpenAI default model forwarding 2026-03-20 13:36:54 +08:00
alfadb
8afa8c1091 fix(apicompat): 修正 Anthropic→OpenAI 推理级别映射
旧映射错误地将所有级别上移一档(medium→high, high→xhigh),
导致 effort=max 被原样透传到 OpenAI 上游并返回 400 错误。

根据两边官方 API 定义对齐:
- Anthropic: low, medium, high(默认), max
- OpenAI:    low, medium, high(默认), xhigh

新的 1:1 映射:low→low, medium→medium, high→high, max→xhigh
2026-03-20 12:01:02 +08:00
Remx
578608d301 fix: format gpt-5.4 mini fallback pricing 2026-03-20 10:54:50 +08:00
wucm667
0d45d8669e fix: quota display shows stale cumulative usage after daily/weekly reset
The quota reset mechanism is lazy — quota_daily_used/quota_weekly_used
in the database are only reset on the next IncrementQuotaUsed call.
The scheduling layer (IsQuotaExceeded) correctly checks period expiry
before enforcing limits, so the account remains usable. However, the
API response mapper reads the raw DB value without checking expiry,
causing the frontend to display cumulative usage (e.g. 110%) even
after the reset period has passed.

Add IsDailyQuotaPeriodExpired/IsWeeklyQuotaPeriodExpired methods and
use them in the mapper to return used=0 when the period has expired.
2026-03-20 10:22:54 +08:00
InCerry
73708da60d Merge branch 'main' of github.com:InCerryGit/sub2api 2026-03-20 10:11:53 +08:00
Remx
c810cad7c8 feat(openai): 增加 gpt-5.4-mini/nano 模型支持与定价配置
- 接入 gpt-5.4-mini/nano 模型识别与规范化,补充默认模型列表
- 增加 gpt-5.4-mini/nano 输入/缓存命中/输出价格与计费兜底逻辑
- 同步前端模型白名单与 OpenCode 配置
- 补充 service tier(priority/flex) 计费回归测试
2026-03-20 09:53:42 +08:00
github-actions[bot]
94bba415b1 chore: sync VERSION to 0.1.104 [skip ci] 2026-03-20 01:31:30 +00:00
shaw
4f7629a4cb fix: add max_claude_code_version to API contract test expected output 2026-03-20 09:17:32 +08:00
Wesley Liddick
4015f31f28 Merge pull request #1157 from LvyuanW/fix-bulk-model-restriction-empty
fix: allow clearing model restriction in bulk edit when whitelist is empty
2026-03-20 09:13:44 +08:00
Wesley Liddick
9dccbe1b07 Merge pull request #1169 from touwaeriol/pr/credits-exhausted-fix
fix(antigravity): correctly mark credits exhausted on "Resource has been exhausted" 429
2026-03-20 09:12:55 +08:00
Wesley Liddick
9a88df7f28 Merge pull request #1167 from touwaeriol/pr/proxy-fast-fail
fix(antigravity): fast-fail on proxy unavailable, temp-unschedule account
2026-03-20 09:12:39 +08:00
Wesley Liddick
a47f622e7e Merge pull request #1159 from JerryFan626/fix/docker-compose-to-docker-compose-v2
docs: update docker-compose commands to Docker Compose V2 syntax
2026-03-20 09:12:14 +08:00
Wesley Liddick
3529148455 Merge pull request #1151 from DaydreamCoding/feat/admin-user-group-filter
feat(admin): 用户管理新增分组列、分组筛选与专属分组一键替换
2026-03-20 09:10:38 +08:00
shaw
01d8286bd9 feat: add max_claude_code_version setting and disable auto-upgrade env var
Add maximum Claude Code version limit to complement the existing minimum
version check. Refactor the version cache from single-value to unified
bounds struct (min+max) with a single atomic.Value and singleflight group.

- Backend: new constant, struct field, cache refactor, validation (semver
  format + cross-validation max >= min), gateway enforcement, audit diff
- Frontend: settings UI input, TypeScript types, zh/en i18n
- Add CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 to all Claude Code
  tutorials on /keys page (unix/cmd/powershell/vscode settings.json)
2026-03-20 09:10:01 +08:00
erio
21b6f2d593 fix(antigravity): correctly mark credits exhausted on "Resource has been exhausted" 429
shouldMarkCreditsExhausted was blocked by isURLLevelRateLimit check when
credit overages retry returned "Resource has been exhausted (e.g. check quota).",
causing credits to never be marked as exhausted. This led to an infinite loop
where each request injected credits, bypassed model rate limits, and failed again.

- Remove isURLLevelRateLimit guard from shouldMarkCreditsExhausted (only called
  for credit retry responses — if credits retry fails, mark exhausted)
- Add "resource has been exhausted" to creditsExhaustedKeywords
- Update tests to match corrected behavior
2026-03-20 00:04:01 +08:00
erio
528ff5d28c fix(antigravity): fast-fail on proxy unavailable, temp-unschedule account
## Problem

When a proxy is unreachable, token refresh retries up to 4 times with
30s timeout each, causing requests to hang for ~2 minutes before
failing with a generic 502 error. The failed account is not marked,
so subsequent requests keep hitting it.

## Changes

### Proxy connection fast-fail
- Set TCP dial timeout to 5s and TLS handshake timeout to 5s on
  antigravity client, so proxy connectivity issues fail within 5s
  instead of 30s
- Reduce overall HTTP client timeout from 30s to 10s
- Export `IsConnectionError` for service-layer use
- Detect proxy connection errors in `RefreshToken` and return
  immediately with "proxy unavailable" error (no retries)

### Token refresh temp-unschedulable
- Add 8s context timeout for token refresh on request path
- Mark account as temp-unschedulable for 10min when refresh fails
  (both background `TokenRefreshService` and request-path
  `GetAccessToken`)
- Sync temp-unschedulable state to Redis cache for immediate
  scheduler effect
- Inject `TempUnschedCache` into `AntigravityTokenProvider`

### Account failover
- Return `UpstreamFailoverError` on `GetAccessToken` failure in
  `Forward`/`ForwardGemini` to trigger handler-level account switch
  instead of returning 502 directly

### Proxy probe alignment
- Apply same 5s dial/TLS timeout to shared `httpclient` pool
- Reduce proxy probe timeout from 30s to 10s
2026-03-19 23:48:37 +08:00
QTom
ba7d2aecbb feat(admin): 用户管理新增分组列、分组筛选与专属分组一键替换
- 新增分组列:展示用户的专属/公开分组,支持 hover 查看详情
- 新增分组筛选:下拉选择或模糊搜索分组名过滤用户
- 专属分组替换:点击专属分组弹出操作菜单,选择目标分组后
  自动授予新分组权限、迁移绑定的 Key、移除旧分组权限
- 后端新增 POST /admin/users/:id/replace-group 端点,事务内
  完成分组替换并失效认证缓存
2026-03-19 22:27:55 +08:00
Wesley Liddick
0236b97d49 Merge pull request #1134 from yasu-dev221/fix/openai-compat-prompt-cache-key
fix(openai): add fallback prompt_cache_key for compat codex OAuth requests
2026-03-19 22:02:08 +08:00
Wesley Liddick
26f6b1eeff Merge pull request #1142 from StarryKira/fix/failover-exhausted-upstream-status-code
fix: record original upstream status code when failover exhausted (#1128)
2026-03-19 21:56:58 +08:00
Wesley Liddick
dc447ccebe Merge pull request #1153 from hging/main
feat: add ungrouped filter to account
2026-03-19 21:55:28 +08:00
Wesley Liddick
7ec29638f4 Merge pull request #1147 from DaydreamCoding/feat/persisted-page-size
feat(frontend): 分页 pageSize 持久化到 localStorage,刷新后自动恢复
2026-03-19 21:53:54 +08:00
Wesley Liddick
4c9562af20 Merge pull request #1148 from weak-fox/ci/sync-version-file-after-release
ci: sync VERSION file back to default branch after release
2026-03-19 21:46:45 +08:00
Wesley Liddick
71942fd322 Merge pull request #1132 from touwaeriol/pr/virtual-scroll
perf(frontend): add virtual scrolling to DataTable
2026-03-19 21:46:16 +08:00
Wesley Liddick
550b979ac5 Merge pull request #1146 from DaydreamCoding/fix/test-403-error-status
fix(test): 测试连接收到 403 时将账号标记为 error 状态
2026-03-19 21:44:57 +08:00
Wesley Liddick
3878a5a46f Merge pull request #1164 from GuangYiDing/fix/normalize-tool-parameters-schema
fix: Anthropic tool schema 转换时补充缺失的 properties 字段
2026-03-19 21:44:18 +08:00
Rose Ding
e443a6a1ea fix: 移除 staticcheck S1005 警告的多余 blank identifier
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:14:29 +08:00
Rose Ding
963494ec6f fix: Anthropic tool schema 转 Responses API 时补充缺失的 properties 字段
当 Claude Code 发来的 MCP tool 的 input_schema 为 {"type":"object"} 且缺少
properties 字段时,OpenAI Codex 后端会拒绝并报错:
Invalid schema for function '...': object schema missing properties.

新增 normalizeToolParameters 函数,在 convertAnthropicToolsToResponses 中
对每个 tool 的 InputSchema 做规范化处理后再赋给 Parameters。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:08:20 +08:00
Remx
42d73118fd feat(openai): 增加 gpt-5.4-mini/nano 模型支持与定价配置
- 接入 gpt-5.4-mini/nano 模型识别与规范化,补充默认模型列表
- 增加 gpt-5.4-mini/nano 输入/缓存命中/输出价格与计费兜底逻辑
- 同步前端模型白名单与 OpenCode 配置
- 补充 service tier(priority/flex) 计费回归测试
2026-03-19 19:03:13 +08:00
Jerry Fan
f2f819d70f docs: update docker-compose commands to Docker Compose V2 syntax
The project prerequisites already require Docker Compose v2+, but command
examples still used the legacy `docker-compose` (hyphenated) form. Replace
all command invocations with `docker compose` (space-separated) while
keeping file name references (docker-compose.yml, etc.) unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:12:50 +08:00
shaw
525cdb8830 feat: Anthropic 账号被动用量采样,页面默认展示被动数据
从上游 /v1/messages 响应头被动采集 5h/7d utilization 并存储到
Account.Extra,页面加载时直接读取本地数据而非调用外部 Usage API。
用户可点击"查询"按钮主动拉取最新数据,主动查询结果自动回写被动缓存。

后端:
- UpdateSessionWindow 合并采集 5h + 7d headers 为单次 DB 写入
- 新增 GetPassiveUsage 从 Extra 构建 UsageInfo (复用 estimateSetupTokenUsage)
- GetUsage 主动查询后 syncActiveToPassive 回写被动缓存
- passive_usage_ 前缀注册为 scheduler-neutral

前端:
- Anthropic 账号 mount/refresh 默认 source=passive
- 新增"被动采样"标签和"查询"按钮 (带 loading 动画)
2026-03-19 17:42:59 +08:00
shaw
a6764e82f2 修复 OAuth/SetupToken 转发请求体重排并增加调试开关 2026-03-19 16:56:18 +08:00
Wang Lvyuan
1de18b89dd merge: sync upstream/main before PR 2026-03-19 16:37:28 +08:00
Wang Lvyuan
882518c111 fix(frontend): allow clearing model restriction in bulk edit 2026-03-19 16:32:13 +08:00
Hg
8027531d07 feat: add ungrouped filter to account 2026-03-19 15:42:21 +08:00
weak-fox
30706355a4 ci: sync VERSION file back to default branch after release 2026-03-19 12:53:28 +08:00
QTom
dfe99507b8 feat(frontend): 分页 pageSize 持久化到 localStorage,刷新后自动恢复
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:03 +08:00
QTom
c1717c9a6c fix(test): 测试连接收到 403 时将账号标记为 error 状态
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:36:40 +08:00
haruka
1fd1a58a7a fix: record original upstream status code when failover exhausted (#1128)
When all failover accounts are exhausted, handleFailoverExhausted maps
the upstream status code (e.g. 403) to a client-facing code (e.g. 502)
but did not write the original code to the gin context. This caused ops
error logs to show the mapped code instead of the real upstream code.

Call SetOpsUpstreamError before mapUpstreamError in all failover-
exhausted paths so that ops_error_logger captures the true upstream
status code and message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:15:02 +08:00
jimmy-coder
fad07507be fix(openai): inject stable compat prompt_cache_key for codex oauth chat-completions path 2026-03-19 03:24:31 +08:00
erio
a20c211162 perf(frontend): add virtual scrolling to DataTable
Replace direct row rendering with @tanstack/vue-virtual. The table
now only renders visible rows (~20) via padding <tr> placeholders,
eliminating the rendering bottleneck when displaying 100+ rows with
heavy cell components.

Key changes:
- DataTable.vue: integrate useVirtualizer (always-on), virtual row
  template with measureElement for variable row heights, defineExpose
  virtualizer/sortedData for external access, overflow-y/flex CSS
- useSwipeSelect.ts: dual-mode support via optional
  SwipeSelectVirtualContext — data-driven row index lookup and
  selection range when virtualizer is present, original DOM-based
  path preserved for callers that don't pass virtualContext
2026-03-18 23:11:49 +08:00
Wesley Liddick
9f6ab6b817 Merge pull request #1090 from laukkw/main
fix(setup): align install validation and expose backend errors
2026-03-18 16:23:06 +08:00
shaw
bf3d6c0e6e feat: add 529 overload cooldown toggle and duration settings in admin gateway page
Move 529 overload cooldown configuration from config file to admin
settings UI. Adds an enable/disable toggle and configurable cooldown
duration (1-120 min) under /admin/settings gateway tab, stored as
JSON in the settings table.

When disabled, 529 errors are logged but accounts are no longer
paused from scheduling. Falls back to config file value when DB
is unreachable or settingService is nil.
2026-03-18 16:22:19 +08:00
Wesley Liddick
241023f3fc Merge pull request #1097 from Ethan0x0000/pr/upstream-model-tracking
feat(usage): 新增 upstream_model 追踪,支持按模型来源统计与展示
2026-03-18 15:36:00 +08:00
Wesley Liddick
1292c44b41 Merge pull request #1118 from touwaeriol/worktree-fix/anti_mapping
feat: map claude-haiku-4-5 variants to claude-sonnet-4-6
2026-03-18 15:13:19 +08:00
Wesley Liddick
b4fce47049 Merge pull request #1116 from wucm667/fix/inject-site-title-in-html
fix: 直接访问或刷新页面时浏览器标签页显示自定义站点名称
2026-03-18 15:12:07 +08:00
Wesley Liddick
e7780cd8c8 Merge pull request #1117 from alfadb/fix/empty-text-block-retry
fix: 修复空 text block 导致上游 400 错误未被重试捕获的问题
2026-03-18 15:10:46 +08:00
erio
af96c8ea53 feat: map claude-haiku-4-5 variants to claude-sonnet-4-6
Update model mapping target for claude-haiku-4-5 and
claude-haiku-4-5-20251001 from claude-sonnet-4-5 to claude-sonnet-4-6.
Includes migration script, default constants, and test updates.
2026-03-18 15:03:24 +08:00
alfadb
7d26b81075 fix: address review - add missing whitespace patterns and narrow error matching 2026-03-18 14:31:57 +08:00
alfadb
b8ada63ac3 fix: strip empty text blocks in retry filter and fix error pattern matching
Empty text blocks ({"type":"text","text":""}) cause Anthropic upstream to
return 400: "text content blocks must be non-empty". This was not caught
by the existing error detection pattern in isThinkingBlockSignatureError,
nor handled by FilterThinkingBlocksForRetry.

- Add empty text block stripping to FilterThinkingBlocksForRetry
- Fix isThinkingBlockSignatureError to match new Anthropic error format
- Add fast-path byte patterns to avoid unnecessary JSON parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:20:00 +08:00
Ethan0x0000
cfaac12af1 Merge upstream/main into pr/upstream-model-tracking
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-18 14:16:50 +08:00
wucm667
6028efd26c test: 添加 injectSiteTitle 函数的单元测试 2026-03-18 14:13:52 +08:00
shaw
62a566ef2c fix: 修复 config.yaml 以只读方式挂载时容器启动失败 (#1113)
entrypoint 中 chown -R /app/data 在遇到 :ro 挂载的文件时报错退出,
添加错误容忍处理;同时去掉 compose 文件注释中的 :ro 建议。
2026-03-18 14:11:51 +08:00
wucm667
94419f434c fix: 直接访问或刷新页面时浏览器标签页显示自定义站点名称
后端 HTML 注入时同步替换 <title> 标签为自定义站点名称,
前端 fetchPublicSettings 完成后重新设置 document.title,
解决路由守卫先于设置加载导致标题回退为默认值的时序问题。
2026-03-18 14:02:00 +08:00
Wesley Liddick
21f349c032 Merge pull request #1095 from LvyuanW/lvyuan/dev
fix(admin/accounts): reset edit modal state on reopen
2026-03-18 11:37:07 +08:00
Wesley Liddick
28e36f7925 Merge pull request #1096 from Ethan0x0000/pr/fix-idle-usage-windows
fix(ui): 会话窗口空闲时显示“现在”,避免重置时间缺失
2026-03-18 11:32:50 +08:00
Wesley Liddick
6c02076333 Merge pull request #1106 from geminiwen/feat/subscription-platform-filter
feat: add platform type filter to subscription management
2026-03-18 11:32:35 +08:00
shaw
7414bdf0e3 fix: 修复 hotpath 测试中 metadata.user_id 格式不合法导致 CI 失败
测试数据使用的 session ID "abc-123" 不符合 ParseMetadataUserID
要求的 36 字符 UUID 格式,替换为合法 UUID。
2026-03-18 11:31:32 +08:00
Wesley Liddick
e6326b2929 Merge pull request #1108 from DaydreamCoding/feat/admin-group-capacity-and-usage
feat(admin): 分组管理列表新增用量、账号分类与容量列
2026-03-18 11:12:43 +08:00
Wesley Liddick
17cdcebd04 Merge pull request #1109 from GuangYiDing/feat/subscription-guide
feat(subscriptions): 订阅管理页面添加教程指南弹窗
2026-03-18 11:12:33 +08:00
shaw
a14babdc73 fix: 兼容 Claude Code v2.1.78+ 新 JSON 格式 metadata.user_id
Claude Code v2.1.78 起将 metadata.user_id 从拼接字符串改为 JSON:
旧: user_{hex}_account_{uuid}_session_{uuid}
新: {"device_id":"...","account_uuid":"...","session_id":"..."}

新增集中解析/格式化模块 metadata_userid.go:
- ParseMetadataUserID: 自动识别两种格式,提取 DeviceID/AccountUUID/SessionID
- FormatMetadataUserID: 根据 UA 版本输出对应格式(>= 2.1.78 输出 JSON)
- ExtractCLIVersion: 从 UA 提取版本号,消除与 ClaudeCodeValidator.ExtractVersion 的重复

修改消费者统一使用新模块:
- claude_code_validator: 用 ParseMetadataUserID 替代只匹配旧格式的 userIDPattern
- identity_service: RewriteUserID/WithMasking 增加 fingerprintUA 参数,
  解析用 ParseMetadataUserID,输出用 FormatMetadataUserID(版本感知)
- gateway_service: GenerateSessionHash 用 ParseMetadataUserID 提取 session_id,
  buildOAuthMetadataUserID 用 FormatMetadataUserID 输出版本匹配格式,
  两处 RewriteUserIDWithMasking 调用传入 fp.UserAgent
- account_test_service: generateSessionString 改用 FormatMetadataUserID,
  自动跟随 DefaultHeaders UA 版本

删除三个旧正则: userIDPattern, userIDRegex, sessionIDRegex
统一 hex 匹配为 [a-fA-F0-9],修复旧 userIDRegex 只匹配小写的不一致
2026-03-18 11:08:58 +08:00
Rose Ding
aadc6a763a feat(subscriptions): 订阅管理页面添加教程指南弹窗
在订阅管理页面工具栏添加教程指南按钮(? 图标),点击弹出模态框,
引导管理员完成订阅功能的完整使用流程:

- 步骤一:创建订阅分组(含跳转分组管理链接)
- 步骤二:分配订阅给用户(搜索用户、选择分组、设置有效期)
- 步骤三:管理已有订阅(调整/重置配额/撤销操作说明表格)
- 底部提示:说明下拉列表为空时的解决方案

弹窗样式参照 BackupView 的 R2 Guide 模态框实现,保持 UI 一致性。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:49:41 +08:00
Rose Ding
f16af8bf88 feat(i18n): 添加订阅管理教程指南英文翻译
在 en.ts 中为订阅管理页面新增 guide 相关翻译词条,
与中文翻译保持结构一致,支持中英文切换。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:49:32 +08:00
Rose Ding
5ceaef4500 feat(i18n): 添加订阅管理教程指南中文翻译
在 zh.ts 中为订阅管理页面新增 guide 相关翻译词条,包括:
- 教程弹窗标题与副标题
- 三步操作引导文案(创建分组、分配订阅、管理订阅)
- 操作说明表格(调整/重置配额/撤销)
- 底部提示信息

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:49:13 +08:00
Gemini Wen
1ac7219a92 fix: add missing platform parameter to List calls in integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:35:03 +08:00
QTom
d4cc9871c4 feat(admin): 分组管理新增容量列(并发/会话/RPM 实时聚合)
复用 GroupCapacityService,在 admin 分组列表中添加容量列,
显示每个分组的实时并发/会话/RPM 使用量和上限。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:06:35 +08:00
QTom
961c30e7c0 feat(admin): 分组管理列表新增用量列与账号数分类
分组管理列表增强:

1. 今日/累计用量列:
   - 新增独立端点 GET /admin/groups/usage-summary
   - 一次查询返回所有分组的今日费用和累计费用(actual_cost)
   - 前端异步加载后合并显示在分组列表中

2. 账号数区分可用/限流/总量:
   - 将账号数列从单一总量改为 badge 内多行展示
   - 可用: active + schedulable 的账号数(绿色)
   - 限流: rate_limit/overload/temp_unschedulable 的账号数(橙色,无限流时隐藏)
   - 总量: 全部关联账号数

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:06:35 +08:00
Gemini Wen
13e85b3147 fix: update remaining test stubs for List interface signature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 09:35:08 +08:00
Gemini Wen
50a3c7fa0b feat: add platform type filter to subscription management page
Add a platform filter dropdown to the admin subscriptions view, allowing
filtering subscriptions by platform (Anthropic, OpenAI, Gemini, etc.)
through the group association.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 09:23:19 +08:00
Ethan0x0000
bd9d2671d7 chore(deps): go mod tidy to remove stale indirect dependencies 2026-03-17 20:46:12 +08:00
Ethan0x0000
62b40636e0 feat(frontend): display upstream model in usage table and distribution charts
Show upstream model mapping (requested -> upstream) in UsageTable with arrow notation. Add requested/upstream/mapping source toggle to ModelDistributionChart with lazy loading — only fetches data when user switches tab, with per-source cache invalidation on filter changes. Include upstream_model column in Excel export and i18n for en/zh.
2026-03-17 19:26:48 +08:00
Ethan0x0000
eeff451bc5 test(backend): add tests for upstream model tracking and model source filtering
Cover IsValidModelSource/NormalizeModelSource, resolveModelDimensionExpression SQL expressions, invalid model_source 400 responses on both GetModelStats and GetUserBreakdown, upstream_model in scan/insert SQL mock expectations, and updated passthrough/billing test signatures.
2026-03-17 19:26:30 +08:00
Ethan0x0000
56fcb20f94 feat(api): expose model_source filter in dashboard endpoints
Add model_source query parameter to GetModelStats and GetUserBreakdown handlers with explicit IsValidModelSource validation. Include model_source in cache key to prevent cross-source cache hits. Expose upstream_model in usage log DTO with omitempty semantics.
2026-03-17 19:26:11 +08:00
Ethan0x0000
7134266acf feat(dashboard): add model source dimension to stats queries
Support querying model statistics by 'requested', 'upstream', or 'mapping' dimension. Add resolveModelDimensionExpression for safe SQL expression generation, IsValidModelSource whitelist validator, and NormalizeModelSource fallback. Repository persists and scans upstream_model in all insert/select paths.
2026-03-17 19:25:52 +08:00
Ethan0x0000
2e4ac88ad9 feat(service): record upstream model across all gateway paths
Propagate UpstreamModel through ForwardResult and OpenAIForwardResult in Anthropic direct, API-key passthrough, Bedrock, and OpenAI gateway flows. Extract optionalNonEqualStringPtr and optionalTrimmedStringPtr into usage_log_helpers.go. Store upstream_model only when it differs from the requested model.

Also introduces anthropicPassthroughForwardInput struct to reduce parameter count.
2026-03-17 19:25:35 +08:00
Ethan0x0000
51547fa216 feat(db): add upstream_model column to usage_logs
Add nullable VARCHAR(100) column to record the actual model sent to upstream providers when model mapping is applied. NULL means no mapping — the requested model was used as-is.

Includes migration, concurrent index for aggregation queries, Ent schema regeneration, and migration README correction (forward-only runner, not goose).
2026-03-17 19:25:17 +08:00
Ethan0x0000
2005fc97a8 fix(ui): show 'now' for idle OpenAI usage windows
Use utilization-based idle detection instead of local request counts so newly imported OAuth accounts keep countdowns when usage is non-zero.
2026-03-17 19:23:35 +08:00
Wang Lvyuan
0772d9250e fix(admin/accounts): reset edit modal state on reopen 2026-03-17 18:44:10 +08:00
laukkw
aa6047c460 fix(setup): align install validation and expose backend errors
Make setup password requirements consistent with backend rules and show API-provided error messages so install failures are actionable. Trim admin email before validation to avoid false invalid-email rejections from surrounding whitespace.
2026-03-17 15:38:18 +08:00
Wesley Liddick
045cba78b4 Merge pull request #1083 from StarryKira/fix/claude-code-version-pattern-validation
fix(settings): remove pattern attribute blocking Claude Code version save fix issue #1081
2026-03-17 14:49:34 +08:00
Wesley Liddick
8989d0d4b6 Merge pull request #1085 from protondrift/main
feat: 个人资料弹窗 GitHub 链接仅对管理员可见
2026-03-17 14:49:07 +08:00
Wesley Liddick
c521117b99 Merge pull request #1074 from StarryKira/fix/session-window-reset-from-header
fix(usage): use real reset header for 5h session window countdown fix issue #1064 #1065
2026-03-17 14:48:16 +08:00
Eric
e0f52a8ab8 feat: 个人资料弹窗 GitHub 链接仅对管理员可见
目前作者已有商业站信息,面向管理可提供赞助渠道,面向普通用户请考虑提供信息隐藏措施
2026-03-17 12:51:34 +08:00
haruka
6c23fadf7e fix(settings): remove pattern attribute blocking Claude Code version save
The `pattern="\d+\.\d+\.\d+"` on the min_claude_code_version input caused
the browser's native HTML5 form validation to silently block form submission
when the value was invalid or when the hidden gateway tab was active. This
resulted in no network request being sent when clicking Save on any tab.

Backend already validates semver format and returns a proper 400 error,
so the frontend pattern attribute is redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:33:57 +08:00
haruka
869952d113 fix(review): address Copilot PR feedback
- Add compile-time interface assertion for sessionWindowMockRepo
- Fix flaky fallback test by capturing time.Now() before calling UpdateSessionWindow
- Replace stale hardcoded timestamps with dynamic future values
- Add millisecond detection and bounds validation for reset header timestamp
- Use pause/resume pattern for interval in UsageProgressBar to avoid idle timers on large lists
- Fix gofmt comment alignment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 10:19:20 +08:00
Wesley Liddick
07ab051ee4 Merge pull request #1078 from luxiang0412/main
fix(proxy): encode special chars in proxy credentials
2026-03-17 09:33:02 +08:00
Wesley Liddick
f2d98fc0c7 Merge pull request #1077 from Clov614/main
fix(auto setup): 修复初始测试连接硬编码问题导致使用自定义数据库测试失败无法执行 auto setup流程
2026-03-17 09:32:52 +08:00
Wesley Liddick
2b41cec840 Merge pull request #1076 from touwaeriol/pr/antigravity-test-connection-unify
refactor(antigravity): unify TestConnection with dispatch retry loop
2026-03-17 09:26:06 +08:00
Wesley Liddick
6cf77040e7 Merge pull request #1075 from touwaeriol/feat/dashboard-user-breakdown
feat(dashboard): add per-user drill-down for distribution charts
2026-03-17 09:25:43 +08:00
Wesley Liddick
20b70bc5fd Merge pull request #1070 from StarryKira/fix/oauth-system-role-to-instructions
fix(oauth): extract system-role input items into instructions field fix issue #1066
2026-03-17 09:24:17 +08:00
Wesley Liddick
4905e7193a Merge pull request #1069 from Ethan0x0000/pr/codex-usage-single-source
fix(openai): use /usage as single source and zero expired codex windows
2026-03-17 09:09:16 +08:00
Wesley Liddick
9c1f4b8e72 Merge pull request #1068 from Ethan0x0000/pr/frontend-last24h
feat(frontend): set last 24h as default range in Usage and Dashboard
2026-03-17 09:06:52 +08:00
Wesley Liddick
9857c17631 Merge pull request #1067 from DaydreamCoding/feat/async-backup
feat(backup): 备份/恢复异步化,解决 504 超时
2026-03-17 08:59:36 +08:00
luxiang
7e34bb946f fix(proxy): encode special chars in proxy credentials 2026-03-17 08:40:08 +08:00
clover614
47b748851b fix(auto setup): 修复初始测试连接硬编码问题导致使用自定义数据库测试失败无法执行 auto setup流程 2026-03-17 06:34:20 +08:00
erio
a6f99cf534 refactor(antigravity): unify TestConnection with dispatch retry loop
TestConnection now reuses antigravityRetryLoop instead of a standalone
HTTP loop, gaining credits overages, smart retry, and 429/503 backoff
for free. AccountSwitchError is caught and surfaced as a friendly
message. Also populates RateLimitedModel in TempUnscheduled switch error.

Test fixes:
- Use RATE_LIMIT_EXCEEDED in 503 short-delay test to avoid 60x1s timeout
- Clamp waitDuration=0 instead of 999s to avoid 15s max-wait timeout
- Enhance mockSmartRetryUpstream with repeatLast and body caching
2026-03-17 01:47:08 +08:00
erio
a120a6bc32 fix(ui): remove redundant sub-table header in user breakdown
The expanded user breakdown rows already align with the parent table
columns (Requests, Token, Actual, Standard), so the repeated sub-header
wastes vertical space. Remove the <thead> from UserBreakdownSubTable.
2026-03-17 00:49:43 +08:00
erio
d557d1a190 fix(ui): restore original max-h-48 height for distribution tables 2026-03-17 00:47:45 +08:00
erio
e0286e5085 test(dashboard): add unit tests for user-breakdown API
Handler tests (9 cases): group_id/model/endpoint filters, default
endpoint_type, custom limit, limit clamping, response format,
empty result, no-filter pass-through.

Repository test: resolveEndpointColumn mapping for inbound/upstream/path.
2026-03-17 00:47:33 +08:00
erio
4b41e898a4 feat(dashboard): add per-user drill-down for group, model, and endpoint distributions
Click on a group name, model name, or endpoint name in the distribution
tables to expand and show per-user usage breakdown (requests, tokens,
actual cost, standard cost).

Backend: new GET /admin/dashboard/user-breakdown API with group_id,
model, endpoint, endpoint_type filters.
Frontend: clickable rows with expand/collapse sub-table in all three
distribution charts.
2026-03-17 00:47:20 +08:00
Elysia
668e164793 fix(usage): use real reset header for session window instead of prediction
The 5h window reset time displayed for Setup Token accounts was inaccurate
because UpdateSessionWindow predicted the window end as "current hour + 5h"
instead of reading the actual `anthropic-ratelimit-unified-5h-reset` response
header. This caused the countdown to differ from the official Claude page.

Backend: parse the reset header (Unix timestamp) and use it as the real
window end, falling back to the hour-truncated prediction only when the
header is absent. Also correct stale predictions when a subsequent request
provides the real reset time.

Frontend: add a reactive 60s timer so the reset countdown in
UsageProgressBar ticks down in real-time instead of freezing at the
initial value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 00:13:45 +08:00
Elysia
fa2e6188d0 fix(oauth): extract system-role input items into instructions field
OAuth upstreams (ChatGPT) reject requests containing role:"system" in
the input array with HTTP 400 "System messages are not allowed". Extract
such items before forwarding and merge their content into the top-level
instructions field, prepending to any existing value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 21:20:46 +08:00
Ethan0x0000
7fde9ebbc2 fix: zero expired codex windows in backend, use /usage API as single frontend data source 2026-03-16 21:14:52 +08:00
Ethan0x0000
aef7c3b9bb feat: set last 24 hours as default date range in DashboardView 2026-03-16 21:14:26 +08:00
Ethan0x0000
a0b76bd608 feat: implement last 24 hours date range preset and update filters in UsageView 2026-03-16 21:14:26 +08:00
QTom
c1fab7f8d8 feat(backup): 备份/恢复异步化,解决 504 超时
POST /backups 和 POST /backups/:id/restore 改为异步:立即返回 HTTP 202,
后台 goroutine 独立执行 pg_dump → gzip → S3 上传,前端每 2s 轮询状态。

后端:
- 新增 StartBackup/StartRestore 方法,后台 goroutine 不依赖 HTTP 连接
- Graceful shutdown 等待活跃操作完成,启动时清理孤立 running 记录
- BackupRecord 新增 progress/restore_status 字段支持进度和恢复状态追踪

前端:
- 创建备份/恢复后轮询 GET /backups/:id 直到完成或失败
- 标签页切换暂停/恢复轮询,组件卸载清理定时器
- 正确处理 409(备份进行中)和轮询超时

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 20:22:10 +08:00
Wesley Liddick
f42c8f2abe Merge pull request #1062 from kunish/fix/antigravity-stream-keepalive
fix(antigravity): add stream keepalive to prevent connection drops
2026-03-16 19:57:13 +08:00
shaw
aa5846b282 fix(docker): resolve /app/data permission denied on volume mounts
Docker named volumes and host bind-mounts may be owned by root,
causing "open data/model_pricing.sha256: permission denied" when
the container runs as the non-root sub2api user.

Add an entrypoint script that fixes /app/data ownership before
dropping to sub2api via su-exec. Replace USER directive with the
entrypoint approach across all three Dockerfiles and update both
GoReleaser configs to include the script in Docker build contexts.
2026-03-16 19:52:14 +08:00
Wesley Liddick
594a0ade38 Merge pull request #1063 from touwaeriol/fix/usage-label-semantic
fix(i18n): correct usage label from "Total" to "Last 30d"
2026-03-16 19:04:36 +08:00
erio
d45cc23171 fix(i18n): correct usage label from "Total" to "Last 30d"
The usage stats query defaults to a 30-day rolling window, but the
UI label said "Total"/"累计" implying lifetime aggregation. Rename
to "Last 30d"/"近30天" so the label matches the actual query semantics.

Closes #1060
2026-03-16 18:25:41 +08:00
kunish
d795734352 fix(antigravity): add stream keepalive to prevent connection drops
Antigravity streaming handlers were missing the keepalive mechanism
that exists in the standard gateway, causing proxy/CDN idle timeouts
to break connections during long thinking phases (e.g. claude-opus-4-6).
This resulted in truncated responses with missing tool calls.

Add StreamKeepaliveInterval support to all three Antigravity streaming
paths: Claude SSE, Gemini SSE, and upstream passthrough.
2026-03-16 17:37:15 +08:00
Wesley Liddick
4da9fdd1d5 Merge pull request #1058 from Ethan0x0000/main
fix(admin/accounts): make usage window refresh deterministic and restore missing stats
2026-03-16 17:06:13 +08:00
Wesley Liddick
6b218caa21 Merge pull request #1053 from touwaeriol/chore/antigravity-ua-1.20.5
chore(antigravity): bump default User-Agent version to 1.20.5
2026-03-16 16:57:22 +08:00
shaw
5c138007d0 chore: update docs 2026-03-16 16:56:42 +08:00
Ethan0x0000
1acfc46f46 fix: always show usage stats for OpenAI OAuth and hide zero-value badges
- Simplify OpenAI rendering: always fetch /usage, prefer fetched data over
  codex snapshot (snapshot serves as loading placeholder only)
- Remove dead code: preferFetchedOpenAIUsage, isOpenAICodexSnapshotStale,
  and unreachable template branch
- Add today-stats support for key accounts (req/tokens/A/U badges)
- Use formatCompactNumber for consistent number formatting
- Add A/U badge titles for clarity
- Filter zero-value window stats in UsageProgressBar to avoid empty badges
- Update tests to match new fetched-data-first behavior
2026-03-16 16:23:13 +08:00
Ethan0x0000
fbffb08aae feat: add today-stats and manual refresh token propagation to usage cells
- Pass todayStats/todayStatsLoading to AccountUsageCell for key accounts
- Propagate usageManualRefreshToken to force usage reload on explicit refresh
- Refresh today stats when toggling usage/today_stats columns visible
2026-03-16 16:23:00 +08:00
Ethan0x0000
8640a62319 refactor: extract formatCompactNumber util and add last_used_at to refresh key
- Add formatCompactNumber() for consistent large-number formatting (K/M/B)
- Include last_used_at in OpenAI usage refresh key for better change detection
- Add .gitattributes eol=lf rules for frontend source files
2026-03-16 16:22:51 +08:00
Ethan0x0000
fa782e70a4 fix: always attach OpenAI 5h/7d window stats regardless of zero values
Removes hasMeaningfulWindowStats guard so the /usage endpoint consistently
returns WindowStats for both time windows. The frontend now controls
zero-value display filtering at the component level.
2026-03-16 16:22:42 +08:00
Ethan0x0000
afd72abc6e fix: allow empty extra payload to clear account quota limits
UpdateAccount previously required len(input.Extra) > 0, causing explicit
empty payloads (extra:{}) to be silently skipped. Change condition to
input.Extra != nil so clearing quota keys actually persists.
2026-03-16 16:22:31 +08:00
erio
71f72e167e chore(antigravity): bump default User-Agent version to 1.20.5 2026-03-16 15:47:32 +08:00
Wesley Liddick
6595c7601e Merge pull request #1050 from touwaeriol/fix/rate-limit-redis-window-reset
fix(billing): add window expiration check to Redis rate limit Lua script
2026-03-16 14:17:41 +08:00
erio
67c0506290 fix(billing): add window expiration check to Redis rate limit Lua script
The updateRateLimitUsageScript Lua script previously performed
unconditional HINCRBYFLOAT on all usage counters without checking
whether the rate limit window had expired. This caused usage to
accumulate across window boundaries in Redis while the DB correctly
reset on expiration, leading to incorrect 429 rate limiting that
could persist for up to 24 hours.

The Lua script now checks each window timestamp before incrementing:
- If the window has expired, usage is reset to the current cost and
  the window timestamp is updated (matching DB-side semantics)
- If the window is still valid, usage is accumulated normally

This also resolves the async race condition where stale HINCRBYFLOAT
tasks from the worker queue could pollute a freshly rebuilt cache
after invalidation, since the script now self-corrects expired windows.

Closes #1049
2026-03-16 13:39:50 +08:00
Wesley Liddick
6447be4534 Merge pull request #1047 from DaydreamCoding/fix/codex-stream-isolation
fix(gateway): 防止 OpenAI Codex 跨用户串流 + WS 连接池条件式 MarkBroken
2026-03-16 11:00:07 +08:00
QTom
3741617ebd fix(gateway): WS 连接池条件式 MarkBroken 防止跨请求串流
正常终端事件(response.completed 等)退出后连接归还复用,
仅异常路径(读写错误、error 事件、客户端断连)MarkBroken 销毁。

Generate 模式:
- 引入 cleanExit 标记,仅在 isTerminalEvent break 时设置 true
- defer 中根据 cleanExit 决定是否 MarkBroken
- 所有异常路径已在各自分支中提前调用 MarkBroken

Ingress 模式:
- 引入 lastTurnClean 标记,sendAndRelay 正常完成时设为 true
- releaseSessionLease 根据 lastTurnClean 决定是否 MarkBroken
- 错误路径重置 lastTurnClean = false
- 客户端断连后 drain 仍保守 MarkBroken(L2916)
2026-03-16 10:50:02 +08:00
QTom
ab4e8b2cf0 fix(gateway): 防止 OpenAI Codex 跨用户串流
根因:多个用户共享同一 OAuth 账号时,conversation_id/session_id 头
未做用户隔离,导致上游 chatgpt.com 将不同用户的请求关联到同一会话。

HTTP SSE 修复:
- 新增 isolateOpenAISessionID(apiKeyID, raw),将 API Key ID 混入
  session 标识符(xxhash),确保不同 Key 的用户产生不同上游会话
- buildUpstreamRequest: OAuth 分支先 Del 客户端透传的 session 头,
  再用隔离值覆盖
- buildUpstreamRequestOpenAIPassthrough: 透传路径同样隔离
- ForwardAsAnthropic: Anthropic Messages 兼容路径同步修复
- buildOpenAIWSHeaders: WS 路径的 OAuth session 头同步隔离
2026-03-16 10:28:51 +08:00
Wesley Liddick
474165d7aa Merge pull request #1043 from touwaeriol/pr/antigravity-credits-overages
feat: Antigravity AI Credits overages handling & balance display
2026-03-16 09:22:19 +08:00
Wesley Liddick
94e067a2e2 Merge pull request #1040 from 0xObjc/codex/fix-user-spending-ranking-others
fix(admin): polish spending ranking and usage defaults
2026-03-16 09:19:46 +08:00
Wesley Liddick
4293c89166 Merge pull request #1036 from Ethan0x0000/feat/usage-endpoint-distribution
fix: record endpoint info for all API surfaces & unify normalization via middleware
2026-03-16 09:17:32 +08:00
Wesley Liddick
ec82c37da5 Merge pull request #1042 from touwaeriol/feat/unified-oauth-refresh-api
feat: unified OAuth token refresh API with distributed locking
2026-03-16 09:00:42 +08:00
erio
552a4b998a fix: resolve golangci-lint issues (gofmt, errcheck)
- Fix gofmt alignment in admin_service.go and trailing newline in
  antigravity_credits_overages.go
- Suppress errcheck for fmt.Sscanf in client.go GetMinimumAmount
2026-03-16 05:15:27 +08:00
erio
0d2061b268 fix: remove ClaudeMax references not yet in upstream/main
Remove SimulateClaudeMaxEnabled field and related logic from
admin_service.go, and remove applyClaudeMaxCacheBillingPolicyToUsage,
applyClaudeMaxNonStreamingRewrite, setupClaudeMaxStreamingHook calls
from antigravity_gateway_service.go. These symbols are not yet
available in upstream/main.
2026-03-16 05:01:42 +08:00
erio
8a260defc2 refactor: replace sync.Map credits state with AICredits rate limit key
Replace process-memory sync.Map + per-model runtime state with a single
"AICredits" key in model_rate_limits, making credits exhaustion fully
isomorphic with model-level rate limiting.

Scheduler: rate-limited accounts with overages enabled + credits available
are now scheduled instead of excluded.

Forwarding: when model is rate-limited + credits available, inject credits
proactively without waiting for a 429 round trip.

Storage: credits exhaustion stored as model_rate_limits["AICredits"] with
5h duration, reusing SetModelRateLimit/isRateLimitActiveForKey.

Frontend: show credits_active (yellow ) when model rate-limited but
credits available, credits_exhausted (red) when AICredits key active.

Tests: add unit tests for shouldMarkCreditsExhausted, injectEnabledCreditTypes,
clearCreditsExhausted, and update existing overages tests.
2026-03-16 04:58:58 +08:00
SilentFlower
e14c87597a feat: simplify AI Credits display logic and enhance UI presentation 2026-03-16 04:58:46 +08:00
SilentFlower
f3f19d35aa feat: enhance Antigravity account overages handling and improve UI credit display 2026-03-16 04:58:35 +08:00
SilentFlower
ced90e1d84 feat: add AI Credits balance handling and update model status indicators 2026-03-16 04:58:23 +08:00
SilentFlower
17e4033340 feat: implement resolveCreditsOveragesModelKey function to stabilize model key resolution for credit overages 2026-03-16 04:58:12 +08:00
erio
044d3a013d fix: suppress SA4006 unused value warning in Path A branch 2026-03-16 01:38:06 +08:00
erio
1fc9dd7b68 feat: unified OAuth token refresh API with distributed locking
Introduce OAuthRefreshAPI as the single entry point for all OAuth token
refresh operations, eliminating the race condition where background
refresh and inline refresh could simultaneously use the same
refresh_token (fixes #1035).

Key changes:
- Add OAuthRefreshExecutor interface extending TokenRefresher with CacheKey
- Add OAuthRefreshAPI.RefreshIfNeeded with lock → DB re-read → double-check flow
- Add ProviderRefreshPolicy / BackgroundRefreshPolicy strategy types
- Simplify all 4 TokenProviders to delegate to OAuthRefreshAPI
- Rewrite TokenRefreshService.refreshWithRetry to use unified API path
- Add MergeCredentials and BuildClaudeAccountCredentials helpers
- Add 40 unit tests covering all new and modified code paths
2026-03-16 01:31:54 +08:00
Peter
8147866c09 fix(admin): polish spending ranking and usage defaults 2026-03-16 00:17:47 +08:00
Ethan0x0000
7bd1972f94 refactor: migrate all handlers to shared endpoint normalization middleware
- Apply InboundEndpointMiddleware to all gateway route groups
- Replace normalizedOpenAIInboundEndpoint/normalizedOpenAIUpstreamEndpoint and normalizedGatewayInboundEndpoint/normalizedGatewayUpstreamEndpoint with GetInboundEndpoint/GetUpstreamEndpoint
- Remove 4 old constants and 4 old normalization functions (-70 lines)
- Migrate existing endpoint normalization test to new API

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-15 22:13:42 +08:00
Ethan0x0000
2c9dcfe27b refactor: add unified endpoint normalization infrastructure
Introduce endpoint.go with shared constants, NormalizeInboundEndpoint, DeriveUpstreamEndpoint, InboundEndpointMiddleware, and context helpers. This replaces the two separate normalization implementations (OpenAI and Gateway) with a single source of truth. Includes comprehensive test coverage.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-15 22:13:31 +08:00
Ethan0x0000
1b79b0f3ff feat: add InboundEndpoint/UpstreamEndpoint fields to non-OpenAI usage records
Extend RecordUsageInput and RecordUsageLongContextInput structs with InboundEndpoint and UpstreamEndpoint so that Claude, Gemini, and Sora handlers can record endpoint info alongside OpenAI handlers.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-15 22:13:22 +08:00
Ethan0x0000
c637e6cf31 fix: use half-open date ranges for DST-safe usage queries
Replace t.Add(24*time.Hour - time.Nanosecond) with t.AddDate(0, 0, 1) and use SQL < instead of <= for end-of-day boundaries. This avoids edge-case misses around DST transitions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-15 22:13:12 +08:00
Wesley Liddick
d3a9f5bb88 Merge pull request #1027 from touwaeriol/feat/ignore-insufficient-balance-errors
feat(ops): add ignore insufficient balance errors toggle and extract error constants
2026-03-15 19:10:18 +08:00
Wesley Liddick
7eb0415a8a Merge pull request #1028 from IanShaw027/fix/open-issues-cleanup
fix: 修复多个issues - Gemini schema 兼容性、批量编辑白名单、Docker 工具支持和限额字段处理Fix/open issues cleanup
2026-03-15 19:09:49 +08:00
erio
bdbc8fa08f fix(ops): align constant declarations for gofmt compliance 2026-03-15 18:55:14 +08:00
erio
63f3af0f94 fix(ops): match "insufficient account balance" in error filter
The upstream Gemini API returns "Insufficient account balance" which
doesn't contain the substring "insufficient balance". Add explicit
match for the full phrase to ensure the filter works correctly.
2026-03-15 18:45:48 +08:00
IanShaw027
686f890fbf style: 修复 gofmt 格式问题 2026-03-15 18:42:32 +08:00
shaw
220fbe6544 fix: 恢复 UsageProgressBar 中被意外移除的窗口统计数据展示
commit 0debe0a8 在修复 OpenAI WS 用量窗口刷新问题时,意外删除了
UsageProgressBar 中的 window stats 渲染逻辑和格式化函数。

恢复进度条上方的统计行(requests, tokens, account cost, user cost)
及对应的 4 个格式化 computed 属性。
2026-03-15 18:29:23 +08:00
shaw
ae44a94325 fix: 重置密码功能新增UI配置发送邮件域名 2026-03-15 17:52:29 +08:00
IanShaw
3718d6dcd4 Merge branch 'Wei-Shaw:main' into fix/open-issues-cleanup 2026-03-15 17:49:20 +08:00
IanShaw027
90b3838173 fix: 移除 Gemini 不支持的 patternProperties 字段 #795 2026-03-15 17:46:58 +08:00
IanShaw027
19d3ecc76f fix: 修复批量编辑账号时模型白名单显示与实际不一致的问题 #982
修复批量编辑账号时,UI 显示的是 plain 模型名(如 GPT-5),但实际落库的是 dated 模型名的问题。

核心改动:
1. 批量编辑白名单不再使用 BulkEditAccountModal.vue 中手写的过期模型列表
   - 移除了 allModels 和 presetMappings 的硬编码列表(共 200+ 行)
   - 直接复用 ModelWhitelistSelector.vue 组件

2. ModelWhitelistSelector 组件支持多平台联合过滤
   - 新增 platforms 属性支持传入多个平台
   - 添加 normalizedPlatforms 计算属性统一处理单平台和多平台场景
   - availableOptions 根据选中的多个平台动态联合过滤模型列表
   - fillRelated 功能支持一次性填充多个平台的相关模型

3. 模型映射预设改为动态生成
   - filteredPresets 改用 getPresetMappingsByPlatform 从统一模型源按平台动态生成
   - 不再依赖弹窗中的手写预设列表

现在的行为:
- UI 显示什么模型,勾选什么模型,传给后端的就是什么模型
- 彻底解决了批量编辑链路上"显示与实际不一致"的问题
- 模型列表和映射预设始终与系统定义保持同步
2026-03-15 17:46:58 +08:00
IanShaw027
6fba4ebb13 fix: 在 Dockerfile.goreleaser 中添加 pg_dump 和 psql 工具 #1002
为了支持容器内的数据库备份和恢复功能,在运行时镜像中添加 PostgreSQL 客户端工具。

变更内容:
- 使用多阶段构建从 postgres:18-alpine 镜像复制 pg_dump 和 psql 二进制文件
- 添加必要的依赖库(libpq, zstd-libs, lz4-libs, krb5-libs, libldap, libedit)
- 升级基础镜像到 alpine:3.21
- 复制 libpq.so.5 共享库以确保工具正常运行

这样可以在运行时容器中直接执行数据库备份和恢复操作,无需访问 Docker socket。
2026-03-15 17:46:58 +08:00
IanShaw027
c31974c913 fix: 兼容部分限额字段为空的情况 #1021
修复在填写限额时,如果不填写完整的三个限额额度(日限额、周限额、月限额)就会报错的问题。

变更内容:
- 后端:添加 optionalLimitField 类型处理空值和空字符串,兼容部分限额字段为空的情况
- 前端:添加 normalizeOptionalLimit 函数规范化限额输入,将空值、空字符串和无效数字统一处理为 null
2026-03-15 17:46:58 +08:00
erio
6177fa5dd8 fix(i18n): correct insufficient balance error hint text
Remove misleading "upstream" wording - the error is about client API key
user balance, not upstream account balance.
2026-03-15 17:41:51 +08:00
erio
cfe72159d0 feat(ops): add ignore insufficient balance errors toggle and extract error constants
- Add 5th error filter switch IgnoreInsufficientBalanceErrors to suppress
  upstream insufficient balance / insufficient_quota errors from ops log
- Extract hardcoded error strings into package-level constants for
  shouldSkipOpsErrorLog, normalizeOpsErrorType, classifyOpsPhase, and
  classifyOpsIsBusinessLimited
- Define ErrNoAvailableAccounts sentinel error and replace all
  errors.New("no available accounts") call sites
- Update tests to use require.ErrorIs with the sentinel error
2026-03-15 17:26:18 +08:00
Wesley Liddick
8321e4a647 Merge pull request #1023 from YanzheL/fix/claude-output-effort-logging
fix: extract and log Claude output_config.effort in usage records
2026-03-15 16:45:37 +08:00
Wesley Liddick
3084330d0c Merge pull request #1019 from Ethan0x0000/feat/usage-endpoint-distribution
feat: add endpoint metadata and usage endpoint distribution insights
2026-03-15 16:42:03 +08:00
Wesley Liddick
b566649e79 Merge pull request #1025 from touwaeriol/fix/rate-limit-nil-window-reset
fix(billing): treat nil rate limit window as expired to prevent usage accumulation
2026-03-15 16:33:14 +08:00
Wesley Liddick
10a6180e4a Merge pull request #1026 from touwaeriol/fix/group-quota-clear
fix(billing): allow clearing group quota limits and treat 0 as zero-limit
2026-03-15 16:33:00 +08:00
Wesley Liddick
cbe9e78977 Merge pull request #1007 from StarryKira/fix/streaming-failover-corruption
fix(gateway): 防止流式 failover 拼接腐化导致客户端收到双 message_start fix issue #991
2026-03-15 16:29:31 +08:00
Wesley Liddick
74145b1f39 Merge pull request #1017 from SsageParuders/fix/bedrock-account-quota
fix: Bedrock 账户配额限制不生效
2026-03-15 16:28:42 +08:00
Elysia
359e56751b 增加测试 2026-03-15 16:21:49 +08:00
erio
5899784aa4 fix(billing): allow clearing group quota limits and treat 0 as zero-limit
Previously, v-model.number produced "" when input was cleared, causing
JSON decode errors on the backend. Also, normalizeLimit treated 0 as
"unlimited" which prevented setting a zero quota. Now "" is converted
to null (unlimited) in frontend, and 0 is preserved as a valid limit.

Closes Wei-Shaw/sub2api#1021
2026-03-15 16:15:15 +08:00
erio
9e8959c56d fix(billing): treat nil rate limit window as expired to prevent usage accumulation
When Redis cache is populated from DB with a NULL window_1d_start, the
Lua increment script only updates usage counters without setting window
timestamps. IsWindowExpired(nil) previously returned false, so the
accumulated usage was never reset across time windows, effectively
turning usage_1d into a lifetime counter. Once this exceeded
rate_limit_1d the key was incorrectly blocked with "日限额已用完".

Fixes Wei-Shaw/sub2api#1022
2026-03-15 14:04:13 +08:00
YanzheL
1bff2292a6 fix: extract and log Claude output_config.effort in usage records
Claude's output_config.effort parameter (low/medium/high/max) was not
being extracted from requests or logged in the reasoning_effort column
of usage logs. Only the OpenAI path populated this field.

Changes:
- Extract output_config.effort in ParseGatewayRequest
- Add ReasoningEffort field to ForwardResult
- Populate reasoning_effort in both RecordUsage and RecordUsageWithLongContext
- Guard against overwriting service-set effort values in handler
- Update stale comments that described reasoning_effort as OpenAI-only
- Add unit tests for extraction, normalization, and persistence
2026-03-15 12:55:37 +08:00
Ethan0x0000
cf9247754e test: fix usage repo stubs for unit builds 2026-03-15 12:51:34 +08:00
Ethan0x0000
eefab15958 feat: 完善使用记录端点可观测性与分布统计
将入站、上游与路径三类端点分布统一到使用记录页的一致化卡片交互中,并补齐端点元数据与统计链路,提升排障与流量分析效率。
2026-03-15 11:26:42 +08:00
Elysia
0e23732631 fix(gateway): 防止流式 failover 拼接腐化导致客户端收到双 message_start
当上游在 SSE 流中途返回 event:error 时,handleStreamingResponse 已将
部分 SSE 事件写入客户端,但原先的 failover 逻辑仍会切换到下一个账号
并写入完整流,导致客户端收到两个 message_start 进而产生 400 错误。

修复方案:在每次 Forward 调用前记录 c.Writer.Size(),若 Forward 返回
UpstreamFailoverError 后 writer 字节数增加,说明 SSE 内容已不可撤销地
发送给客户端,此时直接调用 handleFailoverExhausted 发送 SSE error 事件
终止流,而非继续 failover。

Ping-only 场景不受影响:slot 等待期的 ping 字节在 Forward 前后相等,
正常 failover 流程照常进行。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:49:23 +08:00
SsageParuders
37c044fb4b fix: Bedrock 账户配额限制不生效,配额计数器始终为 $0.00
applyUsageBillingEffects() 中配额更新条件仅检查了 AccountTypeAPIKey,
遗漏了 AccountTypeBedrock,导致 Bedrock 账户的配额计数器永远不递增。
扩展条件以同时支持 APIKey 和 Bedrock 类型。

同时在前端账户筛选下拉框中添加 AWS Bedrock 选项。
2026-03-14 22:47:44 +08:00
shaw
6da5fa01b9 fix(frontend): 修复运维设置对话框保存按钮始终禁用的问题
后端默认 alert.enabled=true 但 recipients 为空,前端验证将其视为
错误并阻断保存按钮。移除该阻断性验证,改为保存时自动禁用无收件人
的邮件通知配置。
2026-03-14 20:39:29 +08:00
shaw
616930f9d3 refactor(frontend): 将备份和数据管理页面合并为设置页的标签页
将独立的 /admin/backup 和 /admin/data-management 页面整合到设置页,
作为「备份」和「Sora 存储」标签页,减少侧边栏条目,集中管理配置。

- 移除 BackupView 和 DataManagementView 的 AppLayout 包装
- 在 SettingsView 中以子组件形式嵌入,使用 v-show 切换标签
- 删除独立路由和侧边栏菜单入口
- 备份/数据标签页下隐藏主保存按钮(各自有独立保存)
- 优化标签栏样式适配7个标签,PC端支持细滚动条
- 清理未使用的图标组件和 i18n 键
2026-03-14 20:22:39 +08:00
Wesley Liddick
b9c31fa7c4 Merge pull request #999 from InCerryGit/fix/enc_coot
fix: handle invalid encrypted content error and retry logic.
2026-03-14 19:29:07 +08:00
Wesley Liddick
17b339972c Merge pull request #1000 from touwaeriol/fix/ops-agg-tuning
fix(ops): tune aggregation constants to prevent PG overload
2026-03-14 19:03:03 +08:00
shaw
39f8bd91b9 fix: remove unused saveRecords method to pass lint 2026-03-14 19:01:27 +08:00
Wesley Liddick
aa4e37d085 Merge pull request #966 from GuangYiDing/feat/db-backup-restore
feat: 数据库定时备份与恢复(S3 兼容存储,支持 Cloudflare R2)
2026-03-14 18:58:56 +08:00
erio
f59b66b7d4 fix(ops): tune aggregation constants to prevent PG overload
Increase MAX(bucket_start) query timeout from 3s to 5s to reduce
timeout-induced fallbacks. Shrink backfill window from 30 days to
1 hour so that fallback recomputation stays lightweight instead of
scanning the entire retention range.
2026-03-14 18:47:37 +08:00
InCerry
8f0ea7a02d Merge branch 'main' into fix/enc_coot 2026-03-14 18:46:33 +08:00
Wesley Liddick
a1dc00890e Merge pull request #944 from miraserver/feat/backend-mode
feat: add Backend Mode toggle to disable user self-service
2026-03-14 17:53:54 +08:00
Wesley Liddick
dfbcc363d1 Merge pull request #969 from wucm667/feat/quota-fixed-reset-mode
feat: 账号配额支持固定时间重置模式
2026-03-14 17:52:56 +08:00
Rose Ding
1047f973d5 fix: 按 review 意见重构数据库备份服务(安全性 + 架构 + 健壮性)
1. S3 凭证加密存储:使用 SecretEncryptor (AES-256-GCM) 加密 SecretAccessKey,
   防止备份文件中泄露 S3 凭证,兼容旧的未加密数据
2. 修复 saveRecord 竞态条件:添加 recordsMu 互斥锁保护 records 的 load/save
3. 恢复操作增加服务端验证:handler 层要求重新输入管理员密码,通过 bcrypt
   校验,前端弹出密码输入框
4. pg_dump/psql/S3 操作抽象为接口:定义 DBDumper 和 BackupObjectStore 接口,
   实现放入 repository 层,遵循项目依赖注入架构规范
5. 改为流式处理避免大数据库 OOM:备份时 pg_dump stdout -> gzip -> io.Pipe ->
   S3 upload;恢复时 S3 download -> gzip reader -> psql stdin,不再全量加载
6. loadRecords 区分"无数据"和"数据损坏"场景:JSON 解析失败返回明确错误
7. 添加 18 个核心逻辑单元测试:覆盖加密、并发、流式备份/恢复、错误处理等

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 17:48:21 +08:00
Wesley Liddick
e32977dd73 Merge pull request #997 from SsageParuders/refactor/bedrock-channel-merge
refactor: merge bedrock-apikey into unified bedrock channel with auth_mode
2026-03-14 17:48:01 +08:00
wucm667
b5f78ec1e8 feat: 实现固定时间重置模式的 SQL 表达式,并添加相关单元测试 2026-03-14 17:37:34 +08:00
SsageParuders
e0f290fdc8 style: fix gofmt formatting for account type constants 2026-03-14 17:32:53 +08:00
Wesley Liddick
fc00a4e3b2 Merge pull request #959 from touwaeriol/feat/antigravity-403-detection
feat(antigravity): add 403 forbidden status detection and display
2026-03-14 17:23:22 +08:00
Wesley Liddick
db1f6ded88 Merge pull request #961 from 0xObjc/codex/ops-openai-token-visibility
feat(ops): make OpenAI token stats optional
2026-03-14 17:23:01 +08:00
SsageParuders
4644af2ccc refactor: merge bedrock-apikey into bedrock with auth_mode credential
Consolidate two separate channel types (bedrock + bedrock-apikey) into
a single "AWS Bedrock" channel. Authentication mode is now distinguished
by credentials.auth_mode ("sigv4" | "apikey") instead of separate types.

Backend:
- Remove AccountTypeBedrockAPIKey constant
- IsBedrock() simplified; IsBedrockAPIKey() checks auth_mode
- Add IsAPIKeyOrBedrock() helper to eliminate repeated type checks
- Extend pool mode, quota scheduling, and billing to bedrock
- Add RetryableOnSameAccount to handleBedrockUpstreamErrors
- Add "bedrock" scope to Beta Policy for independent control

Frontend:
- Merge two buttons into one "AWS Bedrock" with auth mode radio
- Badge displays "Anthropic | AWS"
- Pool mode and quota limit UI available for bedrock
- Quota display in account list (usage bars, capacity badges, reset)
- Remove all bedrock-apikey type references
2026-03-14 17:13:30 +08:00
Wesley Liddick
2e3e8687e1 Merge pull request #993 from xvhuan/fix/codex-responses-id-prefix-hotfix-20260314
fix: 止血 Codex/Responses 原生 input id 被误改成 fc_*
2026-03-14 13:54:26 +08:00
ius
ca42a45802 fix: stop rewriting native responses input ids 2026-03-14 13:47:01 +08:00
Wesley Liddick
9350ecb62b Merge pull request #987 from Ethan0x0000/feat-chatcompletions2repsonses-fix
fix: chat compatibility model fallback and reasoning_content output
2026-03-14 13:41:47 +08:00
Wesley Liddick
a4a026e8da Merge pull request #990 from LvyuanW/admin-openai-available-models-fix
fix: respect OpenAI OAuth model mapping in admin available models
2026-03-14 13:33:18 +08:00
Wesley Liddick
342fd03e72 Merge pull request #986 from LvyuanW/openai-model-mapping-fix
fix: honor account model mapping before group fallback
2026-03-14 13:32:26 +08:00
Ethan0x0000
e3f1fd9b63 fix: handle strings.Builder write errors in assistant parsing 2026-03-14 13:12:17 +08:00
InCerry
e4a4dfd038 Merge remote-tracking branch 'origin/main' into fix/enc_coot
# Conflicts:
#	backend/internal/service/openai_gateway_service.go
2026-03-14 13:04:24 +08:00
Wang Lvyuan
a377e99088 fix: remove unused wildcard mapping helper 2026-03-14 12:56:34 +08:00
Wang Lvyuan
1d3d7a3033 fix: respect OpenAI model mapping in admin available models 2026-03-14 12:45:10 +08:00
Wesley Liddick
e7086cb3a3 Merge pull request #988 from LvyuanW/scheduler-snapshot-sync
fix: sync scheduler snapshot on account updates
2026-03-14 12:38:48 +08:00
shaw
4f2a97073e chore: update docs 2026-03-14 12:37:36 +08:00
Wesley Liddick
7407e3b45d Merge pull request #984 from touwaeriol/docs/ecosystem-projects
docs: add iframe integration feature and ecosystem projects section
2026-03-14 12:31:25 +08:00
Wang Lvyuan
01ef7340aa Merge remote-tracking branch 'origin/main' into openai-model-mapping-fix 2026-03-14 12:27:08 +08:00
Wang Lvyuan
1c960d22c1 fix: sync scheduler snapshot on account updates 2026-03-14 12:21:28 +08:00
Ethan0x0000
ece0606fed fix: consolidate chat-completions compatibility fixes
- apply default mapped model only when scheduling fallback is actually used

- preserve reasoning in OpenAI-compatible output via reasoning_content and avoid invalid input function_call ids
2026-03-14 12:12:08 +08:00
InCerry
2666422b99 fix: handle invalid encrypted content error and retry logic. 2026-03-14 11:42:42 +08:00
Wesley Liddick
e6d59216d4 Merge pull request #975 from Ylarod/aws-bedrock
sub2api: add bedrock support
2026-03-14 10:52:24 +08:00
Wang Lvyuan
4e8615f276 fix: honor account model mapping before group fallback 2026-03-14 10:47:31 +08:00
erio
91e4d95660 docs: add iframe integration feature and ecosystem projects section 2026-03-14 03:16:07 +08:00
erio
45456fa24c fix: restore OAuth 401 temp-unschedulable for Gemini, update Antigravity tests
The 403 detection PR changed the 401 handler condition from
`account.Type == AccountTypeOAuth` to
`account.Type == AccountTypeOAuth && account.Platform == PlatformOpenAI`,
which accidentally excluded Gemini OAuth from the temp-unschedulable path.

Fix: use `!= PlatformAntigravity` instead, preserving Gemini behavior
while correctly excluding Antigravity (whose 401 is handled by
applyErrorPolicy's temp_unschedulable_rules).

Update tests to reflect Antigravity's new 401 semantics:
- HandleUpstreamError: Antigravity OAuth 401 now uses SetError
- CheckErrorPolicy: Antigravity 401 second hit stays TempUnscheduled
- DB fallback: split into Gemini (escalates) and Antigravity (stays temp)
2026-03-14 02:21:22 +08:00
Wesley Liddick
4588258d80 Merge pull request #960 from 0xObjc/codex/user-spending-ranking
feat(admin): add user spending ranking dashboard view
2026-03-13 23:06:30 +08:00
Wesley Liddick
c12e48f966 Merge pull request #949 from kunish/fix/remove-done-stop-sequence
fix: remove SSE termination marker from DefaultStopSequences
2026-03-13 22:56:29 +08:00
Wesley Liddick
ec8f50a658 Merge pull request #951 from wanXcode/fix/dashboard-user-trend-label
fix(dashboard): prefer username over email prefix in recent usage chart
2026-03-13 22:56:13 +08:00
Wesley Liddick
99c9191784 Merge pull request #974 from 0xObjc/codex/next-pr-base-20260313
fix(admin): default dashboard date range to today
2026-03-13 22:55:14 +08:00
shaw
6bb02d141f chore: remove accidentally committed PR diagnostic report 2026-03-13 22:52:05 +08:00
Wesley Liddick
07bb2a5f3f Merge pull request #952 from xvhuan/feat/billing-ledger-decouple-usage-log-20260312
feat: 解耦计费正确性与 usage_logs 批量写压
2026-03-13 22:46:09 +08:00
Wesley Liddick
417861a48e Merge pull request #956 from share-wey/main
chore: codex transform fixes and feature compatibility
2026-03-13 22:36:29 +08:00
Wesley Liddick
b7e878de64 Merge pull request #980 from touwaeriol/feat/redeem-subscription-support
feat(redeem): support subscription type in create-and-redeem API
2026-03-13 22:15:33 +08:00
erio
05edb5514b feat(redeem): support subscription type in create-and-redeem API
Add group_id and validity_days fields to CreateAndRedeemCodeRequest,
enabling subscription-type redemption codes to be created and redeemed
in a single API call.

- Type defaults to "balance" when omitted for backward compatibility
- Subscription type requires group_id (non-nil) and validity_days (>0)
- Existing balance/concurrency callers are unaffected
2026-03-13 21:26:46 +08:00
Ylarod
e90ec847b6 fix lint 2026-03-13 19:15:27 +08:00
erio
6344fa2a86 feat(antigravity): add 403 forbidden status detection, classification and display
Backend:
- Detect and classify 403 responses into three types:
  validation (account needs Google verification),
  violation (terms of service / banned),
  forbidden (generic 403)
- Extract verification/appeal URLs from 403 response body
  (structured JSON parsing with regex fallback)
- Add needs_verify, is_banned, needs_reauth, error_code fields
  to UsageInfo (omitempty for zero impact on other platforms)
- Handle 403 in request path: classify and permanently set account error
- Save validation_url in error_message for degraded path recovery
- Enrich usage with account error on both success and degraded paths
- Add singleflight dedup for usage requests with independent context
- Differentiate cache TTL: success/403 → 3min, errors → 1min
- Return degraded UsageInfo instead of HTTP 500 on quota fetch errors

Frontend:
- Display forbidden status badges with color coding (red for banned,
  amber for needs verification, gray for generic)
- Show clickable verification/appeal URL links
- Display needs_reauth and degraded error states in usage cell
- Add Antigravity tier label badge next to platform type

Tests:
- Comprehensive unit tests for classifyForbiddenType (7 cases)
- Unit tests for extractValidationURL (8 cases including unicode escapes)
- Integration test for FetchQuota forbidden path
2026-03-13 18:22:45 +08:00
Connie Borer
7e288acc90 Merge branch 'Wei-Shaw:main' into main 2026-03-13 17:28:14 +08:00
Peter
29b0e4a8a5 feat(ops): allow hiding alert events 2026-03-13 17:18:04 +08:00
Peter
27ff222cfb fix(admin): default dashboard date range to today 2026-03-13 17:02:54 +08:00
Ylarod
11f7b83522 sub2api: add bedrock support 2026-03-13 17:00:16 +08:00
Rose Ding
f7177be3b6 fix: golangci-lint 修复(gofmt 格式化 + errcheck 返回值检查)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:01:05 +08:00
Rose Ding
875b417fde fix: 补充 wire_gen_test.go 中 provideCleanup 缺少的 backupSvc 参数
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:53:46 +08:00
wucm667
2573107b32 refactor: 将 ComputeQuotaResetAt 和 ValidateQuotaResetConfig 函数中的 map 类型从 map[string]interface{} 修改为 map[string]any 2026-03-13 11:44:49 +08:00
wucm667
5b85005945 feat: 账号配额支持固定时间重置模式
- 后端新增 rolling/fixed 两种配额重置模式,支持日配额和周配额
- fixed 模式下可配置重置时刻(小时)、重置星期几(周配额)及时区(IANA)
- 在 account_repo.go 中使用 SQL 表达式适配两种模式的过期判断与重置时间推进
- 新增 ComputeQuotaResetAt / ValidateQuotaResetConfig 等辅助函数
- DTO 层新增相关字段并在 mappers 中完整映射
- 前端 QuotaLimitCard 新增 rolling/fixed 切换 UI、时区选择器
- CreateAccountModal / EditAccountModal 透传新配置字段
- i18n(zh/en)同步新增相关翻译词条
2026-03-13 11:12:37 +08:00
Wesley Liddick
1ee984478f Merge pull request #957 from touwaeriol/feat/group-rate-multipliers-modal
feat(groups): add rate multipliers management modal
2026-03-13 11:11:13 +08:00
Wesley Liddick
fd693dc526 Merge pull request #967 from StarryKira/fix/admin-reset-quota-monthly
fix: 管理员重置配额补全 monthly 字段并修复 ristretto 缓存异步问题 fix issue #964
2026-03-13 11:10:47 +08:00
haruka
e73531ce9b fix: 管理员重置配额补全 monthly 字段并修复 ristretto 缓存异步问题
- 后端 handler:ResetSubscriptionQuotaRequest 新增 Monthly 字段,
  验证逻辑扩展为 daily/weekly/monthly 至少一项为 true
- 后端 service:AdminResetQuota 新增 resetMonthly 参数,
  调用 ResetMonthlyUsage;重置后追加 subCacheL1.Wait(),
  保证 ristretto Del() 的异步删除立即生效,消除重置后
  /v1/usage 返回旧用量数据的竞态窗口
- 后端测试:更新存量测试用例匹配新签名,补充
  TestAdminResetQuota_ResetMonthlyOnly /
  TestAdminResetQuota_ResetMonthlyUsageError 两个新用例
- 前端 API:resetQuota options 类型新增 monthly: boolean
- 前端视图:confirmResetQuota 改为同时重置 daily/weekly/monthly
- i18n:中英文确认提示文案更新,提及每月配额

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 10:39:35 +08:00
Rose Ding
53ad1645cf feat: 数据库定时备份与恢复(S3 兼容存储,支持 Cloudflare R2)
新增管理员专属的数据库备份与恢复功能:
- 全量 PostgreSQL 备份(pg_dump),gzip 压缩后上传到 S3 兼容存储
- 支持手动备份和 cron 定时备份
- 支持从备份恢复(psql --single-transaction)
- 备份文件自动过期清理(默认 14 天)
- 前端完整管理页面(S3 配置、定时配置、备份列表、恢复/下载/删除)
- 内置 Cloudflare R2 配置教程弹窗
- Dockerfile 从 postgres 镜像多阶段复制 pg_dump/psql,确保版本一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:38:19 +08:00
Wesley Liddick
ecea13757b Merge pull request #955 from DaydreamCoding/feat/gpt-training-off
feat: GPT Private设置数据不用于训练
2026-03-13 09:12:33 +08:00
Peter
af9c4a7dd0 feat(ops): make openai token stats optional 2026-03-13 04:11:58 +08:00
Peter
80d8d6c3bc feat(admin): add user spending ranking dashboard view 2026-03-13 03:43:03 +08:00
erio
d648811233 feat(groups): add rate multipliers management modal
Add a dedicated modal in group management for viewing, adding, editing,
and deleting per-user rate multipliers within a group.

Backend:
- GET /admin/groups/:id/rate-multipliers - list entries with user details
- PUT /admin/groups/:id/rate-multipliers - batch sync (full replace)
- DELETE /admin/groups/:id/rate-multipliers - clear all entries
- Repository: GetByGroupID, SyncGroupRateMultipliers methods on
  user_group_rate_multipliers table (same table as user-side rates)

Frontend:
- New GroupRateMultipliersModal component with:
  - User search and add with email autocomplete
  - Editable rate column with local edit mode (cancel/save)
  - Batch adjust: multiply all rates by a factor
  - Clear all (local operation, requires save to persist)
  - Pagination (10/20/50 per page)
  - Platform icon with brand colors in group info bar
  - Unsaved changes indicator with revert option
- Unit tests for all three backend endpoints
2026-03-12 23:37:36 +08:00
QTom
34695acb85 fix: 移除账号导入时同步调用 disableOpenAITraining,避免网络超时导致导入失败
privacy_mode 改为由 TokenRefreshService 在 token 刷新后异步补设。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:36:25 +08:00
QTom
a63de12182 feat: GPT 隐私模式 + no-train 前端展示优化 2026-03-12 21:24:01 +08:00
yexueduxing
f16910d616 chore: codex transform fixes and feature compatibility 2026-03-12 20:52:35 +08:00
ius
64b3f3cec1 test: relocate best-effort usage log stub 2026-03-12 18:43:37 +08:00
ius
6a685727d0 fix: harden usage billing idempotency and backpressure 2026-03-12 18:38:09 +08:00
ius
32d25f76fc fix: respect preconfigured usage log batch channels 2026-03-12 17:44:57 +08:00
wanXcode
69cafe8674 fix(dashboard): prefer username in user usage trend 2026-03-12 17:42:41 +08:00
ius
18ba8d9166 fix: stabilize repository integration paths 2026-03-12 17:42:41 +08:00
ius
e97fd7e81c test: align oauth passthrough stream expectations 2026-03-12 17:22:01 +08:00
kunish
cdb64b0d33 fix: remove SSE termination marker from DefaultStopSequences
The SSE stream termination marker string was incorrectly included in
DefaultStopSequences, causing Gemini to prematurely stop generating
output whenever the model produced text containing that marker.

The SSE-level protocol filtering in stream_transformer.go already
handles this marker correctly; it should not be a stop sequence for
the model's text generation.
2026-03-12 17:10:01 +08:00
ius
8d4d3b03bb fix: remove unused gateway usage helpers 2026-03-12 17:08:57 +08:00
ius
addefe79e1 fix: align docker health checks with runtime image 2026-03-12 17:03:21 +08:00
ius
b764d3b8f6 Merge remote-tracking branch 'origin/main' into feat/billing-ledger-decouple-usage-log-20260312 2026-03-12 16:53:28 +08:00
ius
611fd884bd feat: decouple billing correctness from usage log batching 2026-03-12 16:53:18 +08:00
Wesley Liddick
826090e099 Merge pull request #946 from StarryKira/antigravity-gemini-thought-signature-fix
fix Antigravity gemini thought signature fix
2026-03-12 13:51:46 +08:00
Wesley Liddick
7399de6ecc Merge pull request #938 from xvhuan/fix/account-extra-scheduler-pressure-20260311
精准收紧 accounts.extra 观测字段触发的调度重建
2026-03-12 13:51:00 +08:00
haruka
25cb5e7505 fix 第一次 400,第二次触发切账号信号 2026-03-12 11:30:53 +08:00
ius
5c13ec3121 Fix lint after rebasing PR #938 branch 2026-03-12 11:20:59 +08:00
ius
d8aff3a7e3 Merge origin/main into fix/account-extra-scheduler-pressure-20260311 2026-03-12 11:12:01 +08:00
haruka
f44927b9f8 add test for fix #935 2026-03-12 11:04:14 +08:00
Wesley Liddick
c0110cb5af Merge pull request #941 from CoolCoolTomato/main
fix: 修复gpt-5.2以上模型映射到gpt-5.2以下时verbosity参数引发的报错
2026-03-12 09:35:09 +08:00
Wesley Liddick
1f8e1142a0 Merge pull request #932 from 0xObjc/codex/usage-view-charts
feat(admin): add metric toggle to usage charts
2026-03-12 09:32:40 +08:00
Wesley Liddick
1e51de88d6 Merge pull request #937 from lxohi/fix/anthropic-stream-keepalive
fix: 为 Anthropic Messages API 流式转发添加下游 keepalive ping
2026-03-12 09:30:18 +08:00
Wesley Liddick
30995b5397 Merge pull request #936 from xvhuan/fix/ops-write-pressure-20260311
降低 ops_error_logs 与 scheduler_outbox 的数据库写放大
2026-03-12 09:28:34 +08:00
Wesley Liddick
eb60f67054 Merge pull request #933 from xvhuan/fix/dashboard-read-pressure-20260311
降低 admin/dashboard 读路径压力,避免 snapshot-v2 并发击穿
2026-03-12 09:28:14 +08:00
Wesley Liddick
78193ceec1 Merge pull request #931 from xvhuan/fix/db-write-amplification-20260311
降低 quota 与 Codex 快照热路径的数据库写放大
2026-03-12 09:27:34 +08:00
Wesley Liddick
f0e08e7687 Merge pull request #930 from GuangYiDing/feat/gemini-25-flash-image-support
feat: 修复 Gemini 生图接口并新增前端生图测试能力
2026-03-12 09:27:19 +08:00
Wesley Liddick
10b8259259 Merge pull request #909 from StarryKira/feature/admin-reset-subscription-quota
Feature/管理员可以重置账号额度
2026-03-12 09:26:47 +08:00
John Doe
6826149a8f feat: add Backend Mode toggle to disable user self-service
Add a system-wide "Backend Mode" that disables user self-registration
and self-service while keeping admin panel and API gateway fully
functional. When enabled, only admin can log in; all user-facing
routes return 403.

Backend:
- New setting key `backend_mode_enabled` with atomic cached reads (60s TTL)
- BackendModeUserGuard middleware blocks non-admin authenticated routes
- BackendModeAuthGuard middleware blocks registration/password-reset auth routes
- Login/Login2FA/RefreshToken handlers reject non-admin when enabled
- TokenPairWithUser struct for role-aware token refresh
- 20 unit tests (middleware + service layer)

Frontend:
- Router guards redirect unauthenticated users to /login
- Admin toggle in Settings page
- Login page hides register link and footer in backend mode
- 9 unit tests for router guard logic
- i18n support (en/zh)

27 files changed, 833 insertions(+), 17 deletions(-)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 02:42:57 +03:00
CoolCoolTomato
eb0b77bf4d fix: 修复流水线golangci-lint 的 errcheck 2026-03-11 22:56:20 +08:00
shaw
9d81467937 refactor: 重构 Chat Completions 端点,采用类型安全的 Responses API 转换
将 /v1/chat/completions 端点从 ResponseWriter 劫持模式重构为独立的
类型安全转换路径,与 Anthropic Messages 端点架构对齐:

- 在 apicompat 包新增 Chat Completions 完整类型定义和双向转换器
- 新增 ForwardAsChatCompletions service 方法,走 Responses API 上游
- Handler 改为独立的账号选择/failover 循环,不再劫持 Responses handler
- 提取 handleCompatErrorResponse 为 Chat Completions 和 Messages 共用
- 删除旧的 forwardChatCompletions 直传路径及相关死代码
2026-03-11 22:15:32 +08:00
CoolCoolTomato
fd8ccaf01a fix: 修复gpt-5.2以上模型映射到gpt-5.2以下时verbosity参数引发的报错 2026-03-11 21:12:07 +08:00
ius
c9debc50b1 Batch usage log writes in repository 2026-03-11 20:29:48 +08:00
ius
2b30e3b6d7 Reduce scheduler rebuilds on neutral extra updates 2026-03-11 19:16:19 +08:00
amberwarden
6e90ec6111 fix: 为 Anthropic Messages API 流式转发添加下游 keepalive ping
Anthropic Messages API 的流式转发路径(gateway_service.go)在上游长时间
无数据时(如 Opus extended thinking 阶段)不会向下游发送任何内容,导致
Cloudflare Tunnel 等代理因连接空闲而断开。

复用已有的 StreamKeepaliveInterval 配置(默认 10 秒),在 select 循环中
添加 keepalive 分支,定时发送 Anthropic 原生格式的 ping 事件保活,与
OpenAI 兼容路径的实现模式保持一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:43:03 +08:00
Wesley Liddick
8dd38f4775 Merge pull request #926 from 7976723/feat/chat-completions-compat-v2
feat: 添加 OpenAI Chat Completions 兼容端点(基于 #648,修复编译错误和运行时 panic)
2026-03-11 17:42:03 +08:00
ius
fbd73f248f Fix ops write pressure integration fixture 2026-03-11 17:40:28 +08:00
Rose Ding
3fcefe6c32 feat: prioritize new gemini image models in frontend 2026-03-11 17:34:44 +08:00
ius
f740d2c291 Reduce ops and scheduler write amplification 2026-03-11 17:32:00 +08:00
Rose Ding
bf6585a40f feat: add gemini image test preview 2026-03-11 17:12:57 +08:00
ius
8c2dd7b3f0 Fix dashboard snapshot lint errors 2026-03-11 16:57:18 +08:00
ius
4167c437a8 Reduce admin dashboard read amplification 2026-03-11 16:46:58 +08:00
Peter
0ddaef3c9a feat(admin): add metric toggle to usage charts 2026-03-11 16:05:27 +08:00
ius
2fc6aaf936 Fix Codex exhausted snapshot propagation 2026-03-11 15:47:39 +08:00
Rose Ding
1c0519f1c7 feat: add gemini 2.5 flash image support 2026-03-11 15:21:52 +08:00
Wesley Liddick
6bbe7800be Merge pull request #908 from wucm667/fix/ops-alert-group-account-metrics
fix: 补充缺失的组级和账户级运维告警指标
2026-03-11 15:04:07 +08:00
ius
2694149489 Reduce DB write amplification on quota and account extra updates 2026-03-11 13:53:19 +08:00
7976723
a17ac50118 fix: 修复 Chat Completions 编译错误和运行时 panic
1. 修复 WriteFilteredHeaders API 不兼容(2处):
   将 s.cfg.Security.ResponseHeaders 改为 s.responseHeaderFilter,
   因为 main 分支已将函数签名改为接受 *responseheaders.CompiledHeaderFilter

2. 修复 writer 生命周期导致的 nil pointer panic:
   ChatCompletions handler 替换了 c.Writer 但未恢复,导致
   OpsErrorLogger 中间件的 defer 释放 opsCaptureWriter 后,
   Logger 中间件调用 c.Writer.Status() 触发空指针解引用。
   通过保存并恢复 originalWriter 修复。

3. 为 chatCompletionsResponseWriter 添加防御性 Status() 和
   Written() 方法,包含 nil 安全检查

4. 恢复 gateway.go 中被误删的 net/http import
2026-03-11 13:49:13 +08:00
7976723
656a77d585 feat: 添加 OpenAI Chat Completions 兼容端点
基于 @yulate 在 PR #648 (commit 0bb6a392) 的工作,解决了与最新
main 分支的合并冲突。

原始功能(@yulate):
- 添加 /v1/chat/completions 和 /chat/completions 兼容端点
- 将 Chat Completions 请求转换为 Responses API 格式并转换回来
- 添加 API Key 直连转发支持
- 包含单元测试

Co-authored-by: yulate <yulate@users.noreply.github.com>
2026-03-11 13:47:37 +08:00
Wesley Liddick
7455476c60 Merge pull request #918 from rickylin047/fix/responses-string-input
fix(openai): convert string input to array for Codex OAuth responses endpoint (fix #919)
2026-03-11 08:54:39 +08:00
Elysia
36cda57c81 fix copilot review issue 2026-03-10 23:59:39 +08:00
rickylin047
9f1f203b84 fix(openai): convert string input to array for Codex OAuth responses endpoint
The ChatGPT backend-api codex/responses endpoint requires `input` to be
an array, but the OpenAI Responses API spec allows it to be a plain string.
When a client sends a string input, sub2api now converts it to the expected
message array format. Empty/whitespace-only strings become an empty array
to avoid triggering a 400 "Input must be a list" error.
2026-03-10 23:43:52 +08:00
haruka
b41a8ca93f add test 2026-03-10 11:33:25 +08:00
wucm667
e3cf0c0e10 fix: 补充缺失的组级和账户级运维告警指标
新增以下运维告警指标类型:
- group_available_accounts: 组内可用账户数
- group_available_ratio: 组内可用账户比例
- group_rate_limit_ratio: 组内限速账户比例
- account_rate_limited_count: 限速账户数
- account_error_count: 错误账户数
- account_error_ratio: 错误账户比例
- overload_account_count: 过载账户数

包含比例和计数类指标的评估逻辑,并注册新的百分比类指标用于阈值校验。
2026-03-10 11:29:31 +08:00
haruka
de18bce9aa feat: add admin reset subscription quota endpoint and UI
- Add AdminResetQuota service method to reset daily/weekly usage windows
- Add POST /api/v1/admin/subscriptions/:id/reset-quota handler and route
- Add resetQuota API function in frontend subscriptions client
- Add reset quota button, confirmation dialog, and handlers in SubscriptionsView
- Add i18n keys for reset quota feature in zh and en locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 11:21:11 +08:00
Wesley Liddick
3cc407bc0e Merge pull request #900 from ischanx/feat/admin-bind-subscription-group
feat: 允许管理员为持有有效订阅的用户绑定订阅类型分组
2026-03-10 11:20:47 +08:00
shaw
00a0a12138 feat: Anthropic平台可配置 anthropic-beta 策略 2026-03-10 11:20:10 +08:00
ischanx
b08767a4f9 fix: avoid admin subscription binding regressions 2026-03-10 10:53:54 +08:00
Wesley Liddick
ac6bde7a98 Merge pull request #872 from StarryKira/fix/oauth-linuxdo-invitation-required
fix: Linux.do OAuth 注册支持邀请码两步流程 (fix #836)
2026-03-10 09:10:35 +08:00
Wesley Liddick
d2d41d68dd Merge pull request #894 from touwaeriol/pr/startup-concurrency-cleanup
feat: cleanup stale concurrency slots on startup
2026-03-10 09:08:33 +08:00
Wesley Liddick
944b7f7617 Merge pull request #904 from james-6-23/fix-pool-mode-retry
fix: OpenAI临时性400错误支持池模式同账号重试 & HelpTooltip层级修复
2026-03-10 09:08:12 +08:00
Wesley Liddick
53825eb073 Merge pull request #903 from touwaeriol/fix/openai-responses-sse-max-line-size-v2
fix: use shared max_line_size config for OpenAI Responses SSE scanner
2026-03-10 09:06:48 +08:00
Wesley Liddick
1a7f49513f Merge pull request #896 from touwaeriol/pr/fix-quota-badge-vertical-layout
fix(frontend): stack quota badges vertically in capacity column
2026-03-10 09:04:53 +08:00
Wesley Liddick
885a2ce7ef Merge pull request #893 from touwaeriol/pr/iframe-lang-passthrough
feat(frontend): pass locale to iframe embedded pages via lang parameter
2026-03-10 09:04:37 +08:00
Wesley Liddick
14ba80a0af Merge pull request #897 from DaydreamCoding/feat/batch-reset-and-openai-jwt
feat: OpenAI 账号信息增强 & 批量操作支持
2026-03-10 09:03:59 +08:00
kyx236
5fa22fdf82 fix: OpenAI临时性400错误支持池模式同账号重试 & HelpTooltip层级修复
1. 识别OpenAI "An error occurred while processing your request" 临时性400错误
   并触发failover,同时在池模式下标记RetryableOnSameAccount,允许同账号重试
2. ForwardAsAnthropic路径同步支持临时性400错误的识别和同账号重试
3. HelpTooltip组件使用Teleport渲染到body,修复在dialog内被裁切的问题
2026-03-10 03:00:58 +08:00
erio
bcaae2eb91 fix: use shared max_line_size config for OpenAI Responses SSE scanner
Two SSE scanners in openai_gateway_messages.go were hardcoded to 1MB
while all other scanners use defaultMaxLineSize (500MB) with config
override. This caused Responses API streams to fail on large payloads.
2026-03-10 02:50:04 +08:00
ischanx
767a41e263 feat: 允许管理员为持有有效订阅的用户绑定订阅类型分组
之前管理员无法通过 API 密钥管理将用户绑定到订阅类型分组(直接返回错误)。
现在改为检查用户是否持有该分组的有效订阅,有则允许绑定,无则拒绝。

- admin_service: 新增 userSubRepo 依赖,替换硬拒绝为订阅校验
- admin_service: 区分 ErrSubscriptionNotFound 和内部错误,避免 DB 故障被误报
- wire_gen/api_contract_test: 同步新增参数
- UserApiKeysModal: 管理员分组下拉不再过滤订阅类型分组

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:51:43 +08:00
QTom
252d6c5301 feat: 支持批量重置状态和批量刷新令牌
- 提取 refreshSingleAccount 私有方法复用单账号刷新逻辑
- 新增 BatchClearError handler (POST /admin/accounts/batch-clear-error)
- 新增 BatchRefresh handler (POST /admin/accounts/batch-refresh)
- 前端 AccountBulkActionsBar 添加批量重置状态/刷新令牌按钮
- AccountsView 添加 handler 支持 partial success 反馈
- i18n 中英文补充批量操作相关翻译
2026-03-09 21:54:27 +08:00
QTom
7a4e65ad4b feat: 导入账号时 best-effort 从 id_token 提取用户信息
提取 DecodeIDToken(跳过过期校验)供导入场景使用,
ParseIDToken 复用它并保留原有过期检查行为。
导入 OpenAI/Sora OAuth 账号时自动补充缺失的 email、
plan_type、chatgpt_account_id 等字段,不覆盖已有值。
2026-03-09 21:53:46 +08:00
QTom
a582aa89a9 feat: 从 OpenAI JWT 提取 chatgpt_plan_type 并在前端展示
OAuth 授权和 token 刷新时从 id_token 的 OpenAI auth claim 中
提取 chatgpt_plan_type(plus/team/pro/free),存入 credentials,
账号管理页面 PlatformTypeBadge 显示订阅类型。
2026-03-09 21:53:46 +08:00
erio
acefa1da12 fix(frontend): stack quota badges vertically in capacity column
QuotaBadge components (daily/weekly/total) were wrapped in a
horizontal flex container, making them visually inconsistent with
other capacity badges (concurrency, window cost, sessions, RPM)
which stack vertically. Remove the wrapper so all badges align
consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 20:18:12 +08:00
erio
a88698f3fc feat: cleanup stale concurrency slots on startup
When the service restarts, concurrency slots from the old process
remain in Redis, causing phantom occupancy. On startup, scan all
concurrency sorted sets and remove members with non-current process
prefix, then clear orphaned wait queue counters.

Uses Go-side SCAN to discover keys (compatible with Redis client
prefix hooks in tests), then passes them to a Lua script for
atomic member-level cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:55:18 +08:00
erio
ebc6755b33 feat(frontend): pass locale to iframe embedded pages via lang parameter
Embedded pages (purchase subscription, custom pages) now receive the
current user locale through a `lang` URL parameter, allowing iframe
content to match the user's language preference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:38:23 +08:00
shaw
c8eff34388 chore: update readme 2026-03-09 16:25:56 +08:00
shaw
f19b03825b chore: update docs 2026-03-09 16:11:44 +08:00
shaw
25178cdbe1 fix: 修复gpt->claude同步请求返回sse的bug 2026-03-09 15:58:44 +08:00
shaw
a461538d58 fix: 修复gpt->claude转换无法命中codex缓存问题 2026-03-09 15:08:37 +08:00
Elysia
b43ee62947 fix CI/CD Error 2026-03-09 13:13:39 +08:00
shaw
ebe6f418f3 fix: gpt->claude格式转换对齐effort映射和fast 2026-03-09 11:42:35 +08:00
Wesley Liddick
391e79f8ee Merge pull request #875 from mt21625457/fix/openai-fast-billing-clean
fix(billing): 修复 OpenAI fast 档位计费并补齐展示
2026-03-09 10:32:18 +08:00
shaw
c7fcb7a84b feat: apikey限额支持查询重置时间 2026-03-09 10:22:24 +08:00
yangjianbo
87f4ed591e fix(billing): 修复 OpenAI fast 档位计费并补齐展示
- 打通 service_tier 在 OpenAI HTTP、WS、passthrough 与 usage 记录中的传递
- 修正 priority/flex 计费逻辑,并将 fast 归一化为 priority
- 在用户端和管理端补齐服务档位与计费明细展示
- 补齐前后端测试,并修复 WS 限流信号重复持久化导致的全量回归失败

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:51:26 +08:00
shaw
440d2e28ed fix: 恢复context-1m-2025-08-07在oauth账号中传递 2026-03-09 09:29:33 +08:00
Wesley Liddick
6cb8980404 Merge pull request #807 from touwaeriol/fix/openai-passthrough-v2
fix(openai): remove misplaced passthrough check from isModelSupportedByAccount
2026-03-09 09:06:35 +08:00
Wesley Liddick
fe752bbd35 Merge pull request #853 from touwaeriol/pr/swipe-select-admin-tables
feat(frontend): 为后台账号管理和 IP 管理增加拖筐选中能力
2026-03-09 09:04:02 +08:00
Wesley Liddick
c74d451fa2 Merge pull request #874 from touwaeriol/pr/increase-sse-max-line-size
fix: increase SSE scanner max line size from 40MB to 500MB
2026-03-09 09:03:39 +08:00
Wesley Liddick
12d743fb35 Merge pull request #868 from touwaeriol/pr/bump-antigravity-version
chore: bump Antigravity user agent version to 1.20.4
2026-03-09 09:03:25 +08:00
Wesley Liddick
6acb9f7910 Merge pull request #864 from StarryKira/fix/clear-thinking-context-management
[Fix] Fix issue #851
2026-03-09 09:02:58 +08:00
erio
eb6f5c6927 test: update UserAgent version assertion to match 1.20.4 2026-03-09 08:59:21 +08:00
erio
7ccb4c8ea3 chore: bump Antigravity user agent version to 1.20.4 2026-03-09 08:59:21 +08:00
erio
4ce986d47d fix: also update viper default max_line_size from 40MB to 500MB
The viper config default (config.go) was overriding the constant
in gateway_service.go. Both must be updated to take effect.
2026-03-09 08:56:54 +08:00
erio
91ef085d7d fix: increase SSE scanner max line size from 40MB to 500MB
4K image base64 data can exceed 40MB limit, causing "bufio.Scanner:
token too long" errors. Scanner is adaptive (starts at 64KB, grows
as needed), so increasing the cap has no impact on normal responses.
2026-03-09 08:56:54 +08:00
Wesley Liddick
97aaa24733 Merge pull request #858 from james-6-23/fix/pool-mode-03bf3485
支持 API Key 上游池模式的同账号重试次数配置与自定义错误策略
2026-03-09 08:48:53 +08:00
Wesley Liddick
faf6441633 Merge pull request #854 from james-6-23/main
feat(admin): 支持定时测试自动恢复并统一账号恢复入口
2026-03-09 08:48:36 +08:00
shaw
00c151b463 feat: gpt->claude格式转换支持图片识别 2026-03-09 08:44:09 +08:00
Elysia
106b20cdbf fix claudecode review bug 2026-03-09 01:18:49 +08:00
Elysia
c069b3b1e8 fix issue #836 linux.do注册无需邀请码 2026-03-09 00:35:34 +08:00
Wesley Liddick
a2ae9f1f27 Merge pull request #866 from touwaeriol/pr/apikey-quota-usage-bars
feat(frontend): add API Key quota progress bars to usage window
2026-03-08 21:50:24 +08:00
erio
4cd6d86426 feat(frontend): add API Key quota progress bars to usage window column
Display daily/weekly/total quota utilization as progress bars in the
usage window column for API Key accounts, providing visual feedback
consistent with other account types (OAuth/Gemini).

- Daily quota: "1d" label with reset countdown
- Weekly quota: "7d" label with reset countdown
- Total quota: "total" label (no reset)
2026-03-08 21:35:26 +08:00
时雨遥
fa72f1947a Update backend/internal/service/gateway_request_test.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 21:21:36 +08:00
时雨遥
9ee7d3935d Update backend/internal/service/gateway_request.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 21:21:28 +08:00
Elysia
1071fe0ac7 add test file 2026-03-08 21:08:09 +08:00
erio
0be003377f feat(frontend): add swipe-to-select for admin tables
Squash of all swipe-select commits for clean rebase.
2026-03-08 21:07:43 +08:00
Elysia
ca3f497b56 fix issue #851 2026-03-08 21:00:34 +08:00
Wesley Liddick
034b84b707 Merge pull request #861 from bayma888/feature/usage-user-balance-popup
feat(ui): 使用记录页面点击用户邮箱可查看用户信息和充值记录
2026-03-08 20:37:45 +08:00
Wesley Liddick
1624523c4e Merge pull request #860 from bayma888/feature/group-display-fix
feat(ui): 优化分组选择器、交互体验和样式遮挡体验问题
2026-03-08 20:37:36 +08:00
Wesley Liddick
313afe14ce Merge pull request #842 from pkssssss/fix/openai-ws-usage-refresh
fix: 修复 OpenAI WS 用量窗口刷新与限额状态不同步
2026-03-08 20:34:54 +08:00
Wesley Liddick
01180b316f Merge pull request #841 from touwaeriol/feature/account-periodic-quota
feat(account): 为 API Key 账号新增日/周周期性配额限制
2026-03-08 20:34:15 +08:00
Wesley Liddick
ee7d061001 Merge pull request #839 from pkssssss/fix/simple-mode-admin-concurrency-30
fix: 简易模式仅提升管理员默认并发到 30
2026-03-08 20:33:11 +08:00
bayma888
60c5949a74 feat(ui): 使用记录页面点击用户邮箱可查看充值记录
- UsageTable 用户邮箱改为可点击链接,点击弹出余额变动记录
- 复用 UserBalanceHistoryModal 组件,通过 getById API 获取完整用户信息
- 新增 hideActions prop 隐藏充值/退款按钮(Usage 页面仅查看)
- i18n: 新增 clickToViewBalance、failedToLoadUser 词条 (en/zh)
2026-03-08 19:11:28 +08:00
bayma888
2ebbd4c94d feat(ui): 优化分组选择器交互体验
- 分组下拉添加搜索框,支持按名称/描述快速筛选
- 新建/编辑密钥弹窗的分组选择也支持搜索
- 智能弹出方向:底部空间不足时自动向上弹出
- 倍率独立为平台配色的圆角标签,更醒目
- 分组名称加粗,名称与描述之间增加间距
- 分组选项之间添加分隔线,视觉更清晰
- 切换图标旁增加"选择分组"文字提示
- 下拉宽度自适应内容长度
- i18n: 新增 searchGroup、noGroupFound 词条 (en/zh)
2026-03-08 18:26:17 +08:00
bayma888
785115c62b fix(ui): improve group selector dropdown width and visibility
- Increase Select dropdown max-width from 320px to 480px for better content display
- Change KeysView group selector from fixed 256px to adaptive 280-480px width
- Make group switch icon always visible (60% opacity, 100% on hover)
- Allow group description to wrap to 2 lines instead of truncating
- Improve user experience for group selection in API keys page
2026-03-08 15:12:15 +08:00
kyx236
e643fc382c feat: 支持 API Key 上游池模式同账号重试次数配置与自定义错误策略 2026-03-08 14:12:17 +08:00
kyx236
34aad82ac3 fix(frontend): 修复后台页面 lint 校验问题 2026-03-08 07:34:15 +08:00
kyx236
0c29468f90 feat(admin): 支持定时测试自动恢复并统一账号恢复入口
- 为定时测试计划增加 auto_recover 配置,补齐前后端类型、接口、仓储与数据库迁移
- 在定时测试成功后自动恢复账号 error、rate-limit 等可恢复运行时状态
- 新增 /admin/accounts/:id/recover-state 接口,合并原有重置状态与清限流操作
- 更新账号管理菜单与定时测试面板,补充自动恢复开关、说明提示和状态展示
- 补充账号恢复、限流清理与仓储同步相关测试
2026-03-08 06:59:53 +08:00
神乐
9301dae63e fix: 修复 OpenAI WS 用量刷新遗漏场景 2026-03-08 04:37:20 +08:00
erio
2475d4a205 feat: add marquee selection box overlay during drag-to-select
Show a semi-transparent blue rectangle overlay while dragging to
select rows, matching the project's primary color theme with dark
mode support. The box spans the full table width from drag start
to current mouse position.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 01:45:22 +08:00
erio
be75fc3474 ci: retrigger CI checks 2026-03-08 01:42:18 +08:00
神乐
785e049af3 Merge branch 'Wei-Shaw:main' into fix/simple-mode-admin-concurrency-30 2026-03-08 00:16:59 +08:00
神乐
be4e49e6d7 Merge branch 'Wei-Shaw:main' into fix/openai-ws-usage-refresh 2026-03-08 00:16:51 +08:00
神乐
1307d604e7 fix: 补齐旧账号的 OpenAI 限流补偿 2026-03-08 00:14:15 +08:00
神乐
45d57018eb fix: 修复 OpenAI WS 限流状态与调度同步 2026-03-07 23:59:39 +08:00
shaw
03bf348530 fix(lint): gofmt formatting fixes for 3 files
Align struct field assignments and fix indentation detected by
golangci-lint v2.9's gofmt checker.
2026-03-07 23:24:09 +08:00
shaw
cab60ef735 fix(ci): downgrade golangci-lint v2.11 to v2.9 to fix lint timeout
v2.10.1 introduced a recursive markDepsForAnalyzingSource change that
causes all transitive dependencies (including 132K lines of ent
generated code) to be analyzed from source instead of compiled export
data, leading to >30 min CI timeout.

v2.9.0 is the first version with Go 1.26 support (PR #6271, merged
Feb 10 2026) and does not have this performance regression.
2026-03-07 23:10:03 +08:00
shaw
a3791104f9 feat: 支持后台设置是否启用整流开关 2026-03-07 21:55:38 +08:00
神乐
2b3e40bb2a test: 修复 PR842 的 CI 失败 2026-03-07 21:24:06 +08:00
神乐
0c1dcad429 test: 修复 PR842 的 CI 失败 2026-03-07 21:09:34 +08:00
神乐
101ef0cf62 fix: 限流账号自动退出调度并优化提示文案 2026-03-07 21:05:37 +08:00
神乐
0debe0a80c fix: 修复 OpenAI WS 用量窗口刷新与限额纠偏 2026-03-07 20:02:58 +08:00
erio
d22e62ac8a fix(test): add allow_messages_dispatch to group API contract test
The recent upstream commit added allow_messages_dispatch to the Group
DTO but did not update the API contract test expectation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:28:22 +08:00
erio
1ee17383f8 feat(account): add daily/weekly periodic quota limits for API Key accounts
Extend the existing total quota limit with daily and weekly periodic
dimensions. Each dimension is independently configurable and uses lazy
reset — when the period expires, usage is automatically reset to zero on
the next increment. Any dimension exceeding its limit will pause the
account from scheduling.

Backend:
- Add GetQuotaDailyLimit/Used, GetQuotaWeeklyLimit/Used, HasAnyQuotaLimit
- Rewrite IncrementQuotaUsed with atomic CTE SQL for 3-dimension update
- Rewrite ResetQuotaUsed to clear all dimensions and period timestamps
- Update postUsageBilling to use HasAnyQuotaLimit()
- Preserve daily/weekly used values on account edit

Frontend:
- Refactor QuotaLimitCard from single v-model to 3-dimension props
- Add QuotaBadge component for compact D/W/$ display
- Update AccountCapacityCell with per-dimension badges
- Update Create/Edit modals with daily/weekly quota fields
- Update AccountActionMenu hasQuotaLimit to check all dimensions
- Add i18n strings for daily/weekly/total quota labels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:06:59 +08:00
神乐
b59c79c458 fix: 简易模式仅提升管理员默认并发到30 2026-03-07 18:19:04 +08:00
shaw
bcb6444f89 fix(ci): update Go version check in release workflow to 1.26.1 2026-03-07 17:11:50 +08:00
Wesley Liddick
c2b14693b4 Merge pull request #835 from biubiutata/codex/fix-openai-originator-detection
fix(openai): 统一官方 Codex 客户端识别逻辑
2026-03-07 17:03:52 +08:00
shaw
92d35409de feat: 为openai分组增加messages调度开关和默认映射模型 2026-03-07 17:02:19 +08:00
shaw
351a08f813 fix: announcement强制弹窗通知补全迁移sql 2026-03-07 15:36:18 +08:00
shaw
a58dc787a9 fix(ci): 精简golangci-lint配置解决v2.11超时问题
- 移除staticcheck 470+冗余检查项,all已包含全部
- unused: generated-is-used改为true,跳过ent 13万行生成代码分析
- unused: exported-fields-are-used改为true,避免全项目导出字段引用追踪
- unused: field-writes-are-uses改为true
2026-03-07 15:17:16 +08:00
shaw
7079edc2d0 feat: announcement支持强制弹窗通知 2026-03-07 15:06:13 +08:00
admin
da89583ccc fix(openai): detect official codex client by headers 2026-03-07 14:12:38 +08:00
shaw
a42a1f08e9 fix: 编辑error状态账号时保存报Status验证失败
后端UpdateAccountRequest.Status的oneof验证缺少error状态,
前端编辑表单也未处理error状态,导致编辑异常账号时无法保存
2026-03-07 13:47:08 +08:00
shaw
ebd5253e22 fix: /response端点移除强制注入大量instructions内容 2026-03-07 13:39:47 +08:00
shaw
6411645ffc fix: 适配claude code调度openai账号的websearch功能 2026-03-07 11:33:08 +08:00
shaw
c0c322ba16 chore: openai账号模型映射新增claude->gpt快捷映射按钮 2026-03-07 10:26:30 +08:00
shaw
d35c5cd491 feat: openai平台的apikey新增claude code使用教程 2026-03-07 10:14:57 +08:00
shaw
7a353028e7 fix: 修复keys速率限制未自动重置额度的bug 2026-03-07 10:13:51 +08:00
shaw
2d8d3b7857 fix(ci): upgrade golangci-lint v2.7 to v2.11 for Go 1.26 compatibility
golangci-lint v2.7 was built with Go 1.25 and cannot lint Go 1.26
targets. v2.8+ added Go 1.26 support.
2026-03-07 08:54:05 +08:00
Wesley Liddick
4190293b07 Merge pull request #823 from StarryKira/fix/empty-stream-failover
Fix/empty streamfix issue #791
2026-03-07 08:51:07 +08:00
Wesley Liddick
421b4c0aff Merge pull request #830 from ckken/pr/ccswitch-import-improvements
fix(ccswitch): improve import provider name and usage parsing
2026-03-07 08:49:09 +08:00
Wesley Liddick
cd69a7cb85 Merge pull request #820 from geminiwen/fix/apikey-credentials-preserve-existing-fields
fix(account): preserve existing credentials when saving apikey accounts
2026-03-07 08:46:51 +08:00
shaw
0c9ba9e86c fix(security): upgrade Go 1.25.7 to 1.26.1 to resolve 4 stdlib vulnerabilities
GO-2026-4602 (os), GO-2026-4601 (net/url), GO-2026-4600 and
GO-2026-4599 (crypto/x509). The crypto/x509 fixes are only
available in go1.26.1+, not backported to go1.25.x.
2026-03-07 08:45:55 +08:00
shaw
1b4d2a41c9 fix(openai): /v1/messages端点补齐Codex用量快照提取与错误透传规则
对齐/v1/responses的Forward方法,修复两处不一致:
- 成功响应时从响应头提取OAuth账号的Codex使用量数据
- 非failover错误场景下应用管理员配置的错误透传规则
2026-03-07 08:40:07 +08:00
Wesley Liddick
0787d2b47a Merge pull request #829 from JIA-ss/fix/usage-query-rate-limit
fix(usage): 修复用量查询 429 重试风暴,增加负缓存、请求去重与随机延迟
2026-03-07 08:32:29 +08:00
ckken
97bf1d85ab feat(ccswitch): use site_name as default provider name in import link 2026-03-07 01:19:10 +08:00
ckken
207a493fab fix(ccswitch): parse remaining quota from /v1/usage response 2026-03-07 01:19:10 +08:00
JIA-ss
1f3f9e131e fix: resolve golangci-lint errors (gofmt alignment, errcheck)
- Fix gofmt: align struct field comments in UsageCache, trim trailing
  whitespace on const comments
- Fix errcheck: use comma-ok on type assertion for singleflight result

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:58:08 +08:00
JIA-ss
4ddedfaaf9 merge: resolve conflict with main (keep both openAI probe and usage fix)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:49:30 +08:00
JIA-ss
3ebebef95f fix(usage): add negative caching, singleflight, and jitter to usage queries
Prevents 429 rate-limit retry storms and reduces upstream correlation risk
for Anthropic usage API queries.

Three changes:
1. Negative caching (1 min TTL) — 429/error responses are now cached,
   preventing every subsequent page load from re-triggering failed API calls.
2. singleflight dedup — concurrent requests for the same account are
   collapsed into a single upstream call, preventing cache stampede.
3. Random jitter (0–800 ms) — staggers multi-account cache-miss bursts so
   requests from different accounts don't hit upstream simultaneously with
   identical TLS fingerprints, reducing anti-abuse correlation risk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:39:16 +08:00
Gemini Wen
9f7ad47598 fix(account): clean up stale credentials fields after spreading currentCredentials
When customErrorCodes is disabled or modelMapping is empty, explicitly
delete the fields inherited from currentCredentials spread to avoid
preserving stale values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:38:58 +08:00
Gemini Wen
3c83cd8be2 Merge remote-tracking branch 'origin/main' into fix/apikey-credentials-preserve-existing-fields 2026-03-06 23:38:18 +08:00
Wesley Liddick
963b3b768c Merge pull request #825 from FizzlyCode/fix/setup-token-real-utilization
fix: Setup Token 账号使用真实 utilization 值替代状态估算
2026-03-06 22:46:20 +08:00
Wesley Liddick
f6709fb5d6 Merge pull request #824 from pkssssss/fix/ws-usage-window-pr
fix(openai): 修复 WS 模式下用量窗口不显示
2026-03-06 22:45:36 +08:00
shaw
921599948b feat: /v1/messages端点适配codex账号池 2026-03-06 22:44:07 +08:00
神乐
5df3cafa99 style(go): format account usage service 2026-03-06 21:31:36 +08:00
神乐
1a2143c1fe fix(openai): adapt messages path to codex transform signature 2026-03-06 21:17:27 +08:00
神乐
dd25281305 chore(test): resolve merge conflict for ws usage window pr 2026-03-06 21:16:21 +08:00
FizzlyCode
49d0301dde fix: Setup Token 账号使用真实 utilization 值替代状态估算
从响应头 anthropic-ratelimit-unified-5h-utilization 获取并存储真实
utilization 值,解决进度条始终显示 0% 的问题。窗口重置时清除旧值,
避免残留上个窗口的数据。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:04:44 +08:00
神乐
d90e56eb45 chore(openai): clean up ws usage window branch 2026-03-06 21:04:24 +08:00
神乐
838ada8864 fix(openai): restore ws usage window display 2026-03-06 20:49:47 +08:00
Elysia
65a106792a fix issue #791 2026-03-06 20:37:09 +08:00
Elysia
ee4bfcbb81 Merge remote-tracking branch 'origin/main' 2026-03-06 20:37:09 +08:00
Gemini Wen
a087f089b8 fix(account): preserve existing credentials when saving apikey accounts
When editing an apikey account, the credentials object was built from
scratch, causing fields like tier_id that are not exposed in the UI to
be silently dropped on save. Spread currentCredentials first so unknown
fields are retained, then let the known fields overwrite them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:19:19 +08:00
Wesley Liddick
afbe8bf001 Merge pull request #809 from alfadb/feature/openai-messages
feat(openai): 添加 /v1/messages 端点和 API 兼容层
2026-03-06 20:16:06 +08:00
Wesley Liddick
2a3ef0be06 Merge pull request #818 from pkssssss/fix/remote-compact
fix(openai): support remote compact task
2026-03-06 19:41:04 +08:00
神乐
3403909354 fix(openai): support remote compact task 2026-03-06 18:51:05 +08:00
Wesley Liddick
005d0c5f53 Merge pull request #815 from mt21625457/pr/openai-user-group-rate-upstream
fix(openai): 统一专属倍率计费链路并补齐回归测试
2026-03-06 17:33:09 +08:00
Wesley Liddick
8aaaeb29cc Merge pull request #813 from FizzlyCode/fix/account-usage-display
fix: 修复账号列表五小时用量显示为 $0.00 的问题
2026-03-06 17:25:03 +08:00
yangjianbo
230f8abd04 test(openai): 修复回归测试未使用字段告警
移除订阅扣费 stub 中未被使用的状态字段与赋值,
消除 golangci-lint 的 unused 告警,保持回归测试语义不变。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:08:41 +08:00
yangjianbo
a18bbb5f2f fix(openai): 统一专属倍率计费链路并补齐回归测试
抽取共享的用户分组专属倍率解析器,统一缓存、singleflight 与回退逻辑。\n\n让 OpenAI 独立计费链路复用专属倍率解析,修复 usage 记录与实际扣费未命中用户专属倍率的问题。\n\n补齐 OpenAI 计费与解析器单元测试,并修复全量回归中暴露的 lint 阻塞项。\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:47:51 +08:00
wioos
60fce4f1dc fix: 修复 lite 模式跳过窗口费用查询导致 $0.00 显示的问题
commit 80ae592c 引入 lite 模式优化首次加载性能,但将窗口费用查询也一起跳过了。
commit 491a7444 尝试用 30 秒快照缓存修复,但缓存过期后问题复现。

移除窗口费用查询的 lite/非 lite 区分,始终执行 PostgreSQL 聚合查询。
同时删除不再需要的 account_window_cost_cache.go 文件。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:42:12 +08:00
wioos
9af65efcdb fix: 修复 zh.ts 缺少 protocols 翻译
在 admin.proxies 下添加 protocols 对象的中文翻译,
与 en.ts 保持一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:42:12 +08:00
alfadb
bc194a7d8c fix: address PR review - Anthropic error format in panic recovery and nil guard
- Add recoverAnthropicMessagesPanic for Messages handler to return
  Anthropic-formatted errors instead of OpenAI Responses format on panic
- Add nil check for rateLimitService.HandleUpstreamError in
  ForwardAsAnthropic to match defensive pattern used elsewhere

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:40:15 +08:00
erio
c28f691f32 fix(openai): remove misplaced passthrough check from isModelSupportedByAccount
isModelSupportedByAccount 不被 OpenAI 调度路径调用,
OpenAI /responses 和 /chat/completions 走的是
openai_account_scheduler.go,透传短路已在 PR #806 的
第二个 commit 中正确添加到该文件。

此处的检查是多余的死代码,因为 OpenAI 账号不会走到
isModelSupportedByAccount 的这个分支。
2026-03-06 14:32:08 +08:00
alfadb
ff1f114989 feat(openai): add /v1/messages endpoint and API compatibility layer
Add Anthropic Messages API support for OpenAI platform groups, enabling
clients using Claude-style /v1/messages format to access OpenAI accounts
through automatic protocol conversion.

- Add apicompat package with type definitions and bidirectional converters
  (Anthropic ↔ Chat, Chat ↔ Responses, Anthropic ↔ Responses)
- Implement /v1/messages endpoint for OpenAI gateway with streaming support
- Add model mapping UI for OpenAI OAuth accounts (whitelist + mapping modes)
- Support prompt caching fields and codex OAuth transforms
- Fix tool call ID conversion for Responses API (fc_ prefix)
- Ensure function_call_output has non-empty output field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:29:22 +08:00
Wesley Liddick
cac230206d Merge pull request #806 from touwaeriol/fix/openai-passthrough-model-check
fix(openai): passthrough accounts bypass model mapping check
2026-03-06 14:09:07 +08:00
erio
79ae15d5e8 fix: OpenAI passthrough accounts bypass model mapping check
透传模式账号仅替换认证,应允许所有模型通过。之前调度阶段的
isModelSupportedByAccount 不感知透传模式,导致 model_mapping
中未配置的新模型(如 gpt-5.4)被拒绝返回 503。
2026-03-06 14:01:47 +08:00
shaw
0cce0a8877 chore: gpt-5.4示例配置修改model_reasoning_effort为xhigh 2026-03-06 11:29:43 +08:00
shaw
225fd035ae chore: 更新codex配置部分支持gpt-5.4的长上下文 2026-03-06 10:55:09 +08:00
Wesley Liddick
fb7d1346b5 Merge pull request #800 from mt21625457/pr/gpt54-support-upstream
feat(openai): 增加 GPT-5.4 支持并修复长上下文计费与白名单回归
2026-03-06 10:42:01 +08:00
shaw
491a744481 fix: 修复账号列表首次加载窗口费用显示 $0.00
lite 模式下从快照缓存读取窗口费用,非 lite 模式查询后写入缓存
2026-03-06 10:23:22 +08:00
yangjianbo
f366026435 fix(openai): 修复 gpt-5.4 长上下文计费与快照白名单
补齐 gpt-5.4 fallback 的长上下文计费元信息,\n确保超过 272000 输入 token 时对整次会话应用\n2x 输入与 1.5x 输出计费规则。\n\n同时将官方快照 gpt-5.4-2026-03-05 加入前端\n白名单候选与回归测试,避免 whitelist 模式误拦截。\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

(cherry picked from commit d95497af87f608c6dadcbe7d6e851de9413ae147)
2026-03-06 10:16:23 +08:00
yangjianbo
1a0d4ed668 feat(openai): 增加 gpt-5.4 模型支持与定价配置
- 接入 gpt-5.4 模型识别与规范化,补充默认模型列表
- 增加 gpt-5.4 输入/缓存命中/输出价格与计费兜底逻辑
- 同步前端模型白名单与 OpenCode 上下文窗口(1050000/128000)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit 924476dcac6181cd0f3ee731ec7b73672ff03793)
2026-03-06 10:16:23 +08:00
Wesley Liddick
63a8c76946 Merge pull request #798 from touwaeriol/feature/account-load-factor
feat: add account load_factor for scheduling load calculation
2026-03-06 09:42:10 +08:00
Wesley Liddick
f355a68bc9 Merge pull request #796 from touwaeriol/feature/apikey-quota-limit
feat: add configurable spending limit for API Key accounts
2026-03-06 09:37:52 +08:00
erio
c87e6526c1 fix: use real Concurrency instead of LoadFactor for metrics and monitoring
LoadFactor should only affect scheduling weight, not load rate reporting.
2026-03-06 05:18:45 +08:00
erio
af3a5076d6 fix: add load_factor upper bound validation to BulkUpdateAccounts 2026-03-06 05:17:52 +08:00
erio
18f2e21414 fix: use HTML-safe expressions for @input handlers in Vue templates
Replace `<` comparisons with Math.max/ternary+>= to avoid Vue template
parser treating `<` as HTML tag start in attribute values.
2026-03-06 05:07:52 +08:00
erio
8a8cdeebb4 fix: prevent negative values for concurrency and load_factor inputs 2026-03-06 05:07:52 +08:00
erio
12b33f4ea4 fix: address load_factor code review findings
- Fix bulk edit: send 0 instead of null/NaN to clear load_factor
- Fix edit modal: explicit NaN check instead of implicit falsy
- Fix create modal: use ?? instead of || for load_factor
- Add load_factor upper limit validation (max 10000)
- Add //go:build unit tag and self-contained intPtrHelper in test
- Add design intent comments on WaitPlan.MaxConcurrency
2026-03-06 05:07:52 +08:00
erio
01b3a09d7d fix: validate account status before update and update load factor hint
- Normalize non-standard status (e.g. "error") to "active" on edit load
- Add pre-submit validation for status field to prevent 400 errors
- Update load factor hint: "提高负载因子可以提高对账号的调度频率"
2026-03-06 05:07:40 +08:00
erio
0d6c1c7790 feat: add independent load_factor field for scheduling load calculation 2026-03-06 05:07:10 +08:00
erio
95e366b6c6 fix: add missing IncrementQuotaUsed and ResetQuotaUsed to stubAccountRepo in api_contract_test 2026-03-06 04:37:56 +08:00
erio
77701143bf fix: use range assertion for time-sensitive ExpiresInDays test
The test could flake depending on exact execution time near midnight
boundaries. Use a range check (29 or 30) instead of exact equality.
2026-03-06 01:07:28 +08:00
erio
02dea7b09b refactor: unify post-usage billing logic and fix account quota calculation
- Extract postUsageBilling() to consolidate billing logic across
  GatewayService.RecordUsage, RecordUsageWithLongContext, and
  OpenAIGatewayService.RecordUsage, eliminating ~120 lines of
  duplicated code
- Fix account quota to use TotalCost × accountRateMultiplier
  (was using raw TotalCost, inconsistent with account cost stats)
- Fix RecordUsageWithLongContext API Key quota only updating in
  balance mode (now updates regardless of billing type)
- Fix WebSocket client disconnect detection on Windows by adding
  "an established connection was aborted" to known disconnect errors
2026-03-06 00:54:17 +08:00
erio
c26f93c4a0 fix: route antigravity apikey account test to native protocol
Antigravity APIKey accounts were incorrectly routed to
testAntigravityAccountConnection which calls AntigravityTokenProvider,
but the token provider only handles OAuth and Upstream types, causing
"not an antigravity oauth account" error.

Extract routeAntigravityTest to route APIKey accounts to native
Claude/Gemini test paths based on model prefix, matching the
gateway_handler routing logic for normal requests.
2026-03-06 00:36:13 +08:00
erio
c826ac28ef refactor: extract QuotaLimitCard component for reuse in create and edit modals
- Extract quota limit card/toggle UI into QuotaLimitCard.vue component
- Use v-model pattern for clean parent-child data flow
- Integrate into both EditAccountModal and CreateAccountModal
- All apikey accounts (all platforms) now support quota limit on creation
- Bump version to 0.1.90.6
2026-03-06 00:36:00 +08:00
erio
1893b0eb30 feat: restyle API Key quota limit UI to card/toggle format
- Redesign quota limit section with card layout and toggle switch
- Add watch to clear quota value when toggle is disabled
- Add i18n keys for toggle labels and hints (zh/en)
- Bump version to 0.1.90.5
2026-03-06 00:35:35 +08:00
erio
05527b13db feat: add quota limit for API key accounts
- Add configurable spending limit (quota_limit) for apikey-type accounts
- Atomic quota accumulation via PostgreSQL JSONB operations on TotalCost
- Scheduler filters out over-quota accounts with outbox-triggered snapshot refresh
- Display quota usage ($used / $limit) in account capacity column
- Add "Reset Quota" action in account menu to reset usage to zero
- Editing account settings preserves quota_used (no accidental reset)
- Covers all 3 billing paths: Anthropic, Gemini, OpenAI RecordUsage

chore: bump version to 0.1.90.4
2026-03-06 00:35:09 +08:00
Wesley Liddick
ae5d9c8bfc Merge pull request #788 from touwaeriol/fix/usage-error-passthrough
fix: pass through upstream HTTP status in usage API errors
2026-03-05 22:05:36 +08:00
erio
9117c2a4ec fix: include upstream error details in usage API error response
When FetchUsageWithOptions receives a non-200 response from the
Anthropic API (e.g. 429 Rate Limited, 401 Unauthorized), the error
was wrapped with fmt.Errorf which infraerrors.FromError cannot
recognize, causing a generic "internal error" message with no details.

Replace fmt.Errorf with infraerrors.New(500, "UPSTREAM_ERROR", msg)
so the upstream error details (status code + body) are included in
the 500 response message. The HTTP status remains 500 to avoid
interfering with frontend auth routing (e.g. 401 would trigger
JWT expiry redirect).
2026-03-05 21:02:11 +08:00
shaw
bab4bb9904 chore: 更新openai、claude使用秘钥教程部分 2026-03-05 18:58:10 +08:00
shaw
33bae6f49b fix: Cache Token拆分为缓存创建和缓存读取 2026-03-05 18:32:17 +08:00
Wesley Liddick
32d619a56b Merge pull request #780 from mt21625457/feat/codex-remote-compact-outcome-logging
feat(openai-handler): support codex remote compact outcome logging
2026-03-05 16:59:02 +08:00
Wesley Liddick
642432cf2a Merge pull request #777 from guoyongchang/feature-schedule-test-support
feat: 支持基于 crontab 的定时账号测试
2026-03-05 16:57:23 +08:00
程序猿MT
61e9598b08 fix(lint): remove redundant context type in compact outcome logger 2026-03-05 16:51:46 +08:00
guoyongchang
d4e34c7514 fix: 修复空结果导致定时测试模态框崩溃的问题
后端返回 null (Go nil slice) 时前端访问 .length 抛出 TypeError,
在 API 层对 listByAccount 和 listResults 加 ?? [] 兜底。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:47:01 +08:00
程序猿MT
bfe7a5e452 test(openai-handler): add codex remote compact outcome coverage 2026-03-05 16:46:14 +08:00
程序猿MT
77d916ffec feat(openai-handler): support codex remote compact outcome logging 2026-03-05 16:46:12 +08:00
guoyongchang
831abf7977 refactor: 移除冗余中间类型和不必要代码
- 移除 ScheduledTestOutcome 中间类型,RunTestBackground 直接返回 *ScheduledTestResult
- 简化 SaveResult 直接接受 *ScheduledTestResult
- 移除 handler 中不必要的 nil 检查
- 移除前端 ScheduledTestsPanel 中多余的 String() 转换

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:37:07 +08:00
guoyongchang
817a491087 simplify: 移除 leader lock,单实例无需分布式锁
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:31:27 +08:00
guoyongchang
9a8dacc514 fix: 修复 golangci-lint depguard 和 gofmt 错误
将 redis leader lock 逻辑从 service 层抽取为 LeaderLocker 接口,
实现移至 repository 层,消除 service 层对 redis 的直接依赖。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:28:48 +08:00
guoyongchang
8adf80d98b fix: wire_gen_test 补充 scheduledTestRunner 参数
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:23:41 +08:00
guoyongchang
62686a6213 revert: 还原 docker-compose.local.yml 的本地测试改动
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:17:33 +08:00
guoyongchang
3a089242f8 feat: 支持基于 crontab 的定时账号测试
每个测试计划绑定一个账号和一个模型,按 cron 表达式定期执行测试,
保存历史结果并在前端账号管理页面中提供完整的增删改查和结果查看功能。

主要变更:
- 新增 scheduled_test_plans / scheduled_test_results 两张表及迁移
- 后端 service 层:CRUD 服务 + 后台 cron runner(每分钟扫描到期计划并发执行)
- RunTestBackground 方法通过 httptest 在内存中执行账号测试并解析 SSE 输出
- Redis leader lock + pg_try_advisory_lock 双重保障多实例部署只执行一次
- REST API:5 个管理端点(计划 CRUD + 结果查询)
- 前端 ScheduledTestsPanel 组件:计划管理、启用开关、内联编辑、结果展开查看
- 中英文 i18n 支持

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:06:05 +08:00
shaw
9d70c38504 fix: 修复claude apikey账号请求时未携带beta=true 查询参数的bug 2026-03-05 15:01:04 +08:00
shaw
aeb464f3ca feat: 模型映射应用 /v1/messages/count_tokens端点 2026-03-05 14:49:28 +08:00
Wesley Liddick
7076717b20 Merge pull request #772 from mt21625457/aicodex2api-main
feat(openai-ws): 合并 WS v2 透传模式与前端 ws mode
2026-03-05 13:46:02 +08:00
程序猿MT
c0a4fcea0a Delete docker-compose-aicodex.yml
删除测试 docker compose文件
2026-03-05 13:44:07 +08:00
程序猿MT
aa2b195c86 Delete Caddyfile.dmit
删除测试caddy 配置文件
2026-03-05 13:43:25 +08:00
yangjianbo
1d0872e7ca feat(openai-ws): 合并 WS v2 透传模式与前端 ws mode
新增 OpenAI WebSocket v2 passthrough relay 数据面与服务适配层,
支持按账号 ws mode 在 ctx_pool 与 passthrough 间路由。

同步调整前端 OpenAI ws mode 选项为 off/ctx_pool/passthrough,
并补充 i18n 文案与对应单测。

新增 Caddyfile.dmit 与 docker-compose-aicodex.yml 部署配置,
用于宿主机场景下的反向代理与服务编排。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:50:58 +08:00
shaw
33988637b5 fix: SMTP测试连接和发送测试邮件返回具体错误信息而非internal error 2026-03-05 10:54:41 +08:00
shaw
d4f6ad7225 feat: 新增apikey的usage查询页面 2026-03-05 10:45:51 +08:00
shaw
078fefed03 fix: 修复账号管理页面容量列显示为0的bug 2026-03-05 09:48:00 +08:00
Wesley Liddick
5b10af85b4 Merge pull request #762 from touwaeriol/fix/dark-theme-open-in-new-tab
fix: add dark theme support for "open in new tab" FAB button
2026-03-05 08:56:28 +08:00
Wesley Liddick
4caf95e5dd Merge pull request #767 from litianc/fix/rewrite-userid-regex-match-account-uuid
fix: extend RewriteUserID regex to match user_id containing account_uuid
2026-03-05 08:56:03 +08:00
litianc
8e1bcf53bb fix: extend RewriteUserID regex to match user_id containing account_uuid
The existing regex only matched the old format where account_uuid is
empty (account__session_). Real Claude Code clients and newer sub2api
generated user_ids use account_{uuid}_session_ which was silently
skipped, causing the original metadata.user_id to leak to upstream
when User-Agent is rewritten by an intermediate gateway.

Closes #766

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:13:17 +08:00
erio
064f9be7e4 fix: add dark theme support for "open in new tab" FAB button
The backdrop-blur background on the iframe "open in new tab" floating
button was hardcoded to bg-white/80, making it look broken in dark
theme. Added dark:bg-dark-800/80 variant for both PurchaseSubscription
and CustomPage views.
2026-03-04 21:40:40 +08:00
Wesley Liddick
adcfb44cb7 Merge pull request #761 from james-6-23/main
feat: 修复 v0.1.89 OAuth 401 永久锁死账号问题,改用临时不可调度实现自动恢复;增强二次 401 自动升级为错误状态,添加 DB   回退确保生效;管理后台新增临时不可调度状态筛选
2026-03-04 21:11:24 +08:00
kyx236
3d79773ba2 Merge branch 'main' of https://github.com/james-6-23/sub2api 2026-03-04 20:25:39 +08:00
kyx236
6aa8cbbf20 feat: 二次 401 直接升级为错误状态,添加 DB 回退确保生效
账号首次 401 仅临时不可调度,给予 token 刷新窗口;若恢复后再次 401
说明凭证确实失效,直接升级为错误状态以避免反复无效调度。

- 缓存中 reason 为空时从 DB 回退读取,防止升级判断失效
- ClearError 同时清除临时不可调度状态,管理员恢复后重新给予一次机会
- 管理后台账号列表添加"临时不可调度"状态筛选
- 补充 DB 回退场景单元测试
2026-03-04 20:25:15 +08:00
shaw
742e73c9c2 fix: 优化充值/订阅菜单的icon 2026-03-04 17:24:09 +08:00
shaw
f8de2bdedc fix(frontend): settings页面分tab拆分 2026-03-04 16:59:57 +08:00
shaw
59879b7fa7 fix(i18n): replace hardcoded English strings in EmailVerifyView with i18n calls 2026-03-04 15:58:44 +08:00
Wesley Liddick
27abae21b8 Merge pull request #724 from PMExtra/feat/registration-email-domain-whitelist
feat(registration): add email domain whitelist policy
2026-03-04 15:51:51 +08:00
shaw
0819c8a51a refactor: 消除重复的 normalizeAccountIDList,补充 PR#754 新增组件的单元测试
- 删除 account_today_stats_cache.go 中重复的 normalizeAccountIDList,统一使用 id_list_utils.go 的 normalizeInt64IDList
- 新增 snapshot_cache_test.go:覆盖 snapshotCache、buildETagFromAny、parseBoolQueryWithDefault
- 新增 id_list_utils_test.go:覆盖 normalizeInt64IDList、buildAccountTodayStatsBatchCacheKey
- 新增 ops_query_mode_test.go:覆盖 shouldFallbackOpsPreagg、cloneOpsFilterWithMode
2026-03-04 15:22:46 +08:00
Wesley Liddick
9dcd3cd491 Merge pull request #754 from xvhuan/perf/admin-core-large-dataset
perf(admin): 优化后台大数据场景加载性能(仪表盘/用户/账号/Ops)
2026-03-04 15:15:13 +08:00
Wesley Liddick
49767cccd2 Merge pull request #755 from xvhuan/perf/admin-usage-fast-pagination-main
perf(admin-usage): 优化 usage 大表分页,默认避免全量 COUNT(*)
2026-03-04 14:15:57 +08:00
PMExtra
29fb447daa fix(frontend): remove unused variables 2026-03-04 14:12:08 +08:00
xvhuan
f6fe5b552d fix(admin): resolve CI lint and user subscriptions regression 2026-03-04 14:07:17 +08:00
PMExtra
bd0801a887 feat(registration): add email domain whitelist policy 2026-03-04 13:54:18 +08:00
xvhuan
05b1c66aa8 perf(admin-usage): avoid expensive count on large usage_logs pagination 2026-03-04 13:51:27 +08:00
xvhuan
80ae592c23 perf(admin): optimize large-dataset loading for dashboard/users/accounts/ops 2026-03-04 13:45:49 +08:00
shaw
ba6de4c4d4 feat: /keys页面支持表单筛选 2026-03-04 11:29:31 +08:00
shaw
46ea9170cb fix: 修复自定义菜单页面管理员视角菜单不生效问题 2026-03-04 10:44:28 +08:00
shaw
7d318aeefa fix: 恢复check_pnpm_audit_exceptions.py 2026-03-04 10:20:19 +08:00
shaw
0aa3cf677a chore: 清理一些无用的文件 2026-03-04 10:15:42 +08:00
shaw
72961c5858 fix: Anthropic 平台无限流重置时间的 429 不再误标记账号限流 2026-03-04 09:36:24 +08:00
Wesley Liddick
a05711a37a Merge pull request #742 from zqq-nuli/fix/ops-error-detail-upstream-payload
fix(frontend): show real upstream payload in ops error detail modal
2026-03-04 09:04:11 +08:00
zqq61
efc9e1d673 fix(frontend): prefer upstream payload for generic ops error body 2026-03-03 23:45:34 +08:00
Wesley Liddick
a11ac188c2 Merge pull request #738 from DaydreamCoding/feat/ungrouped-key-setting
feat(gateway): 系统设置控制未分组 Key 调度 — Handler 层中间件拦截
2026-03-03 21:03:31 +08:00
Wesley Liddick
60350d298a Merge pull request #735 from alfadb/fix/count-tokens-default-ignore
fix(ops): 默认忽略 count_tokens 404 错误
2026-03-03 21:02:46 +08:00
shaw
838dad8759 feat: 重构 /v1/usage 端点,支持 quota_limited 和 unrestricted 双模式
- quota_limited 模式:返回 Key 级别的总额度、速率限制窗口用量和过期时间
- unrestricted 模式:返回订阅限额或钱包余额信息(向后兼容)
- 新增 model_stats 字段,支持 start_date/end_date 参数查询按模型用量统计
- 提取 buildUsageData/parseUsageDateRange 等辅助方法,减少主函数复杂度
- 新增 APIKeyService.GetRateLimitData 和 UsageService.GetAPIKeyModelStats
2026-03-03 20:59:12 +08:00
shaw
a728dfe0c6 refactor: 重构 api_key_auth 中间件,用 skipBilling 替代 7 处散落的 isUsageQuery
将中间件职责拆分为鉴权(Authentication)和计费执行(Billing Enforcement)两层:
- 鉴权层(disabled/IP/用户状态)始终执行
- 计费层(过期/配额/订阅/余额)用单一 skipBilling 守卫整块控制

/v1/usage 端点只需鉴权不需计费,skipBilling 仅出现 2 处(订阅加载错误处理 + 计费块守卫),
取代了之前 isUsageQuery 散布在 7 个 if 分支中的控制流。
2026-03-03 20:58:00 +08:00
QTom
0c7cbe3566 feat(gateway): 系统设置控制未分组 Key 调度 — Handler 层中间件拦截
新增系统设置 allow_ungrouped_key_scheduling(默认关闭),
未分组的 API Key 在网关请求时直接返回 403,
由 RequireGroupAssignment 中间件统一拦截,
支持 Anthropic / Google 两种错误格式响应。

全栈实现:常量 → 结构体 → 解析/更新/初始化 → DTO → 管理接口 →
中间件 → 路由注册 → 前端设置界面 + i18n。
2026-03-03 19:56:27 +08:00
alfadb
832b0185c7 style: fix gofmt formatting in ops_settings.go
Remove extra space before inline comment to pass golangci-lint gofmt check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:00:49 +08:00
alfadb
b1719b26d1 fix(ops): 默认忽略 count_tokens 404 错误
将 IgnoreCountTokensErrors 默认值从 false 改为 true。

count_tokens 返回 404 是预期业务行为(上游不支持 endpoint,
客户端应 fallback 到本地 tokenizer 估算),不应被视为错误。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:50:13 +08:00
shaw
ccf6a921c7 fix: 修复 PR #723 引入的 CI lint 和 test 编译错误
- wire_gen_test.go: 补充 NewTokenRefreshService 缺失的 tempUnschedCache 参数
- config.go, token_refresh_service.go: 修复 gofmt 格式问题
2026-03-03 16:45:29 +08:00
Wesley Liddick
197c570baa Merge pull request #723 from zqq-nuli/fix/oauth-401-temp-unschedulable
fix: OAuth 401 不再永久锁死账号,改用临时不可调度实现自动恢复
2026-03-03 16:33:07 +08:00
shaw
0fe09f1d40 fix: 恢复 PR #682 中被误替换为占位符的 OAuth client_secret
PR #682 (release → main 全量同步) 将 Antigravity 和 Gemini CLI 的
OAuth client_secret 硬编码值替换为了 "GOCSPX-your-client-secret" 占位符,
导致未配置环境变量的部署环境中 token 刷新失败。

恢复内容:
- antigravity/oauth.go: 恢复真实 client_secret
- antigravity/oauth_test.go: 恢复测试断言中的真实值
- geminicli/constants.go: 恢复真实 client_secret
2026-03-03 16:27:28 +08:00
shaw
4a91954532 fix: correct migration 061 checksum and add missing BillingCache mock methods
- Fix fileChecksum for 061 migration: use TrimSpace hash (66207e7a) instead
  of raw sha256sum (97bdd9a3), matching the actual runtime computation
- Add 222b4a09 as accepted DB checksum for 061 migration
- Add missing GetAPIKeyRateLimit/SetAPIKeyRateLimit/UpdateAPIKeyRateLimitUsage/
  InvalidateAPIKeyRateLimit methods to mock BillingCache in test stubs
- Fix NewBillingCacheService call in singleflight test (add apiKeyRepo param)
2026-03-03 16:11:05 +08:00
shaw
b8b5cec35c fix: resolve CI lint errors and test compilation failures for rate limit feature
- Fix errcheck: properly handle rows.Close() error via named return + defer closure
- Fix gofmt: auto-format billing_cache.go, api_key_service.go, billing_cache_service.go
- Add missing rate limit interface methods to 4 test stubs (GetRateLimitData, IncrementRateLimitUsage, ResetRateLimitWindows)
- Fix NewBillingCacheService calls missing the new apiKeyRepo parameter
2026-03-03 15:43:08 +08:00
Wesley Liddick
43c203333e Merge pull request #733 from DaydreamCoding/fix/group-isolation
fix(gateway): 分组隔离 — 禁止未分组账号被跨组调度
2026-03-03 15:10:30 +08:00
Wesley Liddick
1c6393b131 Merge pull request #732 from xvhuan/perf/admin-dashboard-preagg
perf(admin): 优化 Dashboard 大数据量加载(预聚合趋势+异步用户趋势)
2026-03-03 15:10:20 +08:00
Wesley Liddick
22f04e72e5 Merge pull request #731 from xvhuan/fix/061-bounded-backfill-startup
fix(migrations): 061 迁移改为限时分批回填,避免启动阻塞导致 502
2026-03-03 15:08:18 +08:00
shaw
5f3debf65b chore: add migration for api key rate limit fields 2026-03-03 15:05:15 +08:00
shaw
fd8ef27535 Merge branch 'main' of github.com:Wei-Shaw/sub2api
# Conflicts:
2026-03-03 15:04:03 +08:00
shaw
a80ec5d8bb feat: apikey支持5h/1d/7d速率控制 2026-03-03 15:01:10 +08:00
QTom
530a16291c fix(gateway): 分组隔离 — 禁止未分组账号被跨组调度
当 API Key 无分组时,调度仅从未分组账号池中选取。
修复 isAccountInGroup 在 groupID==nil 时的逻辑,
同时补全 scheduler_snapshot_service 和 gemini_compat_service
中的 SimpleMode 保护,确保分组隔离在所有调度路径生效。

新增 ListSchedulableUngroupedByPlatform/s 方法,
使用 Ent 的 Not(HasAccountGroups()) 谓词实现未分组账号隔离。
新增 17 个单元和端到端隔离测试,覆盖所有分支和边界条件。
2026-03-03 13:20:58 +08:00
xvhuan
7be8f4dc6e perf(admin-dashboard): accelerate trend load with pre-aggregation and async user trend 2026-03-03 11:53:54 +08:00
Wesley Liddick
9792b17597 Merge pull request #729 from touwaeriol/pr/fix-admin-menu-visibility
fix(frontend): admin custom menu items not showing in sidebar
2026-03-03 11:35:59 +08:00
ius
99f1e3ff35 fix(migrations): avoid startup outage from 061 full-table backfill 2026-03-03 11:01:22 +08:00
erio
5ba71cd2f1 fix(frontend): admin custom menu items not showing in sidebar
The public settings API filters out menu items with visibility='admin',
so customMenuItemsForAdmin was always empty when reading from
cachedPublicSettings. Fix by loading custom menu items from the admin
settings API (via adminSettingsStore) which returns all items unfiltered.

Changes:
- adminSettings store: store custom_menu_items from admin settings API
- AppSidebar: read admin menu items from adminSettingsStore instead of
  cachedPublicSettings
- CustomPageView: merge public and admin menu items so admin users can
  access admin-only custom pages
2026-03-03 10:45:35 +08:00
Wesley Liddick
b7df7ce5d5 Merge pull request #726 from DaydreamCoding/feat/dual-mode-umq
feat(gateway): 双模式用户消息队列 — 串行队列 + 软性限速
2026-03-03 08:41:34 +08:00
Wesley Liddick
405829dc30 Merge pull request #727 from touwaeriol/pr/custom-menu-pages
feat: custom menu pages with iframe embedding and CSP injection
2026-03-03 08:34:57 +08:00
erio
451a851118 fix: remove unused sanitizeCustomMenuItemsJSON function
Replaced by filterUserVisibleMenuItems which includes both array
validation and admin-item filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:13:08 +08:00
erio
e97c376681 fix: security hardening and architectural improvements for custom menu
1. (Critical) Filter admin-only menu items from public API responses -
   both GetPublicSettings handler and GetPublicSettingsForInjection now
   exclude visibility=admin items, preventing unauthorized access to
   admin menu URLs.

2. (Medium) Validate JSON array structure in sanitizeCustomMenuItemsJSON -
   use json.Unmarshal into []json.RawMessage instead of json.Valid to
   reject non-array JSON values that would cause frontend runtime errors.

3. (Medium) Decouple router from business JSON parsing - move origin
   extraction logic from router.go to SettingService.GetFrameSrcOrigins,
   eliminating direct JSON parsing of custom_menu_items in the routing
   layer.

4. (Low) Restrict custom menu item ID charset to [a-zA-Z0-9_-] via
   regex validation, preventing route-breaking characters like / ? # or
   spaces.

5. (Low) Handle crypto/rand error in generateMenuItemID - return error
   instead of silently ignoring, preventing potential duplicate IDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:05:01 +08:00
erio
7541e243bc style: fix gofmt alignment in setting_service.go 2026-03-03 06:38:04 +08:00
erio
50a8116ae9 fix: update SecurityHeaders call sites to match new signature 2026-03-03 06:37:50 +08:00
erio
bf6fe5e962 fix: custom menu security hardening and code quality improvements
- Add admin menu permission check in CustomPageView (visibility + role)
- Sanitize SVG content with DOMPurify before v-html rendering (XSS prevention)
- Decouple router.go from dto package using anonymous struct
- Consolidate duplicate parseCustomMenuItems into dto.ParseCustomMenuItems
- Enhance menu item validation (count, length, ID uniqueness limits)
- Add audit logging for purchase_subscription and custom_menu_items changes
- Update API contract test to include custom_menu_items field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 06:23:56 +08:00
erio
e4f8799323 fix: include custom_menu_items in GetPublicSettingsForInjection 2026-03-03 06:21:23 +08:00
erio
1f95524996 feat: ImageUpload component, custom page title, sidebar menu order 2026-03-03 06:20:10 +08:00
erio
a50d5d351b fix: replace curly quotes with straight quotes in domain_constants.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 06:18:35 +08:00
erio
067810fa98 feat: custom menu pages with iframe embedding and CSP injection
Add configurable custom menu items that appear in sidebar, each rendering
an iframe-embedded external page. Includes shared URL builder with
src_host/src_url tracking, CSP frame-src multi-origin deduplication,
admin settings UI, and i18n support.

chore: bump version to 0.1.87.19

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 06:18:20 +08:00
QTom
a9285b8a94 feat(gateway): 双模式用户消息队列 — 串行队列 + 软性限速
新增 UMQ (User Message Queue) 双模式支持:
- serialize: 账号级分布式串行锁 + RPM 自适应延迟(严格限流)
- throttle: 仅 RPM 自适应前置延迟,不阻塞并发(软性限速)

后端:
- config: 新增 Mode 字段,保留 Enabled 向后兼容
- service: 新增 UserMessageQueueService(Lua 锁/延迟算法/清理 worker)
- repository: 新增 UserMsgQueueCache(Redis Lua acquire/release/force-release)
- handler: 新增 UserMsgQueueHelper(SSE ping + 等待循环 + throttle)
- gateway: 按 mode 分支集成 serialize/throttle 逻辑
- lint: 修复 gofmt rewrite rules、errcheck 类型断言、staticcheck QF1012

前端:
- 三态选择器 UI(关闭/软性限速/串行队列)替代 toggle 开关
- BulkEdit 支持 null 语义(不修改)
- i18n 中英文文案

通过 6 轮专家评审(42 次 review)、golangci-lint、单元测试、集成测试。
2026-03-03 01:05:11 +08:00
zqq61
ec6bcfeb83 fix: OAuth 401 不再永久锁死账号,改用临时不可调度实现自动恢复
OAuth 账号收到 401 时,原逻辑同时设置 expires_at=now() 和 SetError(),
但刷新服务只查询 status=active 的账号,导致 error 状态的账号永远无法
被刷新服务拾取,expires_at=now() 实际上是死代码。

修复:
- OAuth 401 使用 SetTempUnschedulable 替代 SetError,保持 status=active
- 新增 oauth_401_cooldown_minutes 配置项(默认 10 分钟)
- 刷新成功后同步清除 DB 和 Redis 中的临时不可调度状态
- 不可重试错误检查(invalid_grant 等)从 Antigravity 推广到所有平台
- 可重试错误耗尽后不再标记 error,下个刷新周期继续重试

恢复流程:
OAuth 401 → temp_unschedulable + expires_at=now → 刷新服务拾取
  → 成功: 清除 temp_unschedulable → 自动恢复
  → invalid_grant: SetError → 永久禁用
  → 网络错误: 仅记日志 → 下周期重试
2026-03-02 22:54:38 +08:00
Wesley Liddick
7abec1888f Merge pull request #712 from DaydreamCoding/feat/proxy-failfast-proxyurl
feat(proxy): 集中代理 URL 验证并实现全局 fail-fast
2026-03-02 16:52:22 +08:00
QTom
fdcbf7aacf feat(proxy): 集中代理 URL 验证并实现全局 fail-fast
提取 proxyurl.Parse() 公共包,将分散在 6 处的代理 URL 验证逻辑
统一收敛,确保无效代理配置在创建时立即失败,永不静默回退直连。

主要变更:
- 新增 proxyurl 包:统一 TrimSpace → url.Parse → Host 校验 → Scheme 白名单
- socks5:// 自动升级为 socks5h://,防止 DNS 泄漏(大小写不敏感)
- antigravity: http.ProxyURL → proxyutil.ConfigureTransportProxy 支持 SOCKS5
- openai_oauth: 删除 newOpenAIOAuthHTTPClient,收编至 httpclient.GetClient
- 移除未使用的 ProxyStrict 字段(fail-fast 已是全局默认行为)
- 补充 15 个 proxyurl 测试 + pricing/usage fail-fast 测试
2026-03-02 16:04:20 +08:00
Wesley Liddick
445bfdf242 Merge pull request #706 from PMExtra/feat/default-subscriptions-on-user-create
feat(settings): add default subscriptions for new users
2026-03-02 11:38:26 +08:00
PMExtra
0fba1901c8 fix(ci): fix backend unit test constructor arg and gofmt issues 2026-03-02 10:54:14 +08:00
Wesley Liddick
fc5b9c8235 Merge pull request #705 from DaydreamCoding/feat/fingerprint-ttl-lazy-renewal
feat(identity): 指纹缓存 TTL 懒续期机制
2026-03-02 08:33:23 +08:00
Wesley Liddick
f490f44501 Merge pull request #699 from geminiwen/fix/dashboard-tooltip-token-sort
fix(dashboard): sort recent usage tooltip labels by token consumption
2026-03-02 08:33:00 +08:00
PMExtra
7e02082209 feat(settings): add default subscriptions for new users
- add default subscriptions to admin settings

- auto-assign subscriptions on register and admin user creation

- add validation/tests and align settings UI with subscription selector patterns
2026-03-02 03:59:31 +08:00
QTom
d869ac95fa feat(identity): 指纹缓存 TTL 懒续期机制
- TTL 改为 7 天,配合 24 小时自动续期保持活跃账号永不过期
- 版本升级时采用合并语义,仅更新请求中实际存在的字段
- 添加产品名验证防止浏览器 UA 误判为更新版本
2026-03-02 01:12:41 +08:00
Gemini Wen
5c856460a6 fix(dashboard): sort recent usage tooltip labels by token consumption 2026-03-01 23:21:45 +08:00
Wesley Liddick
3613695f91 Merge pull request #697 from DaydreamCoding/feat/proxy-password-visibility
feat(admin): 代理密码可见性 + 复制代理 URL 功能
2026-03-01 22:21:30 +08:00
QTom
8fb7d476b8 feat(admin): 代理密码可见性 + 复制代理 URL 功能
- 新增 AdminProxy / AdminProxyWithAccountCount DTO,遵循项目 Admin DTO 分层模式
- Proxy.Password 恢复 json:"-" 隐藏,ProxyFromService 不再赋值密码(纵深防御)
- 管理员接口使用 ProxyFromServiceAdmin / ProxyWithAccountCountFromServiceAdmin
- 前端代理列表新增 Auth 列:显示用户名 + 掩码密码 + 眼睛图标切换可见性
- Address 列新增复制按钮:左键复制完整 URL,右键选择格式
- 编辑模态框密码预填充 + 脏标记,避免误更新
2026-03-01 21:29:31 +08:00
Wesley Liddick
dd8df483cd Merge pull request #696 from touwaeriol/feat/group-usage-distribution-chart
feat(dashboard): add group usage distribution chart to usage page
2026-03-01 20:35:58 +08:00
erio
65459a99b6 feat(dashboard): add group usage distribution chart to usage page
Add a doughnut chart showing usage statistics broken down by group on
the admin usage records page. The chart appears alongside the existing
model distribution chart (2-column grid), with the token usage trend
chart moved to a separate full-width row below.

Changes:
- backend/pkg/usagestats: add GroupStat type
- backend/service: add GetGroupStatsWithFilters interface method and implementation
- backend/repository: implement GetGroupStatsWithFilters with LEFT JOIN groups
- backend/handler: add GetGroupStats handler with full filter support
- backend/routes: register GET /admin/dashboard/groups route
- backend/tests: add GetGroupStatsWithFilters stubs to contract/sora tests
- frontend/types: add GroupStat interface
- frontend/api: add getGroupStats API function and types
- frontend/components: add GroupDistributionChart.vue doughnut chart
- frontend/views: update UsageView layout and load group stats in parallel
- frontend/i18n: add groupDistribution, group, noGroup keys (zh + en)
2026-03-01 20:10:51 +08:00
Wesley Liddick
2129584fd6 Merge pull request #695 from geminiwen/fix/group-limit-clear-on-unlimited-pr
fix(group): clear nullable limit fields on update
2026-03-01 19:25:01 +08:00
Wesley Liddick
2da9c216c3 Merge pull request #694 from salmanmkc/upgrade-github-actions-node24-general
Upgrade GitHub Actions to latest versions
2026-03-01 19:20:50 +08:00
Gemini Wen
c6e26c5a16 fix(group): clear nullable limit fields on update 2026-03-01 18:46:38 +08:00
Wesley Liddick
fd57fa4913 Merge pull request #690 from touwaeriol/pr/bulk-edit-mixed-channel-warning
feat: add mixed-channel warning for bulk account edit
2026-03-01 18:25:05 +08:00
Wesley Liddick
8c4d22b3f9 Merge pull request #685 from touwaeriol/pr/admin-create-and-redeem-docs
feat(admin): add create-and-redeem endpoint for payment integrations
2026-03-01 18:24:15 +08:00
Wesley Liddick
c221774c51 Merge pull request #693 from salmanmkc/upgrade-github-actions-node24
Upgrade GitHub Actions for Node 24 compatibility
2026-03-01 18:23:50 +08:00
erio
23686b1391 refactor(docs): move integration doc to docs/ and add download link in settings
- Move ADMIN_PAYMENT_INTEGRATION_API.md → docs/ADMIN_PAYMENT_INTEGRATION_API.md
- Update README.md reference path
- Add payment integration doc download link in admin settings UI (Purchase section)
- Add i18n keys: integrationDoc / integrationDocHint (zh + en)
2026-03-01 18:08:42 +08:00
Wesley Liddick
0fffba5423 Merge pull request #692 from DaydreamCoding/feat/CC_UA
feat(gateway): 添加 Claude Code 客户端最低版本检查功能
2026-03-01 18:03:44 +08:00
Salman Muin Kayser Chishti
0e0eb747b5 Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-03-01 09:10:06 +00:00
Salman Muin Kayser Chishti
f6f8695a8e Upgrade GitHub Actions for Node 24 compatibility
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-03-01 09:10:02 +00:00
QTom
b2141a96e2 fix(ci): 修复 golangci-lint 和 API 合约测试失败
- 修复 errcheck: singleflight 返回值类型断言添加 ok 检查
- 修复 gofmt: 格式化 setting_service.go 和 claude_code_validator_test.go
- 修复 TestAPIContracts: 在 GET /admin/settings 期望中添加 min_claude_code_version 字段
2026-03-01 16:39:21 +08:00
QTom
4280aca82c feat(gateway): 添加 Claude Code 客户端最低版本检查功能
- 通过 User-Agent 识别 Claude Code 客户端并提取版本号
- 在网关层验证客户端版本是否满足管理员配置的最低要求
- 在管理后台提供版本要求配置选项(英文/中文双语)
- 实现原子缓存 + singleflight 防止并发问题和 thundering herd
- 使用 context.WithoutCancel 隔离 DB 查询,避免客户端断连影响缓存
- 双 TTL 策略:60s 正常、5s 错误恢复,保证性能与可用性
- 仅检查 Claude Code 客户端,其他客户端不受影响
- 添加完整单元测试覆盖版本提取、比对、上下文操作
2026-03-01 15:45:44 +08:00
erio
c08889b021 fix: remove unused preload/snapshot functions and fix gofmt 2026-03-01 15:22:27 +08:00
erio
57ebe382f9 fix: remove dead code in BulkUpdateAccounts group binding loop 2026-03-01 15:03:50 +08:00
erio
73089bbfdf fix: display backend error message directly without i18n translation 2026-03-01 14:49:25 +08:00
erio
3a04552f98 fix: use i18n for mixed-channel warning messages and improve bulk pre-check
- BulkUpdate handler: add structured details to 409 response
- BulkUpdateAccounts: simplify to global pre-check before any DB write;
  remove per-account snapshot tracking which is no longer needed
- MixedChannelError.Error(): restore English message for API compatibility
- BulkEditAccountModal: use t() with details for both pre-check and 409
  fallback paths instead of displaying raw backend strings
- Update test to verify pre-check blocks on existing group conflicts
2026-03-01 14:39:07 +08:00
erio
b67bf2227e fix: update mixed channel warning message 2026-03-01 14:25:16 +08:00
erio
dde3b59e7b fix: handle mixed channel warning for multi-platform bulk edit
Previously, preCheckMixedChannelRisk() skipped when selectedPlatforms
had more than one entry, and the catch block in submitBulkUpdate had no
409 handling — so multi-platform conflicts just showed a generic error.

- Rename canPreCheck(): only call pre-check API for single-platform
  antigravity/anthropic selections (API requires a single platform param)
- Pass `built` into preCheckMixedChannelRisk() so pendingUpdatesForConfirm
  is set before returning false
- submitBulkUpdate: add 409 mixed_channel_warning catch as fallback for
  multi-platform case, saving baseUpdates for retry
- Remove needsMixedChannelCheck() gate on confirm_mixed_channel_risk flag;
  use mixedChannelConfirmed alone so multi-platform retry also works
2026-03-01 14:25:16 +08:00
erio
947800b95f fix: bulk edit mixed channel warning not showing confirmation dialog
The response interceptor in client.ts transforms errors into plain
objects {status, code, message}, but catch blocks were checking
error.response?.status (AxiosError format) which never matched.

- Add error field passthrough in client.ts interceptor
- Refactor BulkEditAccountModal to use pre-check API (checkMixedChannelRisk)
  before submit, matching the single edit flow
- Fix EditAccountModal catch blocks to use interceptor error format
- Add bulk-update mixed channel unit tests
2026-03-01 14:25:16 +08:00
erio
7aa4c083a9 feat: bulk update accounts pre-check mixed channel risk with confirm dialog
- Move mixed channel check before any DB writes in BulkUpdateAccounts
- Return 409 from BulkUpdate handler for MixedChannelError
- Add ConfirmDialog to BulkEditAccountModal for mixed channel warning
- Update mixed channel warning message to Chinese
2026-03-01 14:25:02 +08:00
erio
fcc77d1383 refactor(purchase): use URL/searchParams only for purchase query merge 2026-03-01 02:04:19 +08:00
erio
997cd1e332 docs+ui: add bilingual payment integration doc and rename purchase entry to recharge/subscription 2026-03-01 01:53:14 +08:00
erio
2e88e23002 feat(frontend): append purchase query params and make integration doc bilingual 2026-03-01 00:57:26 +08:00
erio
39ca192c41 feat(admin): add create-and-redeem API and payment integration docs 2026-03-01 00:42:21 +08:00
shaw
f7fa71bc28 fix: 将 README 中 Codex WS 配置迁移至使用密钥弹窗
- 移除 README.md / README_CN.md 中的 Codex CLI WebSocket v2 配置示例
- UseKeyModal OpenAI 分组新增 "Codex CLI (WebSocket)" tab,区分普通模式与 WS 模式
- 普通模式 config.toml 不含 WebSocket 字段,WS 模式包含 supports_websockets 和 features 配置
- 所有配置统一使用 sub2api 作为 model_provider 名称
2026-02-28 23:35:31 +08:00
shaw
fbfbb26fd2 fix(ci): 将 gosec 集成到 golangci-lint 解决安全扫描超时
standalone gosec 扫描 24 万行 Go 代码在 CI 中持续超时,
将其作为 golangci-lint 的内置 linter 运行,复用 AST 解析和缓存大幅提速。

- 在 .golangci.yml 中启用 gosec 并迁移原有排除规则
- golangci-lint timeout 从 5m 提升到 30m
- 从 security-scan.yml 移除 standalone gosec 步骤
- 删除不再需要的 .gosec.json 配置文件
2026-02-28 23:12:38 +08:00
Wesley Liddick
493bd188d5 Merge pull request #680 from alfadb/fix/ops-normalize-nil-error-type
fix(ops): validate error_type against known whitelist before classification
2026-02-28 22:39:32 +08:00
Wesley Liddick
9fd95df5cf Merge pull request #679 from DaydreamCoding/feat/account-rpm-limit
feat: 添加账号级别 RPM(每分钟请求数)限流功能
2026-02-28 22:37:10 +08:00
shaw
54de3bf27a fix(ci): gosec 跳过自动生成的代码文件避免扫描超时
为 gosec 添加 -exclude-generated 标志,跳过带有
"// Code generated" 注释的文件(如 wire_gen.go),
防止安全扫描因分析自动生成代码而超时。
2026-02-28 22:30:53 +08:00
Wesley Liddick
4587c3e53e Merge pull request #670 from DaydreamCoding/feat/admin-apikey-group-update
feat(admin): 添加管理员直接修改用户 API Key 分组的功能
2026-02-28 22:20:29 +08:00
shaw
be18bc6fc3 chore: 恢复数据库迁移文件060和修正版本号 2026-02-28 22:02:01 +08:00
QTom
212cbbd3a2 fix: add missing rpmCache nil arg in sora_client_handler_test 2026-02-28 21:30:59 +08:00
QTom
6f9e690345 test(sora): 补充测试 stub 中缺失的 AddGroupToAllowedGroups 方法
feat/admin-apikey-group-update 分支给 UserRepository 接口新增了
AddGroupToAllowedGroups 方法,需要在测试 stub 中补充实现以通过编译。
- sora_client_handler_test.go: stubUserRepoForHandler
- sora_generation_service_test.go: stubUserRepoForQuota
2026-02-28 20:55:31 +08:00
QTom
115d06edf0 fix: 修复 gofmt 格式问题 2026-02-28 20:38:35 +08:00
QTom
e135435ce2 fix: sync test constructor calls with new rpmCache parameter
Add missing nil argument for rpmCache to NewAccountHandler (5 sites)
and NewGatewayService (2 sites) after RPM feature expanded their
signatures.
2026-02-28 20:38:35 +08:00
QTom
cd09adc3cc fix: add sanitizeExtraBaseRPM to BatchCreate handler
Ensures base_rpm validation (clamp 0-10000) is consistent across
all four account mutation paths: Create, Update, BulkUpdate, BatchCreate.
2026-02-28 20:38:06 +08:00
QTom
2491e9b5ad fix: round-3 review fixes for RPM limiting
- Add sanitizeExtraBaseRPM to BulkUpdate handler (was missing)
- Add WindowCost scheduling checks to legacy non-sticky selection
  paths (4 sites), matching existing sticky + load-aware coverage
- Export ParseExtraInt from service package, remove duplicate
  parseExtraIntForValidation from admin handler
2026-02-28 20:38:06 +08:00
QTom
e63c83955a fix: address deep code review issues for RPM limiting
- Move IncrementRPM after Forward success to prevent phantom RPM
  consumption during account switch retries
- Add base_rpm input sanitization (clamp to 0-10000) in Create/Update
- Add WindowCost scheduling checks to legacy path sticky sessions
  (4 check sites + 4 prefetch sites), fixing pre-existing gap
- Clean up rpm_strategy/rpm_sticky_buffer when disabling RPM in
  BulkEditModal (JSONB merge cannot delete keys, use empty values)
- Add json.Number test cases to TestGetBaseRPM/TestGetRPMStickyBuffer
- Document TOCTOU race as accepted soft-limit design trade-off
2026-02-28 20:38:06 +08:00
QTom
4b72aa33f3 fix: add enableRpmLimit to hasAnyFieldEnabled check in BulkEditModal
Without this, submitting a bulk edit with only RPM changes would be
rejected as "no fields selected".
2026-02-28 20:37:37 +08:00
QTom
ff9683b0fc fix: move RPM prefetch before routing segment in legacy/mixed paths
Ensures isAccountSchedulableForRPM calls within the routing segment
hit the prefetch cache instead of querying Redis individually.
2026-02-28 20:37:37 +08:00
QTom
607237571f fix: address code review issues for RPM limiting feature
- Use TxPipeline (MULTI/EXEC) instead of Pipeline for atomic INCR+EXPIRE
- Filter negative values in GetBaseRPM(), update test expectation
- Add RPM batch query (GetRPMBatch) to account List API
- Add warn logs for RPM increment failures in gateway handler
- Reset enableRpmLimit on BulkEditAccountModal close
- Use union type 'tiered' | 'sticky_exempt' for rpmStrategy refs
- Add design decision comments for rdb.Time() RTT trade-off
2026-02-28 20:37:37 +08:00
QTom
28ca7df297 feat: add RPM display to AccountCapacityCell 2026-02-28 20:37:10 +08:00
QTom
856c955386 feat: add RPM config to CreateAccountModal 2026-02-28 20:37:10 +08:00
QTom
e1c9016d90 feat: add RPM config to EditAccountModal 2026-02-28 20:37:10 +08:00
QTom
953c5036bf feat: add RPM types and i18n translations 2026-02-28 20:37:10 +08:00
QTom
37fa980565 feat: flatten RPM config fields in Account DTO 2026-02-28 20:37:10 +08:00
QTom
f648b8e026 feat: increment RPM counter before request forwarding 2026-02-28 20:37:10 +08:00
QTom
678c3ae132 feat: integrate RPM scheduling checks into account selection flow 2026-02-28 20:37:10 +08:00
QTom
c1c31ed9b2 feat: wire RPMCache into GatewayService and AccountHandler 2026-02-28 20:35:38 +08:00
QTom
777be05348 feat: add RPMCache interface and Redis implementation with Lua scripts 2026-02-28 20:34:22 +08:00
QTom
0bb3e4a98c feat: add RPM getter methods and schedulability check to Account model 2026-02-28 20:34:22 +08:00
QTom
9a91815b94 feat(admin): 完整实现管理员修改用户 API Key 分组的功能
## 核心功能
- 添加 AdminUpdateAPIKeyGroupID 服务方法,支持绑定/解绑/保持不变三态语义
- 实现 UserRepository.AddGroupToAllowedGroups 接口,自动同步专属分组权限
- 添加 HTTP PUT /api-keys/:id handler 端点,支持管理员直接修改 API Key 分组

## 事务一致性
- 使用 ent Tx 保证专属分组绑定时「添加权限」和「更新 Key」的原子性
- Repository 方法支持 clientFromContext,兼容事务内调用
- 事务失败时自动回滚,避免权限孤立

## 业务逻辑
- 订阅类型分组阻断,需通过订阅管理流程
- 非活跃分组拒绝绑定
- 负 ID 和非法 ID 验证
- 自动授权响应,告知管理员成功授权的分组

## 代码质量
- 16 个单元测试覆盖所有业务路径和边界用例
- 7 个 handler 集成测试覆盖 HTTP 层
- GroupRepo stub 返回克隆副本,防止测试间数据泄漏
- API 类型安全修复(PaginatedResponse<ApiKey>)
- 前端 ref 回调类型对齐 Vue 规范

## 国际化支持
- 中英文提示信息完整
- 自动授权成功/失败提示
2026-02-28 20:18:14 +08:00
QTom
000e621eb6 feat(admin): 添加管理员直接修改用户 API Key 分组的功能
- 新增 PUT /api/v1/admin/api-keys/:id 端点,允许管理员修改任意用户 API Key 的分组绑定
- 跳过用户级权限校验但保留分组有效性验证,修改后触发认证缓存失效
- Service 层支持三态语义:nil=不修改,0=解绑,>0=绑定,<0=拒绝
- 指针值拷贝保证安全隔离,负数 groupID 返回 400 INVALID_GROUP_ID
- 前端 UserApiKeysModal 新增可点击的分组选择下拉框,支持多 Key 并发更新
- 下拉支持视口翻转和滚动关闭,按钮有 disabled 和加载状态
- 覆盖:后端 20 个单元测试 (Service 11 + Handler 9) + 前端 16 个 E2E 测试
- golangci-lint 0 issues, make test-unit 全部通过
2026-02-28 20:18:14 +08:00
alfadb
093d7ba858 fix(ops): use normalized error type for all classification functions
- Compute normalizedType once and pass to classifyOpsPhase,
  classifyOpsSeverity, classifyOpsIsBusinessLimited, classifyOpsIsRetryable
  instead of raw parsed.ErrorType
- Add test case verifying known type takes precedence over conflicting code

Addresses Copilot review feedback on PR #680.
2026-02-28 19:28:08 +08:00
alfadb
ce006a7a91 fix(ops): validate error_type against known whitelist before classification
Upstream proxies (account 4, 112) return `"<nil>"` as the error.type in
their JSON responses — a Go fmt.Sprintf("%v", nil) artifact. Since
`normalizeOpsErrorType` only checked for empty string, the literal
"<nil>" passed through and poisoned the entire classification chain:
error_phase was misclassified as "internal" (instead of "request"),
severity was inflated to P2, and the stored error_type was meaningless.

Add `isKnownOpsErrorType` whitelist so any unrecognised type falls
through to the code-based or default "api_error" classification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:28:08 +08:00
Wesley Liddick
9d795061af Merge pull request #682 from mt21625457/pr/all-code-sync-20260228
feat(openai-ws): support websocket mode v2, optimize relay performance, enhance sora
2026-02-28 19:11:40 +08:00
yangjianbo
1d1fc019dc fix(lint): resolve data management staticcheck warnings 2026-02-28 15:05:54 +08:00
yangjianbo
bb664d9bbf feat(sync): full code sync from release 2026-02-28 15:01:20 +08:00
Wesley Liddick
bfc7b339f7 Merge pull request #675 from wucm667/fix/gosec-timeout-exclude-ent-dir
fix(ci): 修复 gosec 扫描因 ent 生成代码导致超时的问题
2026-02-28 10:59:19 +08:00
wucm667
f30f8905ec fix(ci): 修复 gosec 扫描因 ent 生成代码导致超时的问题
【问题描述】
backend-security CI job 持续运行约 6 小时后被 GitHub Actions 强制取消,
表现为 'Run gosec' 步骤挂起,最终以 cancelled 状态结束。

【根本原因】
gosec 对 ./... 执行 AST 静态分析时,包含了 ent/ 目录下的
自动生成文件(如 mutation.go 共 24800 行),导致分析时间
超出 GitHub Actions 默认的 6 小时上限。

【修复方案】
1. gosec 命令增加 -exclude-dir=ent 跳过自动生成代码目录
2. backend-security job 增加 timeout-minutes: 15,避免未来
   类似问题再次长时间卡死后才被发现

ent/ 目录内容全部由 Ent ORM 框架自动生成,开发者不直接编写,
不需要纳入人工安全审计范围,排除后不影响扫描有效性。
2026-02-28 10:20:57 +08:00
Wesley Liddick
3bae525026 Merge pull request #650 from wucm667/feat/sync-page-title-on-locale-change
feat(i18n): 切换语言时同步更新页面标题
2026-02-27 19:48:36 +08:00
shaw
df00805a2a feat(frontend): 为管理端用量页面添加列显示设置 2026-02-27 19:41:26 +08:00
Wesley Liddick
a88ee96518 Merge pull request #665 from touwaeriol/fix/2k-image-default-pricing
fix: add 2K image default pricing at 1.5x base price
2026-02-27 19:20:44 +08:00
Wesley Liddick
3cc2f9bd57 Merge pull request #664 from wucm667/fix/account-priority-hint
fix(frontend): add priority hint in edit account modal
2026-02-27 19:19:36 +08:00
erio
d1b684b782 fix: add 2K image default pricing at 1.5x base price
Previously 2K images used the same base price as 1K ($0.134).
Now 2K uses 1.5x multiplier ($0.201), consistent with 4K using 2x ($0.268).

- Backend: add 2K size branch in getDefaultImagePrice
- Frontend: update 2K placeholder from 0.134 to 0.201
- Tests: update assertions for new 2K default price
2026-02-27 17:37:30 +08:00
wucm667
6460d4ad3a fix(frontend): add priority hint in edit account modal 2026-02-27 16:00:11 +08:00
Wesley Liddick
19ea392d5d Merge pull request #663 from touwaeriol/fix/update-antigravity-useragent-version
fix: update antigravity user-agent version to 1.19.6
2026-02-27 15:28:45 +08:00
Wesley Liddick
fb4d016176 Merge pull request #659 from touwaeriol/feature/gemini-3.1-flash-image
feat: 新增 gemini-3.1-flash-image 支持,替代 gemini-3-pro-image
2026-02-27 15:28:33 +08:00
erio
afec747d9e fix: update antigravity user-agent version to 1.19.6
Update the default user-agent version from 1.18.4 to 1.19.6
to match the latest official antigravity client.
2026-02-27 12:31:51 +08:00
erio
7388fcce41 fix: gofmt alignment in constants.go 2026-02-27 09:52:50 +08:00
erio
a6f9f9f968 feat: replace gemini-3-pro-image with gemini-3.1-flash-image
- Add migration 060 to update model_mapping for all antigravity accounts
- Remove gemini-3-pro-image and gemini-3-pro-image-preview mappings
- Add gemini-3.1-flash-image and gemini-3.1-flash-image-preview mappings
- Update frontend usage window to show GImage for new model
- Update isImageGenerationModel to support new model
2026-02-27 09:52:50 +08:00
Wesley Liddick
29759721e0 Merge pull request #651 from cagedbird043/pr/bulk-edit-platform-filter
fix(frontend): 批量编辑添加跨平台模型映射警告与智能过滤
2026-02-27 09:03:00 +08:00
Wesley Liddick
1941b20521 Merge pull request #657 from alfadb/fix/count-tokens-404-passthrough
fix(gateway): count_tokens 不支持时返回 404 而非伪造的 200
2026-02-27 08:42:46 +08:00
alfadb
e6969acb50 fix: address review - fix log wording and add response body assertion in test 2026-02-26 23:49:30 +08:00
alfadb
9489531431 fix(gateway): return 404 instead of fake 200 for unsupported count_tokens endpoint
PR #635 returned HTTP 200 with {"input_tokens": 0} when upstream doesn't
support count_tokens (404). This caused Claude Code CLI to trust the zero
value, believing context uses 0 tokens, so auto-compression never triggers.

Fix: return 404 with proper error body so CLI falls back to its local
tokenizer for accurate estimation. Return nil (not error) to avoid
polluting ops error metrics with expected 404s.

Affected paths:
- Passthrough APIKey accounts: upstream 404 now passed through as 404
- Antigravity accounts: same fix (was also returning fake 200)
2026-02-26 23:34:53 +08:00
cagedbird043
32b7c0ca9b feat(frontend): 补齐 GPT-5.3 系列模型到白名单、批量编辑列表与预设映射
- useModelWhitelist.ts 添加 gpt-5.3-codex、gpt-5.3-codex-spark
- BulkEditAccountModal.vue 添加 5.3 模型选项与预设按钮(含 5.2→5.3 升级映射)
2026-02-26 16:04:15 +08:00
shaw
4ac57b4edf fix: 临时移除fast-mode-2026-02-01避免429问题 2026-02-26 15:44:28 +08:00
cagedbird043
685a1e0ba3 feat(i18n): 添加批量编辑跨平台警告的中英文翻译 2026-02-26 15:24:50 +08:00
cagedbird043
e350aab1bd fix(frontend): 批量编辑添加跨平台模型映射警告与过滤
- 新增 selectedPlatforms prop,从父组件传入选中账号的平台集合
- 根据选中平台过滤模型列表与预设映射按钮,避免误操作
- 混选多平台时显示 amber 警告横幅,提醒用户注意映射适用性
- 仅警告不阻断,保持加法兼容
2026-02-26 15:24:50 +08:00
Wesley Liddick
0dd6986e28 Merge pull request #639 from cagedbird043/pr/refactor-antigravity-model-source
refactor(admin): 消除测试连接 Gemini 模型硬编码,统一由 DefaultModels 提供
2026-02-26 14:57:13 +08:00
Wesley Liddick
6d0102a70c Merge pull request #638 from cagedbird043/pr/antigravity-claude-model-cleanup
feat(antigravity): 更新 opencode.json 模板至 Claude 4.6 并补齐模型支持
2026-02-26 14:55:32 +08:00
cagedbird043
f96a2a18c1 feat(opencode): 更新 opencode.json 模板至 Claude 4.6(默认启用 thinking) 2026-02-26 14:27:51 +08:00
cagedbird043
f955b04a6f feat(frontend): 补齐 Antigravity Claude 4.6 前端预设映射与显示 2026-02-26 14:27:51 +08:00
cagedbird043
2fd6ac319b feat(antigravity): 添加 Claude Opus/Sonnet 4.6 后端模型定义 2026-02-26 14:27:51 +08:00
wucm667
82fbf452a8 feat(i18n): 切换语言时同步更新页面标题
- resolveDocumentTitle() 新增 titleKey 参数,优先通过 i18n 翻译
- router beforeEach 中将路由 meta.titleKey 传入标题解析函数
- setLocale() 切换语言后同步刷新 document.title
2026-02-26 14:04:13 +08:00
cagedbird043
ba69736f55 refactor(admin): 测试连接模型列表改为复用 antigravity.DefaultModels,消除硬编码重复 2026-02-26 13:34:10 +08:00
shaw
c75c6b6858 fix: 将 DriveClient 注入 GeminiOAuthService,消除单元测试中的真实 HTTP 调用
FetchGoogleOneTier 原先在方法内部直接创建 DriveClient 实例,
导致单元测试中对 googleapis.com 发起真实 HTTP 请求,在 CI 环境
产生 401 错误。

将 DriveClient 作为依赖注入到 GeminiOAuthService,遵循项目
端口与适配器架构规范:
- 新增 repository/gemini_drive_client.go 作为 Provider
- 注册到 repository Wire ProviderSet
- 测试中使用 mockDriveClient 替代真实调用
2026-02-26 10:53:04 +08:00
Wesley Liddick
de61745bb2 Merge pull request #635 from alfadb/fix/count-tokens-fallback-for-proxy
fix: count_tokens 端点不支持时降级返回空值
2026-02-26 10:07:30 +08:00
Wesley Liddick
3fab0fcd4c Merge pull request #644 from LemonZuo/fix/remove-pgdata-env-var
移除 PostgreSQL 容器多余重复的 PGDATA 环境变量
2026-02-26 09:40:50 +08:00
alfadb
03bcd94ae5 fix: count_tokens 端点不支持时降级返回空值 (404 only)
第三方 Anthropic 中转站通常不支持 /v1/messages/count_tokens 端点,
上游返回 404 时降级返回 {input_tokens: 0},客户端 fallback 到本地估算。

- 仅匹配 404 状态码,语义明确:端点不存在
- 其他错误 (400/429/500) 保留原始处理链和 ops 遥测
- 无需解析错误消息内容,不依赖字符串匹配
- 新增 table-driven 测试覆盖 fallback 和 non-fallback 路径
2026-02-26 09:28:45 +08:00
Lemon
0343bc7777 fix: 移除 PostgreSQL 容器多余重複的 PGDATA 环境变量 2026-02-26 09:01:03 +08:00
Wesley Liddick
565d19acfd Merge pull request #636 from cagedbird043/pr/antigravity-gemini-3.1-models
fix(antigravity): 补全测试连接 Gemini 模型列表并新增 3.1 Pro High/Low 支持
2026-02-26 08:52:19 +08:00
Wesley Liddick
960acf1982 Merge pull request #632 from cagedbird043/pr/gemini-v1beta-template-align
feat: 对齐 Gemini v1beta 模型模板与映射顺序
2026-02-26 08:45:56 +08:00
cagedbird043
ece911521e fix(antigravity): 修正 Gemini 3.1 Pro High/Low 发布日期为 2026-02-19 2026-02-25 20:18:19 +08:00
cagedbird043
5d95e59742 fix(admin): 补全 antigravity 测试连接下拉框的 Gemini 模型列表 2026-02-25 18:51:47 +08:00
cagedbird043
01d084bbfd feat(antigravity): 新增 Gemini 3.1 Pro High 和 Gemini 3.1 Pro Low 模型支持 2026-02-25 18:51:47 +08:00
cagedbird043
7918fc2844 feat: 对齐 Gemini v1beta 模板模型与顺序 2026-02-25 15:19:23 +08:00
Wesley Liddick
31b30a6df2 Merge pull request #631 from cagedbird043/pr/opencode-template-openai-antigravity
feat: 补齐 OpenCode 模板中的 OpenAI 与 Antigravity 模型配置
2026-02-25 14:23:43 +08:00
cagedbird043
d217b59e0b feat: 补齐 Antigravity OpenCode 模板模型配置 2026-02-25 14:17:31 +08:00
cagedbird043
169a4b9d32 feat: 补齐 OpenAI OpenCode 模板模型配置 2026-02-25 14:17:31 +08:00
shaw
15f3ffb165 chore: 调整模型定价文件仓库 2026-02-25 13:50:21 +08:00
Wesley Liddick
02db1010dd Merge pull request #630 from DouDOU-start/main
Sora 平台: SDK 重构、JSON 转义修复及 AT 手动导入
2026-02-25 11:49:48 +08:00
huangenjun
935ea66681 fix: 修复 sora_sdk_client 类型断言未检查的 errcheck lint 错误
使用安全的 comma-ok 模式替代裸类型断言,避免 golangci-lint errcheck 报错。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:43:08 +08:00
huangenjun
26060e702f feat: Sora 平台支持手动导入 Access Token
新增 Access Token 输入方式,支持批量粘贴(每行一个)直接创建账号,
无需经过 OAuth 授权流程。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:33:07 +08:00
huangenjun
65d4ca2563 fix: 修复流式响应中 URL 的 & 被转义为 \u0026 的问题
新增 jsonMarshalRaw 使用 SetEscapeHTML(false) 替代 json.Marshal,
避免 HTML 字符转义导致客户端无法直接使用返回的 URL。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:32:56 +08:00
huangenjun
3c619a8da5 refactor: 使用 go-sora2api SDK 替代自建 Sora 客户端
使用 go-sora2api v1.1.0 SDK 替代原有 ~2000 行自建 HTTP/PoW/TLS 指纹代码,
SDK 提供高并发性能优化(实例级 rand、PoW 缓冲区复用、context.Context 支持)。

- 新增 SoraSDKClient 适配器实现 SoraClient 接口
- 精简 sora_client.go 为仅保留接口和类型定义
- 更新 Wire 绑定使用 SoraSDKClient
- 删除 SoraDirectClient、sora_curl_cffi_sidecar、sora_request_guard 等旧代码

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:15:38 +08:00
shaw
ded9b6c14e fix: upgrade utls to v1.8.2 to resolve GO-2026-4512 vulnerability 2026-02-25 08:57:43 +08:00
Wesley Liddick
609abbbd7c Merge pull request #624 from cagedbird043/pr/antigravity-gemini31-passthrough-buttons
feat: 补充 Antigravity 的 Gemini 3.1 Pro 透传快捷按钮
2026-02-25 08:45:49 +08:00
Wesley Liddick
1b4e504fad Merge pull request #625 from cagedbird043/pr/antigravity-default-gemini31-passthrough
fix: 默认补全 Antigravity 的 Gemini 3.1 Pro 透传映射
2026-02-25 08:45:16 +08:00
Wesley Liddick
0a3a445828 Merge pull request #628 from cagedbird043/pr/docs-model-mapping-bulk-edit-tip
docs: 增加跨平台批量修改导致模型映射丢失的排障经验
2026-02-25 08:31:31 +08:00
Wesley Liddick
c7e18bd5be Merge pull request #627 from touwaeriol/pr/bugfixes-and-enhancements
feat: 反重力(Antigravity)增强、Failover 重构及新模型支持
2026-02-25 08:30:25 +08:00
cagedbird043
083d202fe4 docs: 增加跨平台批量修改导致模型映射丢失的排障经验 2026-02-25 01:02:25 +08:00
erio
8365a8328b merge: resolve conflicts with upstream/main (Gemini 3→3.1 mappings) 2026-02-25 00:38:39 +08:00
erio
58f21e4b3a fix: correct gofmt alignment in gemini-3.1-pro fallback pricing 2026-02-25 00:23:37 +08:00
erio
5bd7408b2f fix: add fallback pricing for opus-4.6 and gemini-3.1-pro models 2026-02-25 00:10:07 +08:00
erio
c671e8dd1d fix: 统一gemini-3默认映射为非强制3.1 2026-02-24 23:24:48 +08:00
cagedbird043
a3aed3c4c3 fix: 默认补全 antigravity 的 Gemini 3.1 Pro 透传映射 2026-02-24 22:54:11 +08:00
cagedbird043
c008649584 feat: 补充 antigravity 的 Gemini 3.1 Pro 透传快捷按钮 2026-02-24 22:53:53 +08:00
Wesley Liddick
516f8f287c Merge pull request #623 from cagedbird043/fix/antigravity-mapping-upgrade-additions
fix: 补全 Antigravity 模型映射升级与快捷按钮
2026-02-24 22:50:24 +08:00
Wesley Liddick
66148690c6 Merge pull request #622 from cagedbird043/fix/auto-clear-account-error-on-usage
fix: 刷新用量成功后自动清理账号可恢复错误状态
2026-02-24 22:49:08 +08:00
Wesley Liddick
cadd7f546f Merge pull request #621 from cagedbird043/fix/gemini-auth-url-613
fix: 修复 Gemini 授权链接生成失败(issue #613)
2026-02-24 22:48:09 +08:00
erio
a3ff317f1c feat: optimize model rate limit indicator layout with short aliases
- Change layout from fixed 3-column grid to vertical-first responsive
  columns (1 col for ≤4 items, 2 cols for ≤8, 3 cols for 9+)
- Add short aliases for all known model scope keys (e.g. COpus46, CSon46,
  G3PH, G3F) to reduce badge width
- Display countdown timer directly on each badge (supports h/m/s)
- Retain legacy scope aliases for backward compatibility
2026-02-24 22:11:50 +08:00
erio
d8d4b0c0c7 fix: enable Gemini model_mapping UI and extend warmup to Antigravity
- Remove Gemini platform exclusion from model restriction UI in
  Create/Edit account modals (Gemini now supports model_mapping)
- Remove outdated Gemini model passthrough info cards
- Add model_mapping field to GeminiCredentials type
- Extend warmup request interception toggle to Antigravity platform
- Remove redundant try/catch in API key account creation
- Remove noisy gateway.request_completed debug log
- Reorganize Gemini model mapping sections in constants.go
2026-02-24 21:30:32 +08:00
erio
d616f8c854 refactor: remove unused ClientSecret constant
The ClientSecret constant was left as an empty string after
getClientSecret() was refactored to use defaultClientSecret.
Remove the dead constant and update the test accordingly.
2026-02-24 21:09:46 +08:00
erio
b6fa8b8eec fix: update tests for defaultClientSecret and align migration 058
- Fix oauth_test.go and client_test.go to use defaultClientSecret
  variable instead of env var (init() already sets the default)
- Align migration 058 gemini-3-pro-high/low/preview mappings with
  constants.go (map to 3.1 versions)
2026-02-24 21:06:10 +08:00
erio
36d2e6999b feat: add default value for Antigravity OAuth client secret
Add a built-in default for ANTIGRAVITY_OAUTH_CLIENT_SECRET so the
service works out of the box without requiring environment variable
configuration. The env var can still override the default.
2026-02-24 20:54:28 +08:00
cagedbird043
076c00063d feat: 补全 antigravity 模型映射快捷按钮 2026-02-24 20:31:36 +08:00
cagedbird043
ea8104c6a2 fix: antigravity 默认补全 gemini-3-flash 透传 2026-02-24 20:31:36 +08:00
erio
ca3e9336e1 test: update UserAgent version assertion to match 1.18.4 default 2026-02-24 20:31:02 +08:00
erio
f92ab48166 fix: add gemini-3.1-pro-preview to default Antigravity model mapping
Add missing gemini-3.1-pro-preview -> gemini-3.1-pro-high mapping to
DefaultAntigravityModelMapping for consistency with migration 059.
2026-02-24 20:06:19 +08:00
cagedbird043
c10267ce2b fix: 刷新用量成功后自动清理账号可恢复错误状态 2026-02-24 20:04:36 +08:00
cagedbird043
9bd6a62ab3 test: 更新 Gemini OAuth 内置回退测试用例 2026-02-24 20:04:05 +08:00
cagedbird043
0dbea6ca58 fix: 修复 Gemini 授权链接生成失败并改进错误提示 2026-02-24 20:04:05 +08:00
erio
6523b23221 revert: remove backend-ci.yml changes (fork-specific CI config) 2026-02-24 19:45:23 +08:00
erio
29c406dda0 feat: add migrations for sonnet-4-6 and gemini-3.1-pro model mappings
Add migration 058 to update existing Antigravity accounts with
claude-sonnet-4-6 in model_mapping. Add migration 059 to add
gemini-3.1-pro-high/low/preview mappings.
2026-02-24 19:40:30 +08:00
erio
483c8f246d chore: update default Antigravity UserAgent version to 1.18.4
Update the default ANTIGRAVITY_USER_AGENT_VERSION from 1.84.2 to
1.18.4 to match the current Antigravity-Manager desktop client.
2026-02-24 19:39:15 +08:00
erio
645f283108 feat: add claude-sonnet-4-6 and gemini-3.1-pro model support
Add claude-sonnet-4-6 to identity injection modelInfoMap and
Antigravity model selector. Add gemini-3.1-pro-high/low to
Antigravity model list and Sonnet 4.6 preset mapping.
2026-02-24 19:30:01 +08:00
erio
da6fd45000 chore: add sonnet-4-6 mapping, config defaults, and CI improvements
- Add claude-sonnet-4-6 to default Antigravity model mapping
- Add antigravity_extra_retries default value in config
- Add cache-dependency-path to CI setup-go for faster builds
- Simplify vitest config to avoid vite plugin compatibility issues
2026-02-24 18:55:39 +08:00
erio
fb3ef5f388 fix(frontend): add Gemini models to bulk edit and fix status grid layout
Add Gemini model presets to BulkEditAccountModal for bulk model mapping.
Fix AccountStatusIndicator model rate limit grid layout using proper
grid container.
2026-02-24 18:55:25 +08:00
erio
86bc76e352 test: add warmup request interception unit tests
Add comprehensive tests for warmup request interception behavior
covering Antigravity accounts with various credential configurations.
2026-02-24 18:55:11 +08:00
erio
644058174e fix(gemini): enable model_mapping filtering for Gemini API Key accounts
Remove the special case that bypassed model-supported checks for Gemini
API Key accounts, allowing model_mapping to filter requests properly.
Add tests for multiplatform model filtering behavior.
2026-02-24 18:54:59 +08:00
erio
4573868c08 fix(antigravity): bill with mapped model and use final model key for rate limiting
- Use mapped model (billingModel) instead of original request model for billing
- Use resolveFinalAntigravityModelKey for 429 rate limit model key,
  ensuring rate limit records match the actual upstream model
- Add regression tests for both fixes
2026-02-24 18:08:19 +08:00
erio
09166a52f8 refactor: extract failover error handling into FailoverState
- Extract duplicated failover logic from gateway_handler.go (3 places)
  and gemini_v1beta_handler.go into shared failover_loop.go
- Introduce FailoverState with HandleFailoverError and HandleSelectionExhausted
- Move helper functions (needForceCacheBilling, sleepWithContext) into failover_loop.go
- Add comprehensive unit tests (32+ test cases)
- Delete redundant gateway_handler_single_account_retry_test.go
2026-02-24 18:08:04 +08:00
erio
aaac1aaca9 feat: add mixed-channel precheck API for account-group binding
Add a dedicated CheckMixedChannel endpoint that allows the frontend
to pre-validate mixed channel risk before submitting create/update
requests. This improves UX by showing warnings earlier in the flow
instead of only after form submission.

Backend changes:
- Add CheckMixedChannelRequest struct and CheckMixedChannel handler
- Register POST /check-mixed-channel route
- Expose CheckMixedChannelRisk as public method on AdminService
- Simplify Create/Update 409 responses (remove details/require_confirmation)
- Add comprehensive handler tests and stub methods

Frontend changes:
- Add checkMixedChannelRisk API function and TypeScript types
- Refactor CreateAccountModal to precheck before step transition and submission
- Refactor EditAccountModal to precheck before update submission
- Replace pendingPayload pattern with action-based dialog flow
2026-02-24 17:16:53 +08:00
erio
59898c16c6 fix: fix intercept_warmup_requests config not being saved
Extract applyInterceptWarmup utility to unify all credential building
call sites:
- Fix upstream account creation missing intercept_warmup_requests write
- Fix apikey edit mode missing else-branch to clear the setting
- Add backend unit test for IsInterceptWarmupEnabled
- Add frontend unit test for credentialsBuilder
2026-02-24 16:48:16 +08:00
erio
0dacdf480b fix: distinguish client disconnection from upstream retry failure
Before this change, when a client disconnected mid-request, the error
message was "Upstream request failed after retries", which is misleading
and pollutes error logs. Now we check context.Err() to return a more
accurate "Client disconnected" message for both Claude and Gemini
forward paths.
2026-02-24 16:45:08 +08:00
erio
fdf9f68298 fix: update Claude usage window to support 4.6 models
The usage progress bar only matched claude-sonnet-4-5 and
claude-opus-4-5-thinking. After upgrading to 4.6, the backend returns
claude-sonnet-4-6/claude-opus-4-6-thinking which didn't match,
causing the Claude usage bar to not display.

- Add claude-sonnet-4-6 and claude-opus-4-6-thinking to the match list
- Rename label from "C4.5" to "Claude" for future-proofing
2026-02-24 16:44:18 +08:00
shaw
7be5e1734c fix: 修复 CI 集成测试因 context deadline exceeded 未被跳过而失败
skipIfExternalServiceUnavailable 检查了 "timeout" 但 Go 的
context.DeadlineExceeded 错误信息是 "context deadline exceeded",
不包含 "timeout" 子串,导致外部服务不可达时测试直接失败而非跳过。
2026-02-24 15:04:04 +08:00
shaw
bfe414670f chore: update version 2026-02-24 14:51:10 +08:00
shaw
e435a46db5 fix: 修复 antigravity UserAgent 重构遗留的编译错误和测试不匹配
- oauth.go: GetUserAgent() 缺少闭合大括号导致语法错误
- client_test.go/oauth_test.go: UserAgent 变量已重构为 GetUserAgent(),更新测试引用
- model_rate_limit_test.go: gemini-3-pro-preview 映射目标已更新为 gemini-3.1-pro-high,同步测试
2026-02-24 14:44:57 +08:00
Wesley Liddick
84bd881e68 Merge pull request #608 from cagedbird043/feature/gemini-3-to-3.1-mapping
feat: Antigravity 将 gemini-3-pro 路由升级到 gemini-3.1-pro
2026-02-24 14:08:42 +08:00
Wesley Liddick
a901117b8c Merge pull request #605 from cagedbird043/feature/antigravity-user-agent-configurable
feat: 让 Antigravity User-Agent 版本可通过环境变量配置
2026-02-24 14:08:25 +08:00
Wesley Liddick
6bccb8a8a6 Merge branch 'main' into feature/antigravity-user-agent-configurable 2026-02-24 14:01:43 +08:00
Wesley Liddick
3de1e0e485 Merge pull request #597 from 0-don/feat/add-gemini-3.1-pro-preview
feat: add gemini-3.1-pro-preview to model lists
2026-02-24 12:25:17 +08:00
shaw
492b852a1f fix: 幂等测试使用哈希值避免超出 VARCHAR(64) 限制
idempotency_key_hash 和 request_fingerprint 列为 VARCHAR(64),
而 uniqueTestValue 生成的字符串含完整测试名可能超过 64 字符。
新增 hashedTestValue 辅助函数对测试值做 SHA-256 哈希,
与生产逻辑一致且严格符合列宽限制。
2026-02-24 12:18:07 +08:00
shaw
8a137405d4 fix: 移除重复的 ptrTime 函数声明修复编译错误
idempotency_repo_integration_test.go 中的 ptrTime 与
scheduler_cache.go 中的声明冲突,导致 repository 包测试构建失败。
2026-02-24 11:49:01 +08:00
shaw
f431f5ed72 fix: 恢复backend-ci.yml 2026-02-24 11:37:14 +08:00
shaw
980fc9608f fix: 修复日志重复输出及清理冗余迁移逻辑
- logger: sinkCore 包装 tee core 时绕过了子 core 的 Check 级别过滤,
  导致每条日志同时写入 stdout 和 stderr,表现为启动日志重复显示。
  修复为正确委托 Check 给内部 tee core,sinkCore.Write 仅负责 sink 转发。
- migration 054: 移除冗余的遗留列回填逻辑,migration 009 已完成数据迁移,
  直接删除遗留列即可。
2026-02-24 11:31:19 +08:00
Wesley Liddick
07be258dca Merge pull request #603 from mt21625457/release
feat : 大幅度的性能优化 和 新增了很多功能
2026-02-24 11:08:47 +08:00
Wesley Liddick
dbdb29594c Merge pull request #606 from Nek0Neko/fix/user-balance-modal-dark-mode
fix: 更新余额显示样式以支持深色模式
2026-02-24 10:35:52 +08:00
yangjianbo
53d55bb92f feat: 工程清理 2026-02-24 10:19:58 +08:00
cagedbird043
3f3efff065 feat: 添加 Gemini 3→3.1 前端快捷按钮及后端映射(包括 3.1 透传) 2026-02-23 23:06:07 +08:00
Leon-mac
57b078f2c7 fix: 更新余额显示样式以支持深色模式 2026-02-23 22:27:09 +08:00
cagedbird043
1fc6ef3d4f feat: 让 User-Agent 版本可通过环境变量 ANTIGRAVITY_USER_AGENT_VERSION 配置,默认 1.84.2 2026-02-23 21:17:35 +08:00
yangjianbo
c2567831d9 fix(service): 使用 os.Root 修复 Sora 存储路径告警
- 将媒体写入和删除切换为 os.Root 沙箱 API
- 移除旧的路径拼接校验辅助函数并收敛删除逻辑
- 调整并新增相关单元测试覆盖删除行为

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 16:06:04 +08:00
yangjianbo
e8671fd7c2 fix(service): 修复 Sora 媒体落地路径穿越风险
- 新增安全路径拼接校验,确保目标文件仍在下载目录内
- 清理失败下载文件时复用安全校验,避免不安全删除路径
- 增加扩展名白名单归一化与相关单元测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:42:07 +08:00
yangjianbo
4950ee48a0 chore(version): 更新版本号至 0.1.83.4 2026-02-23 13:07:34 +08:00
yangjianbo
5fa45f3b8c feat(idempotency): 为关键写接口接入幂等并完善并发容错 2026-02-23 12:45:37 +08:00
yangjianbo
3b6584cc8d chore(version): 更新版本号至 0.1.83.3 2026-02-22 22:20:42 +08:00
yangjianbo
7be1195281 feat(api-key): 增加 API Key 上次使用时间并补齐测试 2026-02-22 22:07:17 +08:00
yangjianbo
1fae8d086d fix(codex): 补回窗口绝对重置时间类型定义 2026-02-22 21:06:22 +08:00
yangjianbo
10636d8a1f fix(codex): 修复额度窗口过期展示并补齐高覆盖测试
- 后端新增绝对重置时间字段计算(codex_5h_reset_at/codex_7d_reset_at)

- 前端统一窗口解析逻辑:绝对时间优先,updated_at+seconds 回退,过期自动归零

- 新增后端与前端单元测试,覆盖关键边界与异常场景
2026-02-22 21:04:52 +08:00
yangjianbo
c67f02eaf0 fix(jwt): 修复仅配置小时时会话提前失效问题
- 将 jwt.access_token_expire_minutes 默认值改为 0,未显式配置时回退 expire_hour

- 调整配置校验为允许 0,仅拒绝负数并补充优先级注释

- 新增配置与认证服务单元测试,覆盖分钟优先与小时回退场景

- 更新示例配置文档,明确分钟/小时优先级与默认行为
2026-02-22 17:37:35 +08:00
yangjianbo
0b32f61062 fix(ratelimit): 清除限流时同步清理临时不可调度状态
- ClearRateLimit 增加清理 temp_unschedulable 与缓存\n- 新增 ClearRateLimit 相关单元测试覆盖成功与失败分支
2026-02-22 17:00:29 +08:00
yangjianbo
2ee6c26676 fix(gateway): 修复粘性会话预取分组错配并优化并发等待热路径 2026-02-22 16:43:33 +08:00
yangjianbo
a89477ddf5 perf(gateway): 优化热点路径并补齐高覆盖测试 2026-02-22 13:31:30 +08:00
yangjianbo
2f520c8d47 docs(readme): 说明 Sora 功能暂不可用及技术问题
- 在 README.md 增加 Sora 暂不可用状态说明
- 在 README_CN.md 增加对应中文说明并标注恢复后可选
- 明确 gateway.sora_* 配置当前仅为预留项

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:06:56 +08:00
yangjianbo
33db7a0fb6 feat(gateway): 引入使用量记录有界 worker 池与自动扩缩容
- 新增 UsageRecordWorkerPool,支持有界队列、溢出降级策略与自动扩缩容
- 将 Gateway/OpenAI/Sora/Gemini 使用量记录改为提交到统一任务池执行
- 增加 usage_record 配置默认值与校验规则,并补充配置与任务提交相关测试
- 注入并托管 worker 池生命周期,服务退出时统一 StopAndWait

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:56:57 +08:00
yangjianbo
50b9897182 docs(perf): 新增后端热点API性能优化审计行动计划 2026-02-22 12:54:21 +08:00
yangjianbo
f8ac5538e2 Merge branch 'test' into release 2026-02-21 22:00:16 +08:00
yangjianbo
1985be26b2 fix(gateway): 恢复 Anthropic 透传流数据间隔超时保护并补充回归测试 2026-02-21 16:54:44 +08:00
yangjianbo
fdfc739b72 fix(anthropic): 补齐创建账号页自动透传开关并验证后端透传参数
- 在 CreateAccountModal 为 Anthropic API Key 增加自动透传开关

- 创建请求写入 extra.anthropic_passthrough 并补充状态重置

- 新增 AccountHandler 单测,验证 extra 字段从请求到 CreateAccountInput 的透传
2026-02-21 14:40:31 +08:00
yangjianbo
bde9dbc57a feat(anthropic): 支持 API Key 自动透传并优化透传链路性能
- 新增 Anthropic API Key 自动透传开关与后端透传分支(仅替换认证)

- 账号编辑页新增自动透传开关,默认关闭

- 优化透传性能:SSE usage 解析 gjson 快路径、减少请求体重复拷贝、优化流式写回与非流式 usage 解析

- 补充单元测试与 benchmark,确保 Claude OAuth 路径不受影响
2026-02-21 14:16:18 +08:00
yangjianbo
80510e5f16 fix(gateway): 明确旧协议接口不支持的错误提示
将 /v1/chat/completions 的拦截文案改为旧协议不支持,避免误导为会路由到 Sora。
明确要求客户端改用 /v1/responses。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:36:43 +08:00
yangjianbo
773f20ed5e Merge branch 'test' into release 2026-02-21 12:15:30 +08:00
yangjianbo
f323174d07 fix(openai): 修复 codex_cli_only 误拦截并补充 codex 家族识别
- 为 codex_cli_only 增加 originator 判定通道,避免仅依赖 User-Agent 误拦截
- 扩展官方客户端家族标识,补充 codex_chatgpt_desktop 等常见前缀
- 新增并更新单元测试与网关透传回归测试,覆盖 UA 与 originator 组合场景

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:06:24 +08:00
yangjianbo
987589eabc Merge branch 'test' into release 2026-02-21 10:07:53 +08:00
0-don
1004bd86ac feat: add gemini-3.1-pro-preview to model lists
Add the newly released Gemini 3.1 Pro model to both the
native API fallback list and the admin UI test model dropdown.
2026-02-20 23:27:30 +01:00
yangjianbo
03f69dd394 fix(proxy): 将401/405质量检测结果调整为告警 2026-02-20 14:42:07 +08:00
yangjianbo
d14c24bbf3 feat(proxy): 持久化质量检测结果并在列表展示 2026-02-20 12:13:04 +08:00
yangjianbo
48dc011b2a test(admin,service): 修复代理质量与计费单测口径 2026-02-19 21:39:31 +08:00
yangjianbo
b341810e60 fix(sora): 优化 challenge 重试与调试日志 2026-02-19 21:38:04 +08:00
yangjianbo
46d9aee6dd feat(proxy,sora): 增强代理质量检测与Sora稳定性并修复审查问题 2026-02-19 21:18:35 +08:00
yangjianbo
36a1a7998b feat(sora): 强制Sora走curl_cffi sidecar并完善校验测试 2026-02-19 20:29:31 +08:00
yangjianbo
40498aac9d feat(sora): 对齐sora2api分镜角色去水印与挑战错误治理 2026-02-19 20:04:10 +08:00
yangjianbo
440b87094a fix(sora): 增强 Cloudflare 挑战识别并收敛 Sora 请求链路
- 在 failover 场景透传上游响应头并识别 Cloudflare challenge/cf-ray

- 统一 Sora 任务请求的 UA 与代理使用,sentinel 与业务请求保持一致

- 修复流式错误事件 JSON 转义问题并补充相关单元测试
2026-02-19 15:09:58 +08:00
yangjianbo
0832dfb32e fix(sora): 默认开启 TLS 指纹并支持显式关闭 2026-02-19 08:30:54 +08:00
yangjianbo
be09188bda feat(account-test): 增强 Sora 账号测试能力探测与弹窗交互
- 后端新增 Sora2 邀请码与剩余额度探测,并补充对应结果解析
- Sora 测试流程补齐请求头与 Cloudflare 场景提示,完善单测覆盖
- 前端测试弹窗对 Sora 账号改为免选模型流程,并新增中英文提示文案

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:29:51 +08:00
yangjianbo
5d2219d299 fix(sora): 修复令牌刷新请求格式与流式错误转义
- 将 refresh_token 恢复请求改为表单编码并匹配 OAuth 约定
- 流式错误改为 JSON 序列化,避免消息含引号或换行导致 SSE 非法
- 补充 Sora token 恢复与 failover 流式错误透传回归测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:23:00 +08:00
yangjianbo
900cce20a1 feat(sora): 对齐 Sora OAuth 流程并隔离网关请求路径
- 新增并接通 Sora 专用 OAuth 接口与 ST/RT 换取能力
- 完成前端 Sora 授权、RT/ST 手动导入与账号创建流程
- 强化 Sora token 恢复、转发日志与网关路由隔离行为
- 补充后端服务层与路由层相关测试覆盖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:02:56 +08:00
yangjianbo
36bb327024 fix: 更新 ListWithFilters 方法以支持 groupID 参数 2026-02-18 20:52:35 +08:00
yangjianbo
5d9667d27a Merge branch 'main' into test
# Conflicts:
#	backend/cmd/server/VERSION
#	backend/ent/migrate/schema.go
#	backend/ent/mutation.go
#	backend/ent/runtime/runtime.go
#	backend/ent/usagelog.go
#	backend/ent/usagelog/usagelog.go
#	backend/ent/usagelog/where.go
#	backend/ent/usagelog_create.go
#	backend/ent/usagelog_update.go
#	backend/internal/repository/usage_log_repo.go
#	backend/internal/server/api_contract_test.go
#	backend/internal/server/middleware/cors.go
#	backend/internal/service/gateway_service.go
2026-02-18 20:16:31 +08:00
yangjianbo
fad04ca995 Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-18 20:10:32 +08:00
yangjianbo
6577f2ef03 fix(gateway): 避免SSE delta将缓存创建明细重置为0
- 仅在 delta 中 5m/1h 值大于0时覆盖 usage 明细
- 新增回归测试覆盖 delta 默认 0 不应覆盖 message_start 非零值
- 迁移 054 在删除 legacy 字段前追加一次回填,避免升级实例丢失历史写入

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 13:23:12 +08:00
yangjianbo
41d0383fb7 merge(test): 合并 main 并解决前端筛选器冲突 2026-02-15 22:04:06 +08:00
程序猿MT
1cf51b14f7 Merge branch 'Wei-Shaw:main' into main 2026-02-15 20:49:14 +08:00
yangjianbo
372e04f69a fix(docker): 默认从cmd/server/VERSION读取版本号 2026-02-14 23:28:33 +08:00
yangjianbo
e2107ce45e fix(build): Docker 构建注入版本号并同步 aicodex 镜像脚本 2026-02-14 21:16:21 +08:00
yangjianbo
5feff6b1e5 feat: 0.1.74.9 2026-02-14 13:23:26 +08:00
yangjianbo
06b0f62e79 feat(accounts): 自动刷新改为ETag增量同步并优化单账号更新体验
- 前端自动刷新改为 ETag/304 增量合并,减少全量重刷

- 单账号更新后增加静默窗口,避免刚更新即被自动刷新覆盖

- 列表筛选移除时改为待同步提示,不再立即触发全量补页

- 后端账号列表支持 If-None-Match,命中返回 304

- 单账号接口统一补充运行时容量字段并暴露 ETag 头
2026-02-14 13:22:51 +08:00
yangjianbo
40d110efe4 chore(logging): 删除过时的日志审计文档 2026-02-14 12:36:42 +08:00
yangjianbo
f23318fbcf fix(frontend): 同步账号本地移除后的分页状态 2026-02-14 12:35:35 +08:00
yangjianbo
cbab49d65f feat: 0.1.74.8 2026-02-14 12:10:20 +08:00
yangjianbo
b5a3b3db66 Merge branch 'test' into release 2026-02-14 12:07:19 +08:00
yangjianbo
9cafa46dd3 fix(accounts): 账号管理改为单行增量更新并避免全量刷新
- 将编辑与重新授权成功事件改为回传更新后的账号对象
- 在账号列表页按 id 就地补丁更新单行数据并保留运行时容量字段
- 单账号操作(刷新凭证/清错/清限流/临时不可调度重置)改为单行更新
- 后端增强 clear-rate-limit 接口,返回更新后的账号对象
- 同步前端 clearRateLimit API 类型定义

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 12:06:17 +08:00
yangjianbo
f6bff97d26 fix(frontend): 修复前端审计问题并补充回归测试 2026-02-14 11:56:08 +08:00
yangjianbo
d04b47b3ca feat(backend): 提交后端审计修复与配套测试改动 2026-02-14 11:23:10 +08:00
yangjianbo
862199143e fix(ops): 修复错误日志查询 q 过滤列名二义性 2026-02-14 11:21:30 +08:00
yangjianbo
57e8abcb63 fix(openai): 自动透传预检 instructions 并本地 403 拦截 2026-02-14 10:49:01 +08:00
yangjianbo
ed31c54961 fix(openai): 拒绝日志记录原始 User-Agent 便于攻击研判 2026-02-14 09:59:19 +08:00
yangjianbo
4bfa69bffa fix(openai): 仅记录 codex_cli_only 拒绝日志并输出详细 User-Agent 2026-02-14 09:53:17 +08:00
yangjianbo
888f2936ad feat: version 0.1.74.7 2026-02-13 19:28:12 +08:00
yangjianbo
4e894bac1f Merge branch 'test' into release 2026-02-13 19:27:35 +08:00
yangjianbo
f96acf6e27 fix(ops): 修复日志级别过滤并增强OpenAI错误诊断日志
- 移除 warn 级别下 access info 的强制入库补写,确保运行时日志级别真实生效

- 将 OpenAI fallback matched 与 passthrough 断流提示按需求降级为 info

- 为 codex_cli_only 与 instructions required 场景补充请求诊断字段(含 User-Agent)

- 出于安全考虑移除请求体预览,仅保留 request_body_size 与白名单头信息

- 新增/更新回归测试,覆盖 Forward 入口到日志落库链路
2026-02-13 19:27:07 +08:00
yangjianbo
2459eafb71 feat: 完善日志 2026-02-13 13:35:47 +08:00
yangjianbo
ed681d0830 feat: 整理 2026-02-13 12:49:08 +08:00
yangjianbo
3734abed4c feat(openai): 支持 gpt-5.3-codex-spark 并统一 gpt-5.3 到 codex 计费 2026-02-13 09:28:07 +08:00
yangjianbo
abf5de69fb Merge branch 'main' into test 2026-02-12 23:43:47 +08:00
yangjianbo
7582dc53d2 fix(openai): 修复关闭 codex_cli_only 无法持久化问题
在编辑 OpenAI OAuth 账号时,若 codex_cli_only 从开启切换为关闭,
现改为显式写入 false,避免 extra 为空时后端忽略更新导致旧值残留。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:15:41 +08:00
程序猿MT
174d7c774d Merge branch 'Wei-Shaw:main' into main 2026-02-12 23:12:41 +08:00
yangjianbo
a9518cc5be feat(openai): 增加 OAuth 账号 Codex 官方客户端限制开关
新增 codex_cli_only 开关并默认关闭,关闭时完全绕过限制逻辑。
在 OpenAI 网关引入统一检测入口,集中判定账号类型、开关与客户端族。
开启后仅放行 codex_cli_rs、codex_vscode、codex_app 客户端家族。
补充后端判定与网关分支测试,并在前端创建/编辑页增加开关配置与回显。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 22:32:59 +08:00
yangjianbo
2f190d812a fix(openai): 透传OAuth强制store/stream并修复Codex识别 2026-02-12 21:02:52 +08:00
yangjianbo
d411cf4472 fix(openai): 收敛自动透传请求头并增强 OAuth 安全兜底 2026-02-12 20:12:15 +08:00
yangjianbo
1ae49b9ead feat: version 0.1.74.5 2026-02-12 19:32:13 +08:00
yangjianbo
0bf162f64a Merge branch 'dev' into release 2026-02-12 19:23:54 +08:00
yangjianbo
6423636177 Merge branch 'test' into dev 2026-02-12 19:23:35 +08:00
yangjianbo
b6aaee01ce fix(logging): 修复 warn 级别下系统日志空白问题
- 新增 logger.WriteSinkEvent,支持旁路写入 sink,不受全局级别门控影响\n- 在 http.access 中间件中,当 info 被门控时补写 sink,保障 Ops 系统日志可索引\n- 增加 level=warn 场景回归测试,验证访问日志仍可入库
2026-02-12 19:19:11 +08:00
yangjianbo
3511376c2c chore(logging): 默认使用 console 普通日志输出
- 将配置默认 log.format 从 json 调整为 console\n- 将 logger 初始化兜底默认格式调整为 console\n- 同步更新 deploy 配置示例
2026-02-12 19:07:16 +08:00
yangjianbo
584cfc3db2 chore(logging): 完成后端日志审计与结构化迁移
- 将高密度服务与处理器日志迁移到新日志系统(LegacyPrintf/结构化日志)
- 增加 stdlog bridge 与兼容测试,保留旧日志捕获能力
- 将 OpenAI 断流告警改为结构化 Warn 并改造对应测试为 sink 捕获
- 补齐后端相关文件 logger 引用并通过全量 go test
2026-02-12 19:01:09 +08:00
yangjianbo
eaa7d899f0 fix(ops): 优化系统日志展示为可读文本
解析 extra 字段(status_code/latency_ms/method/path 等)并拼成普通文本\n表格改为 3 列并固定时间/级别宽度,详情列填满后自动换行

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:00:16 +08:00
yangjianbo
84cc651b46 fix(logger): 修复 caller 字段与 OpsSystemLogSink 停止刷盘
修复点:

- zap logger 不再强制 AddCallerSkip(1),确保 caller 指向真实调用点

- slog handler 避免重复写 time 字段

- OpsSystemLogSink 优先从字段 component 识别业务组件;停止时 drain 队列并用可用 ctx 刷盘

补充:新增/完善对应单测
2026-02-12 17:42:29 +08:00
yangjianbo
b7243660c4 fix(deploy): 修复 Postgres 数据未持久化导致重启后无法登录
原因:postgres:18-alpine 默认 PGDATA 不在 /var/lib/postgresql/data,数据落到匿名卷,docker compose down/up 会触发 initdb 重新初始化。

修复:在 compose 中显式设置 PGDATA=/var/lib/postgresql/data,让数据落到 postgres_data 命名卷。
2026-02-12 17:42:18 +08:00
yangjianbo
e722992439 fix(setup): 数据库有用户时跳过管理员引导 2026-02-12 16:50:42 +08:00
yangjianbo
fff1d54858 feat(log): 落地统一日志底座与系统日志运维能力 2026-02-12 16:27:29 +08:00
yangjianbo
a5f29019d9 test(ops): 提升日志链路覆盖率并修复lint阻塞 2026-02-12 16:25:44 +08:00
yangjianbo
208c5380f4 fix(ops): 排除刷新信号避免分页重置页码 2026-02-12 15:00:22 +08:00
yangjianbo
29191af877 Merge branch 'dev' into release 2026-02-12 14:40:37 +08:00
yangjianbo
2d6066f985 Merge branch 'test' into dev 2026-02-12 14:40:22 +08:00
yangjianbo
3ea5e5c33a feat: update build aicodex.sh 2026-02-12 14:40:05 +08:00
yangjianbo
dbd7969a3e Merge branch 'test' into release 2026-02-12 14:27:58 +08:00
yangjianbo
af3069073a chore(lint): 修复 golangci-lint unused
- 移除 OpenAIGatewayHandler 未使用字段

- 删除并发缓存中未使用的 Redis 脚本常量

- 将仅供 unit 测试使用的 parseIntegralNumber 移入 unit build tag 文件
2026-02-12 14:20:56 +08:00
yangjianbo
65661f24e2 feat(ops): 运维监控新增 OpenAI Token 请求统计表
- 新增管理端接口 /api/v1/admin/ops/dashboard/openai-token-stats,按模型聚合统计 gpt% 请求

- 支持 time_range=30m|1h|1d|15d|30d(默认 30d),支持 platform/group_id 过滤

- 支持分页(page/page_size)或 TopN(top_n)互斥查询

- 前端运维监控页新增统计表卡片,包含空态/错误态与分页/TopN 交互

- 补齐后端与前端测试
2026-02-12 14:20:14 +08:00
yangjianbo
ed2eba9028 fix(gateway): 默认过滤OpenAI透传超时头并补充断流告警 2026-02-12 14:16:18 +08:00
yangjianbo
10c1590b1d Merge branch 'dev' into release 2026-02-12 12:12:40 +08:00
yangjianbo
114e172603 test(repository): 补充 JWT 密钥引导并发与兼容性单测 2026-02-12 12:07:20 +08:00
yangjianbo
09c8380b3d fix(repository): 修复 JWT 密钥引导冲突一致性与并发读取竞态 2026-02-12 12:04:13 +08:00
yangjianbo
ba567babf4 :erge branch 'dev' into release 2026-02-12 11:45:11 +08:00
yangjianbo
9403aa9bd1 feat: version 0.1.74.4 2026-02-12 11:44:45 +08:00
yangjianbo
34b8bbcbe4 Merge branch 'dev' into release 2026-02-12 11:43:47 +08:00
yangjianbo
6b36992d34 feat(security): 启动时自动迁移并持久化JWT密钥
- 新增 security_secrets 表及 Ent schema 用于存储系统级密钥
- 启动阶段支持无 jwt.secret 配置并在数据库中自动生成持久化
- 在 Ent 初始化后补齐密钥并执行完整配置校验
- 增加并发与异常分支单元测试,覆盖密钥引导核心路径

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:41:20 +08:00
yangjianbo
6533a4647d fix(openai): 增强自动透传命中日志 2026-02-12 11:41:06 +08:00
yangjianbo
9c910c2049 feat(openai): 支持自动透传开关并透传 User-Agent
- OpenAI OAuth/API Key 统一支持自动透传开关,编辑页可开关\n- 透传模式仅替换认证并保留计费/并发/审计,修复 API Key responses 端点拼接\n- Usage 页面显示原始 User-Agent 且不截断,补充回归测试与清单
2026-02-12 10:56:07 +08:00
yangjianbo
43dc23a47d Merge branch 'test' into release 2026-02-12 09:49:05 +08:00
yangjianbo
61a2bf469a feat(openai): 极致优化 OAuth 链路并补齐性能守护
- 优化 /v1/responses 热路径,减少重复解析与不必要拷贝\n- 优化并发与 token 竞争路径并补齐运行指标\n- 补充 OpenAI/Ops 相关单元测试与回归用例\n- 新增灰度阈值守护与压测脚本,支撑发布验收
2026-02-12 09:41:37 +08:00
yangjianbo
a88bb8684f fix(openai): 修复 OAuth 透传流式断开与压缩头问题
- 透传流式在客户端断开后继续 drain 上游并解析 usage,避免计费信息丢失

- 阻断透传 accept-encoding,避免压缩响应影响 SSE/usage 解析

- 阻断 proxy-authorization,避免透传代理鉴权信息

- 补充回归测试:请求头阻断与断流后 usage 采集
2026-02-11 22:17:38 +08:00
程序猿MT
8da5fac69e Merge branch 'Wei-Shaw:main' into main 2026-02-11 18:39:52 +08:00
yangjianbo
e2cdb6c758 feat: 优化build image 2026-02-11 18:07:50 +08:00
yangjianbo
f1e884ce2b feat(openai): 增加 OAuth 透传开关
- 仅对 Codex CLI 且账号开启时走原样透传(只替换认证)

- 透传模式禁用工具修正/模型替换,并旁路解析 usage 用于计费

- 管理后台增加开关与文案,ops upstream error 记录 passthrough 标记

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 00:59:39 +08:00
yangjianbo
86f3124720 perf(service): 优化重试场景 thinking 过滤性能
- 避免全量 Unmarshal 请求体,改为仅解析 messages 子树

- 顶层 thinking 使用 sjson 直接删除,减少整体重写

- content 仅在需要修改时延迟分配 new slice

- 增加 FilterThinkingBlocksForRetry 基准测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 00:47:26 +08:00
yangjianbo
4b309fa8b5 fix(gateway): 优化 ParseGatewayRequest 函数,使用 unsafe 提高性能并增加 JSON 校验 2026-02-10 22:12:24 +08:00
yangjianbo
166080b29c chore: 更新版本号至 0.1.74.3 2026-02-10 18:02:02 +08:00
yangjianbo
3b0910f664 Merge branch 'main' into test-sora 2026-02-10 18:01:17 +08:00
yangjianbo
e489996713 test(backend): 补充改动代码单元测试覆盖率至 85%+
新增 48 个测试用例覆盖修复代码的各分支路径:
- subscription_maintenance_queue: nil receiver/task、Stop 幂等、零值参数 (+6)
- billing_service: CalculateCostWithConfig、错误传播、SoraImageCost 等 (+12)
- timing_wheel_service: Schedule/ScheduleRecurring after Stop (+3)
- sora_media_cleanup_service: nil guard、Start/Stop 各分支、timezone (+10)
- sora_gateway_service: normalizeSoraMediaURLs、buildSoraContent 等辅助函数 (+17)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 17:52:10 +08:00
yangjianbo
54fe363257 fix(backend): 修复代码审核发现的 8 个确认问题
- P0-1: subscription_maintenance_queue 使用 RWMutex 防止 channel close/send 竞态
- P0-2: billing_service CalculateCostWithLongContext 修复被吞没的 out-range 错误
- P1-1: timing_wheel_service Schedule/ScheduleRecurring 添加 SetTimer 错误日志
- P1-2: sora_gateway_service StoreFromURLs 失败时降级使用原始 URL
- P1-3: concurrency_cache 用 Pipeline 替代 Lua 脚本兼容 Redis Cluster
- P1-6: sora_media_cleanup_service runCleanup 添加 nil cfg/storage 防护

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 17:51:49 +08:00
程序猿MT
1dd3158c7e Merge branch 'Wei-Shaw:main' into main 2026-02-10 13:55:51 +08:00
yangjianbo
5d1c51a37f fix(handler): 修复 gjson 迁移后的请求校验语义回退
- OpenAI handler: 添加 gjson.ValidBytes 校验 JSON 合法性;model 校验改为
  检查 gjson.String 类型而非仅判断非空(拒绝 model:123 等非法类型);stream
  字段添加 True/False 类型检查;sjson.SetBytes 返回值显式处理错误
- Sora handler: 添加 gjson.ValidBytes 校验;model 校验同上改为类型检查;
  messages 校验从 Exists+Type==JSON 改为 IsArray+len>0(拒绝空数组和对象)
- 补充 TestOpenAIHandler_GjsonValidation 和更新 TestSoraHandler_ValidationExtraction
  覆盖新增的边界校验场景

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:13:20 +08:00
yangjianbo
58912d4ac5 perf(backend): 使用 gjson/sjson 优化热路径 JSON 处理
将 API 网关热路径中的 json.Unmarshal+json.Marshal 替换为 gjson 零拷贝查询和 sjson 精准写入:
- unwrapV1InternalResponse 性能提升 22x(4009ns→182ns),内存分配减少 28.5x
- unwrapGeminiResponse、extractGeminiUsage、estimateGeminiCountTokens、ParseGeminiRateLimitResetTime 改为接收 []byte 使用 gjson 提取
- ParseGatewayRequest 的 model/stream/metadata/thinking/max_tokens 改用 gjson 类型安全提取
- Handler 层(sora/openai)改用 gjson 提取字段、sjson 注入/修改字段,移除 map[string]any 中间变量
- Sora Client 响应解析改用 gjson ForEach 遍历,减少内存分配
- 新增约 100 个单元测试用例,所有改动函数覆盖率 >85%

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 08:59:30 +08:00
yangjianbo
29ca1290b3 chore(test): 清理测试用例与类型导入 2026-02-10 00:37:56 +08:00
yangjianbo
3fcb0cc37c feat(subscription): 有界队列执行维护并改进鉴权解析 2026-02-10 00:37:47 +08:00
yangjianbo
2bfb16291f fix(unit): 修复 unit tag 测试编译与账号选择用例 2026-02-09 21:35:41 +08:00
yangjianbo
d367d1cde6 Merge branch 'main' into test-sora 2026-02-09 20:40:09 +08:00
yangjianbo
3c46f7d266 fix: update .gitignore to include frontend coverage directory 2026-02-09 20:26:46 +08:00
yangjianbo
16131c3d3f Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-09 20:26:03 +08:00
yangjianbo
d7011163b8 fix: 修复代码审核发现的安全和质量问题
安全修复(P0):
- 移除硬编码的 OAuth client_secret(Antigravity、Gemini CLI),
  改为通过环境变量注入(ANTIGRAVITY_OAUTH_CLIENT_SECRET、
  GEMINI_CLI_OAUTH_CLIENT_SECRET)
- 新增 logredact.RedactText() 对非结构化文本做敏感信息脱敏,
  覆盖 GOCSPX-*/AIza* 令牌和常见 key=value 模式
- 日志中不再打印 org_uuid、account_uuid、email_address 等敏感值

安全修复(P1):
- URL 验证增强:新增 ValidateHTTPURL 统一入口,支持 allowlist 和
  私网地址阻断(localhost/内网 IP)
- 代理回退安全:代理初始化失败时默认阻止直连回退,防止 IP 泄露,
  可通过 security.proxy_fallback.allow_direct_on_error 显式开启
- Gemini OAuth 配置校验:client_id 与 client_secret 必须同时
  设置或同时留空

其他改进:
- 新增 tools/secret_scan.py 密钥扫描工具和 Makefile secret-scan 目标
- 更新所有 docker-compose 和部署配置,传递 OAuth secret 环境变量
- google_one OAuth 类型使用固定 redirectURI,与 code_assist 对齐

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:58:13 +08:00
yangjianbo
fc8a39e0f5 test: 删除CI工作流,大幅提升后端单元测试覆盖率至50%+
删除因GitHub计费锁定而失败的CI工作流。
为6个核心Go源文件补充单元测试,全部达到50%以上覆盖率:
- response/response.go: 97.6%
- antigravity/oauth.go: 90.1%
- antigravity/client.go: 88.6% (新增27个HTTP客户端测试)
- geminicli/oauth.go: 91.8%
- service/oauth_service.go: 61.2%
- service/gemini_oauth_service.go: 51.9%

新增/增强8个测试文件,共计5600+行测试代码。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:07:58 +08:00
yangjianbo
9da80e9fda feat: update 2026-02-08 12:13:29 +08:00
yangjianbo
bb5a5dd65e test: 完善自动化测试体系(7个模块,73个任务)
系统性地修复、补充和强化项目的自动化测试能力:

1. 测试基础设施修复
   - 修复 stubConcurrencyCache 缺失方法和构造函数参数不匹配
   - 创建 testutil 共享包(stubs.go, fixtures.go, httptest.go)
   - 为所有 Stub 添加编译期接口断言

2. 中间件测试补充
   - 新增 JWT 认证中间件测试(有效/过期/篡改/缺失 Token)
   - 补充 rate_limiter 和 recovery 中间件测试场景

3. 网关核心路径测试
   - 新增账户选择、等待队列、流式响应、并发控制、计费、Claude Code 检测测试
   - 覆盖负载均衡、粘性会话、SSE 转发、槽位管理等关键逻辑

4. 前端测试体系(11个新测试文件,163个测试用例)
   - Pinia stores: auth, app, subscriptions
   - API client: 请求拦截器、响应拦截器、401 刷新
   - Router guards: 认证重定向、管理员权限、简易模式限制
   - Composables: useForm, useTableLoader, useClipboard
   - Components: LoginForm, ApiKeyCreate, Dashboard

5. CI/CD 流水线重构
   - 重构 backend-ci.yml 为统一的 ci.yml
   - 前后端 4 个并行 Job + Postgres/Redis services
   - Race 检测、覆盖率收集与门禁、Docker 构建验证

6. E2E 自动化测试
   - e2e-test.sh 自动化脚本(Docker 启动→健康检查→测试→清理)
   - 用户注册→登录→API Key→网关调用完整链路测试
   - Mock 模式和 API Key 脱敏支持

7. 修复预存问题
   - tlsfingerprint dialer_test.go 缺失 build tag 导致集成测试编译冲突

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 12:05:39 +08:00
yangjianbo
53e1c8b268 perf(日志): 降噪优化,将常规成功日志降级为 Debug 级别
- GIN Logger 中间件跳过 /health 和 /setup/status 的请求日志
- UsageCleanup 空闲轮询(no_task)日志降级为 slog.Debug
- Scheduler 常规 rebuild ok 日志降级为 slog.Debug
- DashboardAggregation 常规聚合完成日志降级为 slog.Debug
- TokenRefresh 无刷新活动时周期日志降级为 slog.Debug

生产环境(Info 级别)下自动静默,debug 模式下仍可见。
错误、警告类日志保持原有级别不变。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:29:24 +08:00
yangjianbo
d876686a00 feat: update skills 2026-02-07 22:25:57 +08:00
yangjianbo
7546a56736 feat: update skills 2026-02-07 22:21:39 +08:00
yangjianbo
00caf0bcd8 test: 为代码审核修复添加详细单元测试(7个测试文件,50+测试用例)
新增测试文件:
- cors_test.go: CORS 条件化头部测试(12个测试,覆盖白名单/黑名单/通配符/凭证/多源/Vary)
- gateway_helper_backoff_test.go: nextBackoff 退避测试(6个测试+基准,验证指数增长/边界/抖动/收敛)
- billing_cache_jitter_test.go: jitteredTTL 抖动测试(5个测试+基准,验证范围/上界/方差/均值)
- subscription_calculate_progress_test.go: calculateProgress 纯函数测试(9个测试,覆盖日/周/月限额/超限截断/过期)
- openai_gateway_handler_test.go: SSE JSON 转义测试(7个子用例,验证双引号/反斜杠/换行符安全)

更新测试文件:
- response_transformer_test.go: 增强 generateRandomID 测试(7个测试,含并发/字符集/降级计数器)
- security_headers_test.go: 适配 GenerateNonce 新签名
- api_key_auth_test.go: 适配 NewSubscriptionService 新参数

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 22:14:07 +08:00
yangjianbo
9634494ba9 fix: 修复代码审核发现的10个问题(P0安全+P1数据一致性+P2性能优化)
P0: OpenAI SSE 错误消息 JSON 注入 — 使用 json.Marshal 替代 fmt.Sprintf
P1: subscription 续期包裹 Ent 事务确保原子性
P1: CSP nonce 生成处理 crypto/rand 错误,失败降级为 unsafe-inline
P1: singleflight 透传数据库真实错误,不再吞没为 not found
P1: GetUserSubscriptionsWithProgress 提取 calculateProgress 消除 N+1
P2: billing_cache/gateway_helper 迁移到 math/rand/v2 消除全局锁争用
P2: generateRandomID 降级分支增加原子计数器防碰撞
P2: CORS 非白名单 origin 不再设置 Allow-Headers/Methods/Max-Age
P2: Turnstile 验证移除 VerifyCode 空值跳过条件防绕过
P2: Redis Cluster Lua 脚本空 KEYS 添加兼容性警告注释

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 22:13:45 +08:00
yangjianbo
e1ac0db05c feat: 优化skills 2026-02-07 21:57:29 +08:00
yangjianbo
6f3e77a2df feat: update skills 2026-02-07 21:32:42 +08:00
yangjianbo
4a20a2a8ba fix: 修复批量更新凭证明细与缓存TTL抖动
- BatchUpdateCredentials 返回 success/failed/results 及 success_ids/failed_ids

- billing jitteredTTL 改为只减不增,确保TTL不超上界

- crypto/rand 失败时随机ID降级避免 panic

- OpenAI SelectAccount 失败日志去重并补充字段

- 修复两处类型断言以通过 errcheck
2026-02-07 21:18:03 +08:00
yangjianbo
bc3ca5f068 chore: 更新版本号至 0.1.74.1 2026-02-07 20:39:06 +08:00
yangjianbo
fd43be8d0b merge: 合并 main 分支到 test,解决 config 和 modelWhitelist 冲突
- config.go: 保留 Sora 配置,合入 SubscriptionCache 配置
- useModelWhitelist.ts: 同时保留 soraModels 和 antigravityModels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:18:07 +08:00
yangjianbo
836ba14b70 fix: 修复函数签名变更后的调用参数不匹配
- handleUpstreamError 补齐新增的三个参数 (0, "", false)
- handleStreamingResponse 移除已删除的 nil 参数

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:05:29 +08:00
yangjianbo
a14dfb769a Merge branch 'dev-release' 2026-02-07 19:58:00 +08:00
yangjianbo
2588fa6a8f fix(audit): 第二批审计修复 — P0 生产 Bug、安全加固、性能优化、缓存一致性、代码质量
基于 backend-code-audit 审计报告,修复剩余 P0/P1/P2 共 34 项问题:

P0 生产 Bug:
- 修复 time.Since(time.Now()) 计时逻辑错误 (P0-03)
- generateRandomID 改用 crypto/rand 替代固定索引 (P0-04)
- IncrementQuotaUsed 重写为 Ent 原子操作消除 TOCTOU 竞态 (P0-05)

安全加固:
- gateway/openai handler 错误响应替换为泛化消息,防止内部信息泄露 (P1-14)
- usage_log_repo dateFormat 参数改用白名单映射,防止 SQL 注入 (P1-16)
- 默认配置安全加固:sslmode=prefer、response_headers=true、mode=release (P1-18/19, P2-15)

性能优化:
- gateway handler 循环内 defer 替换为显式 releaseWait 闭包 (P1-02)
- group_repo/promo_code_repo Count 前 Clone 查询避免状态污染 (P1-03)
- usage_log_repo 四个查询添加 LIMIT 10000 防止 OOM (P1-07)
- GetBatchUsageStats 添加时间范围参数,默认最近 30 天 (P1-10)
- ip.go CIDR 预编译为包级变量 (P1-11)
- BatchUpdateCredentials 重构为先验证后更新 (P1-13)

缓存一致性:
- billing_cache 添加 jitteredTTL 防止缓存雪崩 (P2-10)
- DeductUserBalance/UpdateSubscriptionUsage 错误传播修复 (P2-12)
- UserService.UpdateBalance 成功后异步失效 billingCache (P2-13)

代码质量:
- search 截断改为按 rune 处理,支持多字节字符 (P2-01)
- TLS Handshake 改为 HandshakeContext 支持 context 取消 (P2-07)
- CORS 预检添加 Access-Control-Max-Age: 86400 (P2-16)

测试覆盖:
- 新增 user_service_test.go(UpdateBalance 缓存失效 6 个用例)
- 新增 batch_update_credentials_test.go(fail-fast + 类型验证 7 个用例)
- 新增 response_transformer_test.go、ip_test.go、usage_log_repo_unit_test.go、search_truncate_test.go
- 集成测试:IncrementQuotaUsed 并发测试、billing_cache 错误传播测试
- config_test.go 补充 server.mode/sslmode 默认值断言

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 19:46:42 +08:00
yangjianbo
f6ca701917 fix(oauth): SessionStore.Stop() 添加 sync.Once 防重入保护 (P1-05)
oauth 和 openai 包的 SessionStore.Stop() 直接调用 close(stopCh),
重复调用会导致 panic。使用 sync.Once 包裹确保幂等安全。

新增单元测试覆盖连续调用和 50 goroutine 并发调用场景。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:39:18 +08:00
yangjianbo
a84604dceb fix(config): 禁止 server.frontend_url 携带 query/userinfo 2026-02-07 17:37:08 +08:00
yangjianbo
e75d3e3584 fix(security): 修复密码重置链接 Host Header 注入漏洞 (P0-07)
ForgotPassword 原来从 c.Request.Host 构建重置链接基础 URL,攻击者
可伪造 Host 头将重置链接指向恶意域名窃取 token。

修复方案:
- ServerConfig 新增 frontend_url 配置项
- auth_handler 改为从配置读取前端 URL,未配置时拒绝请求
- Validate() 校验 frontend_url 必须为绝对 HTTP(S) URL
- 新增 TestValidateServerFrontendURL 单元测试
- config.example.yaml 添加配置说明

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:15:26 +08:00
yangjianbo
8226a4ce4d perf(service): 优化 model 替换函数,用 gjson/sjson 替代全量 JSON 序列化
SSE 热路径中 replaceModelInSSELine 和 replaceModelInResponseBody 原来
使用 json.Unmarshal/Marshal 对每个事件做全量反序列化再序列化,现改为
gjson.Get/sjson.Set 精确字段操作,消除 O(n) 中间 map 分配,保持 JSON
字段顺序不变。涉及 OpenAIGatewayService 和 GatewayService 两个服务。

新增 23 个单元测试覆盖:顶层/嵌套 model 替换、不匹配跳过、空行/[DONE]/
非法 JSON 等边界情况。

Fixes: P1-08

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:09:55 +08:00
yangjianbo
65c0d8b51f fix(middleware): 管理员JWT增加TokenVersion校验
管理员改密后旧JWT会被拒绝,并补充单元测试覆盖。
2026-02-07 16:34:57 +08:00
yangjianbo
a9e256ce8c fix(openai): 修复 usage 为空导致 panic(P0-02) 2026-02-07 16:15:30 +08:00
yangjianbo
7e1674e43a chore(version): 更新版本号至 0.1.70.2 2026-02-07 14:58:52 +08:00
yangjianbo
fc104dfb56 feat:增加端口 2026-02-07 14:57:50 +08:00
yangjianbo
0e514ed80b perf(middleware): 优化订阅模式认证中间件,5次串行调用降至2步同步+1步异步
- 为 GetActiveSubscription 添加 ristretto L1 缓存 + singleflight 防击穿
- 合并 ValidateSubscription + CheckUsageLimits 为纯内存 ValidateAndCheckLimits
- 窗口维护操作(激活/重置)异步化,不再阻塞首字节
- 缓存返回浅拷贝,避免并发 data race 和缓存污染
- 所有管理操作(分配/续期/撤销/扩展/窗口重置)同步失效 L1 缓存
- 新增 SubscriptionCacheConfig 可配置 L1 缓存大小/TTL/抖动

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:43:12 +08:00
yangjianbo
782a54a8a1 chore(version): 更新版本号至 0.1.70.1 2026-02-07 11:17:46 +08:00
yangjianbo
4e01126ff2 test(codex): 清理无用的 opencode 缓存测试
移除不再需要的 setupCodexCache 调用与辅助函数(已不再回源/读写缓存)
2026-02-07 09:53:01 +08:00
yangjianbo
55b56328da feat(codex): 移除 opencode 指令回源与缓存
- 不再从 GitHub 拉取 opencode codex_header.txt\n- 删除 ~/.opencode 缓存与异步刷新逻辑\n- 所有 instructions 统一使用内置 codex_cli_instructions.md
2026-02-07 09:28:32 +08:00
yangjianbo
ce764bf2d9 feat(gateway): 支持强制 Codex CLI 模式并伪装 UA
- Codex CLI 请求仅使用内置 instructions,不再读取 opencode 缓存/回源\n- 新增 gateway.force_codex_cli(环境变量 GATEWAY_FORCE_CODEX_CLI)\n- ForceCodexCLI=true 时转发上游强制 User-Agent=codex_cli_rs/0.0.0\n- 更新 deploy 示例配置
2026-02-07 09:21:15 +08:00
yangjianbo
d71537d431 perf(service): SSE Scanner buffer 改用 sync.Pool 复用,减少高并发 GC 压力
将流式响应中 bufio.Scanner 的 64KB buffer 从每次 make 分配改为
sync.Pool 复用,统一切片表达式为 [:0]、变量命名为 scanBuf,
并补充对应的单元测试。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:55:12 +08:00
yangjianbo
ae1ba45350 perf(service): jitterTTL 改用 rand/v2 并移除锁 2026-02-06 21:22:38 +08:00
yangjianbo
c4182f8c33 perf(service): 移除 jitter 随机数全局锁 2026-02-06 21:20:25 +08:00
yangjianbo
028f8aaa97 feat: 优化.env参数 2026-02-06 21:01:30 +08:00
yangjianbo
d3f11fdbd3 chore(deploy): aicodex 默认 max_conns_per_host=8192 2026-02-06 20:50:44 +08:00
yangjianbo
8672b2f3ec chore(gateway): 提升 max_idle_conns 并补齐 env 2026-02-06 20:48:48 +08:00
yangjianbo
de753a149e chore(deploy): 补齐连接池默认与 8G 参数 2026-02-06 20:44:08 +08:00
yangjianbo
2d4bbbf49d feat: 优化codex冷启动, 还有连接池数据库配置信息 2026-02-06 20:31:42 +08:00
yangjianbo
792bef615c Merge branch 'main' into test 2026-02-06 09:59:15 +08:00
yangjianbo
000a943cce Merge branch 'main' into test 2026-02-06 08:43:42 +08:00
yangjianbo
f82e346f02 Merge branch 'main' into test 2026-02-06 08:12:40 +08:00
yangjianbo
d8e405511e Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-06 06:56:23 +08:00
yangjianbo
74d35f0860 chore(ent): 重新生成代码 2026-02-04 20:41:26 +08:00
yangjianbo
de7ff902de Merge branch 'main' into test 2026-02-04 20:35:09 +08:00
yangjianbo
317f26f0bf feat: update caddy 2026-02-04 19:27:51 +08:00
程序猿MT
dd96ada3c6 Merge branch 'Wei-Shaw:main' into main 2026-02-04 18:56:47 +08:00
yangjianbo
9b120e68b8 fix(sora): 恢复流式辅助逻辑并通过 lint 2026-02-04 14:06:06 +08:00
yangjianbo
377bffe281 Merge branch 'main' into test 2026-02-03 22:48:04 +08:00
yangjianbo
31fe017888 Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-03 21:00:11 +08:00
yangjianbo
99250ec527 fix(Sora): 加固直连安全与下载限制
补充图片输入 SSRF 防护与重定向限制\n增加媒体下载超时/大小上限配置并更新示例\n完善 recent_tasks 轮询回退策略与相关测试\n\n测试: go test ./... -tags=unit
2026-02-01 22:10:15 +08:00
yangjianbo
dcf5f60237 feat: add codex skills 2026-02-01 21:38:00 +08:00
yangjianbo
399dd78b2a feat(Sora): 直连生成并移除sora2api依赖
实现直连 Sora 客户端、媒体落地与清理策略\n更新网关与前端配置以支持 Sora 平台\n补齐单元测试与契约测试,新增 curl 测试脚本\n\n测试: go test ./... -tags=unit
2026-02-01 21:37:10 +08:00
yangjianbo
78d0ca3775 fix(sora): 修复流式重写与计费问题 2026-01-31 21:46:28 +08:00
yangjianbo
618a614cbf feat(Sora): 完成Sora网关接入与媒体能力
新增 Sora 网关路由、账号调度与同步服务\n补充媒体代理与签名 URL、模型列表动态拉取\n完善计费配置、前端支持与相关测试
2026-01-31 20:22:22 +08:00
yangjianbo
99dc3b59bc feat(账号): 添加 Sora 账号双表同步与创建
- 新增 sora_accounts 表与 accounts.extra GIN 索引\n- OpenAI OAuth 支持同时创建 Sora 账号并同步配置\n- Token 刷新同步关联 Sora 账号凭证与扩展表\n- 增加 Sora 账号连通性测试与前端开关文案
2026-01-30 14:08:04 +08:00
yangjianbo
d9e345f23d Merge branch 'test' of https://github.com/mt21625457/aicodex2api into test 2026-01-29 20:34:21 +08:00
yangjianbo
a505d992ee feat: 优化配置 2026-01-29 20:33:26 +08:00
yangjianbo
13262a5698 feat(sora): 新增 Sora 平台支持并修复高危安全和性能问题
新增功能:
- 新增 Sora 账号管理和 OAuth 认证
- 新增 Sora 视频/图片生成 API 网关
- 新增 Sora 任务调度和缓存机制
- 新增 Sora 使用统计和计费支持
- 前端增加 Sora 平台配置界面

安全修复(代码审核):
- [SEC-001] 限制媒体下载响应体大小(图片 20MB、视频 200MB),防止 DoS 攻击
- [SEC-002] 限制 SDK API 响应大小(1MB),防止内存耗尽
- [SEC-003] 修复 SSRF 风险,添加 URL 验证并强制使用代理配置

BUG 修复(代码审核):
- [BUG-001] 修复 for 循环内 defer 累积导致的资源泄漏
- [BUG-002] 修复图片并发槽位获取失败时已持有锁未释放的永久泄漏

性能优化(代码审核):
- [PERF-001] 添加 Sentinel Token 缓存(3 分钟有效期),减少 PoW 计算开销

技术细节:
- 使用 io.LimitReader 限制所有外部输入的大小
- 添加 urlvalidator 验证防止 SSRF 攻击
- 使用 sync.Map 实现线程安全的包级缓存
- 优化并发槽位管理,添加 releaseAll 模式防止泄漏

影响范围:
- 后端:新增 Sora 相关数据模型、服务、网关和管理接口
- 前端:新增 Sora 平台配置、账号管理和监控界面
- 配置:新增 Sora 相关配置项和环境变量

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-29 16:18:38 +08:00
yangjianbo
bece1b5201 perf(服务端): 启用 h2c 并保留 HTTP/1.1 回退 2026-01-24 20:01:03 +08:00
1260 changed files with 272249 additions and 47017 deletions

View File

@@ -61,6 +61,9 @@ temp/
deploy/install.sh
deploy/sub2api.service
deploy/sub2api-sudoers
deploy/data/
deploy/postgres_data/
deploy/redis_data/
# GoReleaser
.goreleaser.yaml

7
.gitattributes vendored
View File

@@ -4,6 +4,13 @@ backend/migrations/*.sql text eol=lf
# Go 源代码文件
*.go text eol=lf
# 前端 源代码文件
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.vue text eol=lf
# Shell 脚本
*.sh text eol=lf

View File

@@ -5,12 +5,33 @@ 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-04-05"
expires_on: "2026-07-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-04-05"
expires_on: "2026-07-06"
owner: "security@your-domain"
- package: lodash
advisory: "GHSA-r5fr-rjxr-66jc"
severity: high
reason: "lodash _.template not used with untrusted input; only internal admin UI templates"
mitigation: "No user-controlled template strings; plan to migrate to lodash-es tree-shaken imports"
expires_on: "2026-07-02"
owner: "security@your-domain"
- package: lodash-es
advisory: "GHSA-r5fr-rjxr-66jc"
severity: high
reason: "lodash-es _.template not used with untrusted input; only internal admin UI templates"
mitigation: "No user-controlled template strings; plan to migrate to native JS alternatives"
expires_on: "2026-07-02"
owner: "security@your-domain"
- package: axios
advisory: "GHSA-3p68-rc4w-qgx5"
severity: critical
reason: "NO_PROXY bypass not exploitable; all API calls go to known endpoints via server-side proxy"
mitigation: "Proxy configuration not user-controlled; upgrade when axios releases fix"
expires_on: "2026-07-10"
owner: "security@your-domain"

View File

@@ -11,15 +11,16 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
check-latest: false
cache: true
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.25.7'
go version | grep -q 'go1.26.2'
- name: Unit tests
working-directory: backend
run: make test-unit
@@ -30,18 +31,19 @@ jobs:
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
check-latest: false
cache: true
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.25.7'
go version | grep -q 'go1.26.2'
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.7
args: --timeout=5m
working-directory: backend
version: v2.9
args: --timeout=30m
working-directory: backend

View File

@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Update VERSION file
run: |
@@ -45,7 +45,7 @@ jobs:
echo "Updated VERSION file to: $VERSION"
- name: Upload VERSION artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: version-file
path: backend/cmd/server/VERSION
@@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
@@ -63,7 +63,7 @@ jobs:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'pnpm'
@@ -78,7 +78,7 @@ jobs:
working-directory: frontend
- name: Upload frontend artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: frontend-dist
path: backend/internal/web/dist/
@@ -89,25 +89,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Download VERSION artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: version-file
path: backend/cmd/server/
- name: Download frontend artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: frontend-dist
path: backend/internal/web/dist/
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
check-latest: false
@@ -115,7 +115,7 @@ jobs:
- name: Verify Go version
run: |
go version | grep -q 'go1.25.7'
go version | grep -q 'go1.26.2'
# Docker setup for GoReleaser
- name: Set up QEMU
@@ -173,7 +173,7 @@ jobs:
run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }}
@@ -188,7 +188,7 @@ jobs:
# Update DockerHub description
- name: Update DockerHub description
if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }}
uses: peter-evans/dockerhub-description@v4
uses: peter-evans/dockerhub-description@v5
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
with:
@@ -271,3 +271,36 @@ jobs:
parse_mode: "Markdown",
disable_web_page_preview: true
}')"
sync-version-file:
needs: [release]
if: ${{ needs.release.result == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Checkout default branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.repository.default_branch }}
- name: Sync VERSION file to released tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION=${{ github.event.inputs.tag }}
VERSION=${VERSION#v}
else
VERSION=${GITHUB_REF#refs/tags/v}
fi
CURRENT_VERSION=$(tr -d '\r\n' < backend/cmd/server/VERSION || true)
if [ "$CURRENT_VERSION" = "$VERSION" ]; then
echo "VERSION file already matches $VERSION"
exit 0
fi
echo "$VERSION" > backend/cmd/server/VERSION
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add backend/cmd/server/VERSION
git commit -m "chore: sync VERSION to ${VERSION} [skip ci]"
git push origin HEAD:${{ github.event.repository.default_branch }}

View File

@@ -12,38 +12,34 @@ permissions:
jobs:
backend-security:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
check-latest: false
cache-dependency-path: backend/go.sum
- name: Verify Go version
run: |
go version | grep -q 'go1.25.7'
go version | grep -q 'go1.26.2'
- name: Run govulncheck
working-directory: backend
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Run gosec
working-directory: backend
run: |
go install github.com/securego/gosec/v2/cmd/gosec@latest
gosec -conf .gosec.json -severity high -confidence high ./...
frontend-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'pnpm'

15
.gitignore vendored
View File

@@ -116,17 +116,22 @@ backend/.installed
# ===================
tests
CLAUDE.md
AGENTS.md
.claude
scripts
.code-review-state
openspec/
docs/
#openspec/
code-reviews/
AGENTS.md
#AGENTS.md
backend/cmd/server/server
deploy/docker-compose.override.yml
.gocache/
vite.config.js
docs/*
.serena/
!docs/PAYMENT.md
!docs/PAYMENT_CN.md
.serena/
.codex/
frontend/coverage/
aicodex
output/

View File

@@ -47,6 +47,8 @@ dockers:
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
dockerfile: Dockerfile.goreleaser
use: buildx
extra_files:
- deploy/docker-entrypoint.sh
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{ .Version }}"

View File

@@ -63,6 +63,8 @@ dockers:
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
dockerfile: Dockerfile.goreleaser
use: buildx
extra_files:
- deploy/docker-entrypoint.sh
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{ .Version }}"
@@ -76,6 +78,8 @@ dockers:
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"
dockerfile: Dockerfile.goreleaser
use: buildx
extra_files:
- deploy/docker-entrypoint.sh
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.version={{ .Version }}"
@@ -89,6 +93,8 @@ dockers:
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
dockerfile: Dockerfile.goreleaser
use: buildx
extra_files:
- deploy/docker-entrypoint.sh
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{ .Version }}"
@@ -102,6 +108,8 @@ dockers:
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
dockerfile: Dockerfile.goreleaser
use: buildx
extra_files:
- deploy/docker-entrypoint.sh
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.version={{ .Version }}"

1
Antigravity-Manager Submodule

Submodule Antigravity-Manager added at a9d96bd549

View File

@@ -209,7 +209,30 @@ git add ent/ # 生成的文件也要提交
---
### 坑 10PR 提交前检查清单
### 坑 10前端测试看似正常,但后端调用失败(模型映射被批量误改)
**典型现象**
- 前端按钮点测看起来正常;
- 实际通过 API/客户端调用时返回 `Service temporarily unavailable` 或提示无可用账号;
- 常见于 OpenAI 账号(例如 Codex 模型)在批量修改后突然不可用。
**根因**
- OpenAI 账号编辑页默认不显式展示映射规则,容易让人误以为“没映射也没关系”;
- 但在**批量修改同时选中不同平台账号**OpenAI + Antigravity/Gemini模型白名单/映射可能被跨平台策略覆盖;
- 结果是 OpenAI 账号的关键模型映射丢失或被改坏,后端选不到可用账号。
**修复方案(按优先级)**
1. **快速修复(推荐)**:在批量修改中补回正确的透传映射(例如 `gpt-5.3-codex -> gpt-5.3-codex-spark`)。
2. **彻底重建**:删除并重新添加全部相关账号(最稳但成本高)。
**关键经验**
- 如果某模型已被软件内置默认映射覆盖,通常不需要额外再加透传;
- 但当上游模型更新快于本仓库默认映射时,**手动批量添加透传映射**是最简单、最低风险的临时兜底方案;
- 批量操作前尽量按平台分组,不要混选不同平台账号。
---
### 坑 11PR 提交前检查清单
提交 PR 前务必本地验证:

View File

@@ -7,8 +7,9 @@
# =============================================================================
ARG NODE_IMAGE=node:24-alpine
ARG GOLANG_IMAGE=golang:1.25.7-alpine
ARG ALPINE_IMAGE=alpine:3.20
ARG GOLANG_IMAGE=golang:1.26.2-alpine
ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine
ARG GOPROXY=https://goproxy.cn,direct
ARG GOSUMDB=sum.golang.google.cn
@@ -36,7 +37,7 @@ RUN pnpm run build
FROM ${GOLANG_IMAGE} AS backend-builder
# Build arguments for version info (set by CI)
ARG VERSION=docker
ARG VERSION=
ARG COMMIT=docker
ARG DATE
ARG GOPROXY
@@ -61,14 +62,24 @@ COPY backend/ ./
COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist
# Build the binary (BuildType=release for CI builds, embed frontend)
RUN CGO_ENABLED=0 GOOS=linux go build \
# Version precedence: build arg VERSION > cmd/server/VERSION
RUN VERSION_VALUE="${VERSION}" && \
if [ -z "${VERSION_VALUE}" ]; then VERSION_VALUE="$(tr -d '\r\n' < ./cmd/server/VERSION)"; fi && \
DATE_VALUE="${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
CGO_ENABLED=0 GOOS=linux go build \
-tags embed \
-ldflags="-s -w -X main.Commit=${COMMIT} -X main.Date=${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} -X main.BuildType=release" \
-ldflags="-s -w -X main.Version=${VERSION_VALUE} -X main.Commit=${COMMIT} -X main.Date=${DATE_VALUE} -X main.BuildType=release" \
-trimpath \
-o /app/sub2api \
./cmd/server
# -----------------------------------------------------------------------------
# Stage 3: Final Runtime Image
# Stage 3: PostgreSQL Client (version-matched with docker-compose)
# -----------------------------------------------------------------------------
FROM ${POSTGRES_IMAGE} AS pg-client
# -----------------------------------------------------------------------------
# Stage 4: Final Runtime Image
# -----------------------------------------------------------------------------
FROM ${ALPINE_IMAGE}
@@ -81,9 +92,21 @@ LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
RUN apk add --no-cache \
ca-certificates \
tzdata \
curl \
su-exec \
libpq \
zstd-libs \
lz4-libs \
krb5-libs \
libldap \
libedit \
&& rm -rf /var/cache/apk/*
# Copy pg_dump and psql from the same postgres image used in docker-compose
# This ensures version consistency between backup tools and the database server
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/
# Create non-root user
RUN addgroup -g 1000 sub2api && \
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
@@ -91,21 +114,24 @@ RUN addgroup -g 1000 sub2api && \
# Set working directory
WORKDIR /app
# Copy binary from builder
COPY --from=backend-builder /app/sub2api /app/sub2api
# Copy binary/resources with ownership to avoid extra full-layer chown copy
COPY --from=backend-builder --chown=sub2api:sub2api /app/sub2api /app/sub2api
COPY --from=backend-builder --chown=sub2api:sub2api /app/backend/resources /app/resources
# Create data directory
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
RUN mkdir -p /app/data && chown sub2api:sub2api /app/data
# Switch to non-root user
USER sub2api
# Copy entrypoint script (fixes volume permissions then drops to sub2api)
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# Expose port (can be overridden by SERVER_PORT env var)
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1
# Run the application
ENTRYPOINT ["/app/sub2api"]
# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["/app/sub2api"]

View File

@@ -5,7 +5,12 @@
# It only packages the pre-built binary, no compilation needed.
# =============================================================================
FROM alpine:3.19
ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine
FROM ${POSTGRES_IMAGE} AS pg-client
FROM ${ALPINE_IMAGE}
LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform"
@@ -16,8 +21,21 @@ RUN apk add --no-cache \
ca-certificates \
tzdata \
curl \
su-exec \
libpq \
zstd-libs \
lz4-libs \
krb5-libs \
libldap \
libedit \
&& rm -rf /var/cache/apk/*
# Copy pg_dump and psql from a version-matched PostgreSQL image so backup and
# restore work in the runtime container without requiring Docker socket access.
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/
# Create non-root user
RUN addgroup -g 1000 sub2api && \
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
@@ -30,11 +48,15 @@ COPY sub2api /app/sub2api
# Create data directory
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
USER sub2api
# Copy entrypoint script (fixes volume permissions then drops to sub2api)
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
ENTRYPOINT ["/app/sub2api"]
# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["/app/sub2api"]

View File

@@ -1,4 +1,4 @@
.PHONY: build build-backend build-frontend test test-backend test-frontend
.PHONY: build build-backend build-frontend build-datamanagementd test test-backend test-frontend test-datamanagementd secret-scan
# 一键编译前后端
build: build-backend build-frontend
@@ -11,6 +11,10 @@ build-backend:
build-frontend:
@pnpm --dir frontend run build
# 编译 datamanagementd宿主机数据管理进程
build-datamanagementd:
@cd datamanagement && go build -o datamanagementd ./cmd/datamanagementd
# 运行测试(后端 + 前端)
test: test-backend test-frontend
@@ -20,3 +24,9 @@ test-backend:
test-frontend:
@pnpm --dir frontend run lint:check
@pnpm --dir frontend run typecheck
test-datamanagementd:
@cd datamanagement && go test ./...
secret-scan:
@python3 tools/secret_scan.py

140
README.md
View File

@@ -8,27 +8,31 @@
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
**AI API Gateway Platform for Subscription Quota Distribution**
English | [中文](README_CN.md)
English | [中文](README_CN.md) | [日本語](README_JA.md)
</div>
> **Sub2API officially uses only the domains `sub2api.org` and `pincc.ai`. Other websites using the Sub2API name may be third-party deployments or services and are not affiliated with this project. Please verify and exercise your own judgment.**
---
## Demo
Try Sub2API online: **https://demo.sub2api.org/**
Try Sub2API online: **[https://demo.sub2api.org/](https://demo.sub2api.org/)**
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
| Email | Password |
|-------|----------|
| admin@sub2api.com | admin123 |
| admin@sub2api.org | admin123 |
## Overview
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions (like Claude Code $200/month). Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
## Features
@@ -38,7 +42,65 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
- **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate limits
- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md))
- **Admin Dashboard** - Web interface for monitoring and management
- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard
## ❤️ Sponsors
> [Want to appear here?](mailto:support@pincc.ai)
<table>
<tr>
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> is the official relay service built on Sub2API, offering stable access to Claude Code, Codex, Gemini and other popular models — ready to use, no deployment or maintenance required.</td>
</tr>
<tr>
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
<td>Thanks to PackyCode for sponsoring this project! PackyCode is a reliable and efficient API relay service provider, offering relay services for Claude Code, Codex, Gemini, and more. PackyCode provides special discounts for our software users: register using <a href="https://www.packyapi.com/register?aff=sub2api">this link</a> and enter the "sub2api" promo code during first recharge to get 10% off.</td>
</tr>
<tr>
<td width="180"><a href="https://poixe.com/i/sub2api"><img src="assets/partners/logos/poixe.png" alt="PoixeAi" width="150"></a></td>
<td>Thanks to Poixe Ai for sponsoring this project! Poixe AI provides reliable LLM API services. You can leverage the platform's API endpoints to seamlessly build AI-powered products. Additionally, you can become a vendor by providing AI API resources to the platform and earn revenue. Register through the exclusive <a href="https://poixe.com/i/sub2api">sub2api</a> referral link and receive a bonus of $5 USD on your first top-up.</td>
</tr>
<tr>
<td width="180"><a href="https://ctok.ai"><img src="assets/partners/logos/ctok.png" alt="CTok" width="150"></a></td>
<td>Thanks to CTok.ai for sponsoring this project! CTok.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://ctok.ai">here</a> to register!</td>
</tr>
<tr>
<td width="180"><a href="https://code.silkapi.com/"><img src="assets/partners/logos/silkapi.png" alt="silkapi" width="150"></a></td>
<td>Thanks to SilkAPI for sponsoring this project! <a href="https://code.silkapi.com/">SilkAPI</a> is a relay service built on Sub2API, specializing in providing high-speed and stable Codex API relay.</td>
</tr>
<tr>
<td width="180"><a href="https://ylscode.com/"><img src="assets/partners/logos/ylscode.png" alt="ylscode" width="150"></a></td>
<td>Thanks to YLS Code for sponsoring this project! <a href="https://ylscode.com/">YLS Code</a> is dedicated to building secure enterprise-grade Coding Agent productivity services, offering stable and fast Codex / Claude / Gemini subscription services along with pay-as-you-go API options for flexible choices. Register now for a limited-time 3-day Codex trial bonus!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>Thanks to AICodeMirror for sponsoring this project! AICodeMirror provides official high-stability relay services for Claude Code / Codex / Gemini CLI, with enterprise-grade concurrency, fast invoicing, and 24/7 dedicated technical support. Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original price, with extra discounts on top-ups! AICodeMirror offers special benefits for sub2api users: register via <a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">this link</a> to enjoy 20% off your first top-up, and enterprise customers can get up to 25% off!</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for sub2api users: if you register via <a href="https://aigocode.com/invite/SUB2API">this link</a>, you'll receive an extra 10% bonus credit on your first top-up!</td>
</tr>
</table>
## Ecosystem
Community projects that extend or integrate with Sub2API:
| Project | Description | Features |
|---------|-------------|----------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~Self-service payment system~~ | **Now Built-in** — Payment is now integrated into Sub2API, no separate deployment needed. See [Payment Configuration Guide](docs/PAYMENT.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native |
## Tech Stack
@@ -51,9 +113,15 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
---
## Documentation
## Nginx Reverse Proxy Note
- Dependency Security: `docs/dependency-security.md`
When using Nginx as a reverse proxy for Sub2API (or CRS) with Codex CLI, add the following to the `http` block in your Nginx configuration:
```nginx
underscores_in_headers on;
```
Nginx drops headers containing underscores by default (e.g. `session_id`), which breaks sticky session routing in multi-account setups.
---
@@ -149,14 +217,14 @@ mkdir -p sub2api-deploy && cd sub2api-deploy
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Start services
docker-compose -f docker-compose.local.yml up -d
docker compose up -d
# View logs
docker-compose -f docker-compose.local.yml logs -f sub2api
docker compose logs -f sub2api
```
**What the script does:**
- Downloads `docker-compose.local.yml` and `.env.example`
- Downloads `docker-compose.local.yml` (saved as `docker-compose.yml`) and `.env.example`
- Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
- Creates `.env` file with auto-generated secrets
- Creates data directories (uses local directories for easy backup/migration)
@@ -216,16 +284,16 @@ mkdir -p data postgres_data redis_data
# 5. Start all services
# Option A: Local directory version (recommended - easy migration)
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml up -d
# Option B: Named volumes version (simple setup)
docker-compose up -d
docker compose up -d
# 6. Check status
docker-compose -f docker-compose.local.yml ps
docker compose -f docker-compose.local.yml ps
# 7. View logs
docker-compose -f docker-compose.local.yml logs -f sub2api
docker compose -f docker-compose.local.yml logs -f sub2api
```
#### Deployment Versions
@@ -243,15 +311,15 @@ Open `http://YOUR_SERVER_IP:8080` in your browser.
If admin password was auto-generated, find it in logs:
```bash
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```
#### Upgrade
```bash
# Pull latest image and recreate container
docker-compose -f docker-compose.local.yml pull
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```
#### Easy Migration (Local Directory Version)
@@ -260,7 +328,7 @@ When using `docker-compose.local.yml`, migrate to a new server easily:
```bash
# On source server
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
@@ -270,23 +338,23 @@ scp sub2api-complete.tar.gz user@new-server:/path/
# On new server
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml up -d
```
#### Useful Commands
```bash
# Stop all services
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
# Restart
docker-compose -f docker-compose.local.yml restart
docker compose -f docker-compose.local.yml restart
# View all logs
docker-compose -f docker-compose.local.yml logs -f
docker compose -f docker-compose.local.yml logs -f
# Remove all data (caution!)
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```
@@ -363,6 +431,12 @@ default:
rate_multiplier: 1.0
```
### Sora Status (Temporarily Unavailable)
> ⚠️ Sora-related features are temporarily unavailable due to technical issues in upstream integration and media delivery.
> Please do not rely on Sora in production at this time.
> Existing `gateway.sora_*` configuration keys are reserved and may not take effect until these issues are resolved.
Additional security-related options are available in `config.yaml`:
- `cors.allowed_origins` for CORS allowlist
@@ -515,6 +589,28 @@ sub2api/
└── install.sh # One-click installation script
```
## Disclaimer
> **Please read carefully before using this project:**
>
> :rotating_light: **Terms of Service Risk**: Using this project may violate Anthropic's Terms of Service. Please read Anthropic's user agreement carefully before use. All risks arising from the use of this project are borne solely by the user.
>
> :book: **Disclaimer**: This project is for technical learning and research purposes only. The author assumes no responsibility for account suspension, service interruption, or any other losses caused by the use of this project.
---
## Star History
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture>
</a>
---
## License
MIT License

View File

@@ -8,27 +8,30 @@
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
**AI API 网关平台 - 订阅配额分发管理**
[English](README.md) | 中文
[English](README.md) | 中文 | [日本語](README_JA.md)
</div>
> **Sub2API 官方仅使用 `sub2api.org` 与 `pincc.ai` 两个域名。其他使用 Sub2API 名义的网站可能为第三方部署或服务,与本项目无关,请自行甄别。**
---
## 在线体验
体验地址:**https://v2.pincc.ai/**
体验地址:**[https://demo.sub2api.org/](https://demo.sub2api.org/)**
演示账号(共享演示环境;自建部署不会自动创建该账号):
| 邮箱 | 密码 |
|------|------|
| admin@sub2api.com | admin123 |
| admin@sub2api.org | admin123 |
## 项目概述
Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(如 Claude Code $200/月)的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务,平台负责鉴权、计费、负载均衡和请求转发。
Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务,平台负责鉴权、计费、负载均衡和请求转发。
## 核心功能
@@ -38,7 +41,65 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
- **智能调度** - 智能账号选择,支持粘性会话
- **并发控制** - 用户级和账号级并发限制
- **速率限制** - 可配置的请求和 Token 速率限制
- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe用户自助充值无需独立部署支付服务[配置指南](docs/PAYMENT_CN.md)
- **管理后台** - Web 界面进行监控和管理
- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如工单等),扩展管理后台功能
## ❤️ 赞助商
> [想出现在这里?](mailto:support@pincc.ai)
<table>
<tr>
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> 是基于 Sub2API 搭建的官方中转服务,提供 Claude Code、Codex、Gemini 等主流模型的稳定中转,开箱即用,免去自建部署与运维烦恼。</td>
</tr>
<tr>
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
<td>感谢 PackyCode 赞助了本项目PackyCode 是一家稳定、高效的API中转服务商提供 Claude Code、Codex、Gemini 等多种中转服务。PackyCode 为本软件的用户提供了特别优惠,使用<a href="https://www.packyapi.com/register?aff=sub2api">此链接</a>注册并在充值时填写"sub2api"优惠码首次充值可以享受9折优惠</td>
</tr>
<tr>
<td width="180"><a href="https://poixe.com/i/sub2api"><img src="assets/partners/logos/poixe.png" alt="PoixeAI" width="150"></a></td>
<td>感谢 Poixe AI 赞助了本项目Poixe AI 提供可靠的 AI 模型接口服务,您可以使用平台提供的 LLM API 接口轻松构建 AI 产品,同时也可以成为供应商,为平台提供大模型资源以赚取收益。通过 <a href="https://poixe.com/i/sub2api">此链接</a> 专属链接注册,充值额外赠送 $5 美金</td>
</tr>
<tr>
<td width="180"><a href="https://ctok.ai"><img src="assets/partners/logos/ctok.png" alt="CTok" width="150"></a></td>
<td>感谢 CTok.ai 赞助了本项目CTok.ai 致力于打造一站式 AI 编程工具服务平台。我们提供 Claude Code 专业套餐及技术社群服务,同时支持 Google Gemini 和 OpenAI Codex。通过精心设计的套餐方案和专业的技术社群为开发者提供稳定的服务保障和持续的技术支持让 AI 辅助编程真正成为开发者的生产力工具。点击<a href="https://ctok.ai">这里</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://code.silkapi.com/"><img src="assets/partners/logos/silkapi.png" alt="silkapi" width="150"></a></td>
<td>感谢 丝绸API 赞助了本项目! <a href="https://code.silkapi.com/">丝绸API</a> 是基于 Sub2API 搭建的中转服务,专注于提供 Codex 高速稳定API中转。</td>
</tr>
<tr>
<td width="180"><a href="https://ylscode.com/"><img src="assets/partners/logos/ylscode.png" alt="ylscode" width="150"></a></td>
<td>感谢 伊莉思Code 赞助了本项目! <a href="https://ylscode.com/">伊莉思Code</a> 致力于构建安全的企业级Coding Agent生产力服务提供稳定快速的 Codex / Claude / Gemini 订阅服务与即用即付API多种方案灵活选择限时注册赠送 3 天 Codex 试用福利!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>感谢 AICodeMirror 赞助了本项目AICodeMirror 提供 Claude Code / Codex / Gemini CLI 官方高稳定性中转服务企业级并发、快速开票、7×24 小时专属技术支持。Claude Code / Codex / Gemini 官方通道低至原价 38% / 2% / 9%充值更享额外折扣AICodeMirror 为 sub2api 用户提供专属福利:通过<a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">此链接</a>注册,首次充值立享 8 折优惠,企业客户最高可享 75 折!</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>感谢 AIGoCode 赞助了本项目AIGoCode 是一站式集成 Claude Code、Codex 以及最新 Gemini 模型的综合平台,为您提供稳定、高效、高性价比的 AI 编程服务。平台提供灵活的订阅方案,零封号风险,免 VPN 直连响应极速。AIGoCode 为 sub2api 用户准备了专属福利:通过<a href="https://aigocode.com/invite/SUB2API">此链接</a>注册,首次充值可额外获得 10% 赠送额度!</td>
</tr>
</table>
## 生态项目
围绕 Sub2API 的社区扩展与集成项目:
| 项目 | 说明 | 功能 |
|------|------|------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~自助支付系统~~ | **已内置** — 支付功能已集成到 Sub2API 中,无需独立部署。详见 [支付配置指南](docs/PAYMENT_CN.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用iOS/Android/Web支持用户管理、账号管理、监控看板、多后端切换基于 Expo + React Native 构建 |
## 技术栈
@@ -51,16 +112,15 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
---
## 文档
## Nginx 反向代理注意事项
- 依赖安全:`docs/dependency-security.md`
通过 Nginx 反向代理 Sub2API或 CRS 服务)并搭配 Codex CLI 使用时,需要在 Nginx 配置的 `http` 块中添加:
---
```nginx
underscores_in_headers on;
```
## OpenAI Responses 兼容注意事项
- 当请求包含 `function_call_output` 时,需要携带 `previous_response_id`,或在 `input` 中包含带 `call_id``tool_call`/`function_call`,或带非空 `id` 且与 `function_call_output.call_id` 匹配的 `item_reference`
- 若依赖上游历史记录,网关会强制 `store=true` 并需要复用 `previous_response_id`,以避免出现 “No tool call found for function call output” 错误。
Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),这会导致多账号环境下的粘性会话功能失效。
---
@@ -156,14 +216,14 @@ mkdir -p sub2api-deploy && cd sub2api-deploy
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# 启动服务
docker-compose -f docker-compose.local.yml up -d
docker compose up -d
# 查看日志
docker-compose -f docker-compose.local.yml logs -f sub2api
docker compose logs -f sub2api
```
**脚本功能:**
- 下载 `docker-compose.local.yml` `.env.example`
- 下载 `docker-compose.local.yml`(本地保存为 `docker-compose.yml``.env.example`
- 自动生成安全凭证JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD
- 创建 `.env` 文件并填充自动生成的密钥
- 创建数据目录(使用本地目录,便于备份和迁移)
@@ -223,16 +283,16 @@ mkdir -p data postgres_data redis_data
# 5. 启动所有服务
# 选项 A本地目录版推荐 - 易于迁移)
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml up -d
# 选项 B命名卷版简单设置
docker-compose up -d
docker compose up -d
# 6. 查看状态
docker-compose -f docker-compose.local.yml ps
docker compose -f docker-compose.local.yml ps
# 7. 查看日志
docker-compose -f docker-compose.local.yml logs -f sub2api
docker compose -f docker-compose.local.yml logs -f sub2api
```
#### 部署版本对比
@@ -244,21 +304,33 @@ docker-compose -f docker-compose.local.yml logs -f sub2api
**推荐:** 使用 `docker-compose.local.yml`(脚本部署)以便更轻松地管理数据。
#### 启用“数据管理”功能datamanagementd
如需启用管理后台“数据管理”,需要额外部署宿主机数据管理进程 `datamanagementd`
关键点:
- 主进程固定探测:`/tmp/sub2api-datamanagement.sock`
- 只有该 Socket 可连通时,数据管理功能才会开启
- Docker 场景需将宿主机 Socket 挂载到容器同路径
详细部署步骤见:`deploy/DATAMANAGEMENTD_CN.md`
#### 访问
在浏览器中打开 `http://你的服务器IP:8080`
如果管理员密码是自动生成的,在日志中查找:
```bash
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```
#### 升级
```bash
# 拉取最新镜像并重建容器
docker-compose -f docker-compose.local.yml pull
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```
#### 轻松迁移(本地目录版)
@@ -267,7 +339,7 @@ docker-compose -f docker-compose.local.yml up -d
```bash
# 源服务器
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
@@ -277,23 +349,23 @@ scp sub2api-complete.tar.gz user@new-server:/path/
# 新服务器
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker-compose -f docker-compose.local.yml up -d
docker compose -f docker-compose.local.yml up -d
```
#### 常用命令
```bash
# 停止所有服务
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
# 重启
docker-compose -f docker-compose.local.yml restart
docker compose -f docker-compose.local.yml restart
# 查看所有日志
docker-compose -f docker-compose.local.yml logs -f
docker compose -f docker-compose.local.yml logs -f
# 删除所有数据(谨慎!)
docker-compose -f docker-compose.local.yml down
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```
@@ -370,6 +442,33 @@ default:
rate_multiplier: 1.0
```
### Sora 功能状态(暂不可用)
> ⚠️ 当前 Sora 相关功能因上游接入与媒体链路存在技术问题,暂时不可用。
> 现阶段请勿在生产环境依赖 Sora 能力。
> 文档中的 `gateway.sora_*` 配置仅作预留,待技术问题修复后再恢复可用。
### Sora 媒体签名 URL功能恢复后可选
当配置 `gateway.sora_media_signing_key``gateway.sora_media_signed_url_ttl_seconds > 0` 时,网关会将 Sora 输出的媒体地址改写为临时签名 URL`/sora/media-signed/...`)。这样无需 API Key 即可在浏览器中直接访问,且具备过期控制与防篡改能力(签名包含 path + query
```yaml
gateway:
# /sora/media 是否强制要求 API Key默认 false
sora_media_require_api_key: false
# 媒体临时签名密钥(为空则禁用签名)
sora_media_signing_key: "your-signing-key"
# 临时签名 URL 有效期(秒)
sora_media_signed_url_ttl_seconds: 900
```
> 若未配置签名密钥,`/sora/media-signed` 将返回 503。
> 如需更严格的访问控制,可将 `sora_media_require_api_key` 设为 true仅允许携带 API Key 的 `/sora/media` 访问。
访问策略说明:
- `/sora/media`:内部调用或客户端携带 API Key 才能下载
- `/sora/media-signed`:外部可访问,但有签名 + 过期控制
`config.yaml` 还支持以下安全相关配置:
- `cors.allowed_origins` 配置 CORS 白名单
@@ -383,6 +482,14 @@ default:
- `server.trusted_proxies` 启用可信代理解析 X-Forwarded-For
- `turnstile.required` 在 release 模式强制启用 Turnstile
**网关防御纵深建议(重点)**
- `gateway.upstream_response_read_max_bytes`:限制非流式上游响应读取大小(默认 `8MB`),用于防止异常响应导致内存放大。
- `gateway.proxy_probe_response_read_max_bytes`:限制代理探测响应读取大小(默认 `1MB`)。
- `gateway.gemini_debug_response_headers`:默认 `false`,仅在排障时短时开启,避免高频请求日志开销。
- `/auth/register``/auth/login``/auth/login/2fa``/auth/send-verify-code` 已提供服务端兜底限流Redis 故障时 fail-close
- 推荐将 WAF/CDN 作为第一层防护,服务端限流与响应读取上限作为第二层兜底;两层同时保留,避免旁路流量与误配置风险。
**⚠️ 安全警告HTTP URL 配置**
`security.url_allowlist.enabled=false` 时,系统默认执行最小 URL 校验,**拒绝 HTTP URL**,仅允许 HTTPS。要允许 HTTP URL例如用于开发或内网测试必须显式设置
@@ -428,6 +535,29 @@ Invalid base URL: invalid url scheme: http
./sub2api
```
#### HTTP/2 (h2c) 与 HTTP/1.1 回退
后端明文端口默认支持 h2c并保留 HTTP/1.1 回退用于 WebSocket 与旧客户端。浏览器通常不支持 h2c性能收益主要在反向代理或内网链路。
**反向代理示例Caddy**
```caddyfile
transport http {
versions h2c h1
}
```
**验证:**
```bash
# h2c prior knowledge
curl --http2-prior-knowledge -I http://localhost:8080/health
# HTTP/1.1 回退
curl --http1.1 -I http://localhost:8080/health
# WebSocket 回退验证(需管理员 token
websocat -H="Sec-WebSocket-Protocol: sub2api-admin, jwt.<ADMIN_TOKEN>" ws://localhost:8080/api/v1/admin/ops/ws/qps
```
#### 开发模式
```bash
@@ -520,6 +650,28 @@ sub2api/
└── install.sh # 一键安装脚本
```
## 免责声明
> **使用本项目前请仔细阅读:**
>
> :rotating_light: **服务条款风险**: 使用本项目可能违反 Anthropic 的服务条款。请在使用前仔细阅读 Anthropic 的用户协议,使用本项目的一切风险由用户自行承担。
>
> :book: **免责声明**: 本项目仅供技术学习和研究使用,作者不对因使用本项目导致的账户封禁、服务中断或其他损失承担任何责任。
---
## Star History
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture>
</a>
---
## 许可证
MIT License

623
README_JA.md Normal file
View File

@@ -0,0 +1,623 @@
# Sub2API
<div align="center">
[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/)
[![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
**サブスクリプションクォータ配分のための AI API ゲートウェイプラットフォーム**
[English](README.md) | [中文](README_CN.md) | 日本語
</div>
> **Sub2API が公式に使用しているドメインは `sub2api.org` と `pincc.ai` のみです。Sub2API の名称を使用している他のウェブサイトは、サードパーティによるデプロイやサービスであり、本プロジェクトとは一切関係がありません。ご利用の際はご自身で確認・判断をお願いします。**
---
## デモ
Sub2API をオンラインでお試しください: **[https://demo.sub2api.org/](https://demo.sub2api.org/)**
デモ用認証情報(共有デモ環境です。セルフホスト環境では**自動作成されません**:
| メールアドレス | パスワード |
|-------|----------|
| admin@sub2api.org | admin123 |
## 概要
Sub2API は、AI 製品のサブスクリプションから API クォータを配分・管理するために設計された AI API ゲートウェイプラットフォームです。ユーザーはプラットフォームが生成した API キーを通じて上流の AI サービスにアクセスでき、プラットフォームは認証、課金、負荷分散、リクエスト転送を処理します。
## 機能
- **マルチアカウント管理** - 複数の上流アカウントタイプOAuth、APIキーをサポート
- **APIキー配布** - ユーザー向けの APIキーの生成と管理
- **精密な課金** - トークンレベルの使用量追跡とコスト計算
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要([設定ガイド](docs/PAYMENT.md)
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
- **外部システム連携** - 外部システム(チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
## ❤️ スポンサー
> [こちらに掲載しませんか?](mailto:support@pincc.ai)
<table>
<tr>
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> は Sub2API 上に構築された公式リレーサービスで、Claude Code、Codex、Gemini などの人気モデルへの安定したアクセスを提供します。デプロイやメンテナンスは不要で、すぐにご利用いただけます。</td>
</tr>
<tr>
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
<td>PackyCode のご支援に感謝しますPackyCode は Claude Code、Codex、Gemini などのリレーサービスを提供する信頼性の高い API 中継プラットフォームです。本ソフト利用者向けに特別割引があります:<a href="https://www.packyapi.com/register?aff=sub2api">このリンク</a>で登録し、チャージ時に「sub2api」クーポンを入力すると 10% オフになります。</td>
</tr>
<tr>
<td width="180"><a href="https://poixe.com/i/sub2api"><img src="assets/partners/logos/poixe.png" alt="PoixeAi" width="150"></a></td>
<td>Poixe AI のご支援に感謝しますPoixe AI は信頼性の高い LLM API サービスを提供しています。プラットフォームの API エンドポイントを活用して、AI 搭載プロダクトをシームレスに構築できます。また、ベンダーとして AI API リソースをプラットフォームに提供し、収益を得ることも可能です。専用の <a href="https://poixe.com/i/sub2api">sub2api</a> 紹介リンクから登録すると、初回チャージ時に $5 USD のボーナスがもらえます。</td>
</tr>
<tr>
<td width="180"><a href="https://ctok.ai"><img src="assets/partners/logos/ctok.png" alt="CTok" width="150"></a></td>
<td>CTok.ai のご支援に感謝しますCTok.ai はワンストップ AI プログラミングツールサービスプラットフォームの構築に取り組んでいます。Claude Code の専用プランと技術コミュニティサービスを提供し、Google Gemini や OpenAI Codex もサポートしています。丁寧に設計されたプランと専門的な技術コミュニティを通じて、開発者に安定したサービス保証と継続的な技術サポートを提供し、AI アシスト プログラミングを真の生産性向上ツールにします。<a href="https://ctok.ai">こちら</a>から登録!</td>
</tr>
<tr>
<td width="180"><a href="https://code.silkapi.com/"><img src="assets/partners/logos/silkapi.png" alt="silkapi" width="150"></a></td>
<td>SilkAPI のご支援に感謝します!<a href="https://code.silkapi.com/">SilkAPI</a> は Sub2API をベースに構築された中継サービスで、高速かつ安定した Codex API 中継の提供に特化しています。</td>
</tr>
<tr>
<td width="180"><a href="https://ylscode.com/"><img src="assets/partners/logos/ylscode.png" alt="ylscode" width="150"></a></td>
<td>YLS Code のご支援に感謝します!<a href="https://ylscode.com/">YLS Code</a> は安全なエンタープライズグレードの Coding Agent 生産性サービスの構築に取り組んでおり、安定かつ高速な Codex / Claude / Gemini サブスクリプションサービスと従量課金 API の柔軟なプランを提供しています。期間限定で新規登録者に 3 日間の Codex 試用特典をプレゼント中!</td>
</tr>
<tr>
<td width="180"><a href="https://www.aicodemirror.com/register?invitecode=KMVZQM"><img src="assets/partners/logos/AICodeMirror.jpg" alt="AICodeMirror" width="150"></a></td>
<td>AICodeMirror のご支援に感謝しますAICodeMirror は Claude Code / Codex / Gemini CLI の公式高安定性リレーサービスを提供しており、エンタープライズグレードの同時実行、迅速な請求書発行、24時間年中無休の専属テクニカルサポートを備えています。Claude Code / Codex / Gemini の公式チャネルを定価の 38% / 2% / 9% で利用可能、チャージ時にはさらに追加割引AICodeMirror は sub2api ユーザー向けに特別特典を提供中:<a href="https://www.aicodemirror.com/register?invitecode=KMVZQM">こちらのリンク</a>から登録すると、初回チャージが 20% オフ、法人のお客様は最大 25% オフ!</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>AIGoCode のご支援に感謝しますAIGoCode は Claude Code、Codex、最新の Gemini モデルを統合したオールインワンプラットフォームで、安定的かつ効率的でコストパフォーマンスに優れた AI コーディングサービスを提供します。柔軟なサブスクリプションプラン、アカウント停止リスクゼロ、VPN 不要の直接アクセス、超高速レスポンスが特長です。AIGoCode は sub2api ユーザー向けに特別特典を用意しています:<a href="https://aigocode.com/invite/SUB2API">こちらのリンク</a>から登録すると、初回チャージ時に 10% のボーナスクレジットを追加プレゼント!</td>
</tr>
</table>
## エコシステム
Sub2API を拡張・統合するコミュニティプロジェクト:
| プロジェクト | 説明 | 機能 |
|---------|-------------|----------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~セルフサービス決済システム~~ | **内蔵済み** — 決済機能は Sub2API に統合されました。別途デプロイは不要です。[決済設定ガイド](docs/PAYMENT.md)をご参照ください |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | モバイル管理コンソール | ユーザー管理、アカウント管理、監視ダッシュボード、マルチバックエンド切り替えが可能なクロスプラットフォームアプリiOS/Android/Web。Expo + React Native で構築 |
## 技術スタック
| コンポーネント | 技術 |
|-----------|------------|
| バックエンド | Go 1.25.7, Gin, Ent |
| フロントエンド | Vue 3.4+, Vite 5+, TailwindCSS |
| データベース | PostgreSQL 15+ |
| キャッシュ/キュー | Redis 7+ |
---
## Nginx リバースプロキシに関する注意
Sub2APIまたは CRSを Nginx でリバースプロキシし、Codex CLI と組み合わせて使用する場合、Nginx の `http` ブロックに以下の設定を追加してください:
```nginx
underscores_in_headers on;
```
Nginx はデフォルトでアンダースコアを含むヘッダー(例: `session_id`)を破棄するため、マルチアカウント構成でのスティッキーセッションルーティングに支障をきたします。
---
## デプロイ
### 方法1: スクリプトによるインストール(推奨)
GitHub Releases からビルド済みバイナリをダウンロードするワンクリックインストールスクリプトです。
#### 前提条件
- Linux サーバーamd64 または arm64
- PostgreSQL 15+(インストール済みかつ稼働中)
- Redis 7+(インストール済みかつ稼働中)
- root 権限
#### インストール手順
```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
```
スクリプトは以下を実行します:
1. システムアーキテクチャの検出
2. 最新リリースのダウンロード
3. バイナリを `/opt/sub2api` にインストール
4. systemd サービスの作成
5. システムユーザーと権限の設定
#### インストール後の作業
```bash
# 1. サービスを起動
sudo systemctl start sub2api
# 2. 起動時の自動起動を有効化
sudo systemctl enable sub2api
# 3. ブラウザでセットアップウィザードを開く
# http://YOUR_SERVER_IP:8080
```
セットアップウィザードでは以下の設定を行います:
- データベース設定
- Redis 設定
- 管理者アカウントの作成
#### アップグレード
**管理ダッシュボード**の左上にある**アップデートを確認**ボタンをクリックすることで、ダッシュボードから直接アップグレードできます。
Web インターフェースでは以下が可能です:
- 新しいバージョンの自動確認
- ワンクリックでのアップデートのダウンロードと適用
- 必要に応じたロールバック
#### よく使うコマンド
```bash
# ステータスを確認
sudo systemctl status sub2api
# ログを表示
sudo journalctl -u sub2api -f
# サービスを再起動
sudo systemctl restart sub2api
# アンインストール
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
```
---
### 方法2: Docker Compose推奨
PostgreSQL と Redis のコンテナを含む Docker Compose でデプロイします。
#### 前提条件
- Docker 20.10+
- Docker Compose v2+
#### クイックスタート(ワンクリックデプロイ)
自動デプロイスクリプトを使用して簡単にセットアップできます:
```bash
# デプロイ用ディレクトリを作成
mkdir -p sub2api-deploy && cd sub2api-deploy
# デプロイ準備スクリプトをダウンロードして実行
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# サービスを起動
docker compose up -d
# ログを表示
docker compose logs -f sub2api
```
**スクリプトの動作内容:**
- `docker-compose.local.yml``docker-compose.yml` として保存)と `.env.example` をダウンロード
- セキュアな認証情報JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORDを自動生成
- 自動生成されたシークレットで `.env` ファイルを作成
- データディレクトリを作成(バックアップ・移行が容易なローカルディレクトリを使用)
- 生成された認証情報を参照用に表示
#### 手動デプロイ
手動でセットアップする場合:
```bash
# 1. リポジトリをクローン
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
# 2. 環境設定ファイルをコピー
cp .env.example .env
# 3. 設定を編集(セキュアなパスワードを生成)
nano .env
```
**`.env` の必須設定:**
```bash
# PostgreSQL パスワード(必須)
POSTGRES_PASSWORD=your_secure_password_here
# JWT シークレット(推奨 - 再起動後もユーザーのログイン状態を保持)
JWT_SECRET=your_jwt_secret_here
# TOTP 暗号化キー(推奨 - 再起動後も二要素認証を維持)
TOTP_ENCRYPTION_KEY=your_totp_key_here
# オプション: 管理者アカウント
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password
# オプション: カスタムポート
SERVER_PORT=8080
```
**セキュアなシークレットの生成方法:**
```bash
# JWT_SECRET を生成
openssl rand -hex 32
# TOTP_ENCRYPTION_KEY を生成
openssl rand -hex 32
# POSTGRES_PASSWORD を生成
openssl rand -hex 32
```
```bash
# 4. データディレクトリを作成(ローカルバージョンの場合)
mkdir -p data postgres_data redis_data
# 5. すべてのサービスを起動
# オプション A: ローカルディレクトリバージョン(推奨 - 移行が容易)
docker compose -f docker-compose.local.yml up -d
# オプション B: 名前付きボリュームバージョン(シンプルなセットアップ)
docker compose up -d
# 6. ステータスを確認
docker compose -f docker-compose.local.yml ps
# 7. ログを表示
docker compose -f docker-compose.local.yml logs -f sub2api
```
#### デプロイバージョン
| バージョン | データストレージ | 移行 | 推奨用途 |
|---------|-------------|-----------|----------|
| **docker-compose.local.yml** | ローカルディレクトリ | ✅ 容易(ディレクトリ全体を tar | 本番環境、頻繁なバックアップ |
| **docker-compose.yml** | 名前付きボリューム | ⚠️ docker コマンドが必要 | シンプルなセットアップ |
**推奨:** データ管理が容易な `docker-compose.local.yml`(スクリプトによるデプロイ)を使用してください。
#### アクセス
ブラウザで `http://YOUR_SERVER_IP:8080` を開いてください。
管理者パスワードが自動生成された場合は、ログで確認できます:
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```
#### アップグレード
```bash
# 最新イメージをプルしてコンテナを再作成
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```
#### 簡単な移行(ローカルディレクトリバージョン)
`docker-compose.local.yml` を使用している場合、新しいサーバーへの移行が簡単です:
```bash
# 移行元サーバーにて
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
# 新しいサーバーに転送
scp sub2api-complete.tar.gz user@new-server:/path/
# 移行先サーバーにて
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -d
```
#### よく使うコマンド
```bash
# すべてのサービスを停止
docker compose -f docker-compose.local.yml down
# 再起動
docker compose -f docker-compose.local.yml restart
# すべてのログを表示
docker compose -f docker-compose.local.yml logs -f
# すべてのデータを削除(注意!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```
---
### 方法3: ソースからビルド
開発やカスタマイズのためにソースコードからビルドして実行します。
#### 前提条件
- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+
#### ビルド手順
```bash
# 1. リポジトリをクローン
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api
# 2. pnpm をインストール(未インストールの場合)
npm install -g pnpm
# 3. フロントエンドをビルド
cd frontend
pnpm install
pnpm run build
# 出力先: ../backend/internal/web/dist/
# 4. フロントエンドを組み込んだバックエンドをビルド
cd ../backend
go build -tags embed -o sub2api ./cmd/server
# 5. 設定ファイルを作成
cp ../deploy/config.example.yaml ./config.yaml
# 6. 設定を編集
nano config.yaml
```
> **注意:** `-tags embed` フラグはフロントエンドをバイナリに組み込みます。このフラグがない場合、バイナリはフロントエンド UI を提供しません。
**`config.yaml` の主要設定:**
```yaml
server:
host: "0.0.0.0"
port: 8080
mode: "release"
database:
host: "localhost"
port: 5432
user: "postgres"
password: "your_password"
dbname: "sub2api"
redis:
host: "localhost"
port: 6379
password: ""
jwt:
secret: "change-this-to-a-secure-random-string"
expire_hour: 24
default:
user_concurrency: 5
user_balance: 0
api_key_prefix: "sk-"
rate_multiplier: 1.0
```
### Sora ステータス(一時的に利用不可)
> ⚠️ Sora 関連の機能は、上流統合およびメディア配信の技術的問題により一時的に利用できません。
> 現時点では本番環境で Sora に依存しないでください。
> 既存の `gateway.sora_*` 設定キーは予約されていますが、これらの問題が解決されるまで有効にならない場合があります。
`config.yaml` では追加のセキュリティ関連オプションも利用できます:
- `cors.allowed_origins` - CORS 許可リスト
- `security.url_allowlist` - 上流/価格/CRS ホストの許可リスト
- `security.url_allowlist.enabled` - URL バリデーションの無効化(注意して使用)
- `security.url_allowlist.allow_insecure_http` - バリデーション無効時に HTTP URL を許可
- `security.url_allowlist.allow_private_hosts` - プライベート/ローカル IP アドレスを許可
- `security.response_headers.enabled` - 設定可能なレスポンスヘッダーフィルタリングを有効化(無効時はデフォルトの許可リストを使用)
- `security.csp` - Content-Security-Policy ヘッダーの制御
- `billing.circuit_breaker` - 課金エラー時にフェイルクローズ
- `server.trusted_proxies` - X-Forwarded-For パースの有効化
- `turnstile.required` - リリースモードでの Turnstile 必須化
**⚠️ セキュリティ警告: HTTP URL 設定**
`security.url_allowlist.enabled=false` の場合、システムはデフォルトで最小限の URL バリデーションを行い、**HTTP URL を拒否**して HTTPS のみを許可します。HTTP URL を許可するには(開発環境や内部テスト用など)、以下を明示的に設定する必要があります:
```yaml
security:
url_allowlist:
enabled: false # 許可リストチェックを無効化
allow_insecure_http: true # HTTP URL を許可(⚠️ セキュリティリスクあり)
```
**または環境変数で設定:**
```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
```
**HTTP を許可するリスク:**
- API キーとデータが**平文**で送信される(傍受の危険性)
- **中間者攻撃MITM**を受けやすい
- **本番環境には不適切**
**HTTP を使用すべき場面:**
- ✅ ローカルサーバーでの開発・テストhttp://localhost
- ✅ 信頼できるエンドポイントを持つ内部ネットワーク
- ✅ HTTPS 取得前のアカウント接続テスト
- ❌ 本番環境HTTPS のみを使用)
**この設定なしで表示されるエラー例:**
```
Invalid base URL: invalid url scheme: http
```
URL バリデーションまたはレスポンスヘッダーフィルタリングを無効にする場合は、ネットワーク層を強化してください:
- 上流ドメイン/IP のエグレス許可リストを適用
- プライベート/ループバック/リンクローカル範囲をブロック
- TLS のみのアウトバウンドトラフィックを強制
- プロキシで機密性の高い上流レスポンスヘッダーを除去
```bash
# 6. アプリケーションを実行
./sub2api
```
#### 開発モード
```bash
# バックエンド(ホットリロード付き)
cd backend
go run ./cmd/server
# フロントエンド(ホットリロード付き)
cd frontend
pnpm run dev
```
#### コード生成
`backend/ent/schema` を編集した場合、Ent + Wire を再生成してください:
```bash
cd backend
go generate ./ent
go generate ./cmd/server
```
---
## シンプルモード
シンプルモードは、フル SaaS 機能を必要とせず、素早くアクセスしたい個人開発者や社内チーム向けに設計されています。
- 有効化: 環境変数 `RUN_MODE=simple` を設定
- 違い: SaaS 関連機能を非表示にし、課金プロセスをスキップ
- セキュリティに関する注意: 本番環境では `SIMPLE_MODE_CONFIRM=true` も設定する必要があります
---
## Antigravity サポート
Sub2API は [Antigravity](https://antigravity.so/) アカウントをサポートしています。認証後、Claude および Gemini モデル用の専用エンドポイントが利用可能になります。
### 専用エンドポイント
| エンドポイント | モデル |
|----------|-------|
| `/antigravity/v1/messages` | Claude モデル |
| `/antigravity/v1beta/` | Gemini モデル |
### Claude Code の設定
```bash
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
```
### ハイブリッドスケジューリングモード
Antigravity アカウントはオプションの**ハイブリッドスケジューリング**をサポートしています。有効にすると、汎用エンドポイント `/v1/messages` および `/v1beta/` も Antigravity アカウントにリクエストをルーティングします。
> **⚠️ 警告**: Anthropic Claude と Antigravity Claude は**同じ会話コンテキスト内で混在させることはできません**。グループを使用して適切に分離してください。
### 既知の問題
Claude Code では、Plan Mode を自動的に終了できません。(通常、ネイティブの Claude API を使用する場合、計画が完了すると Claude Code はユーザーに計画を承認または拒否するオプションをポップアップ表示します。)
**回避策**: `Shift + Tab` を押して手動で Plan Mode を終了し、計画を承認または拒否するためのレスポンスを入力してください。
---
## プロジェクト構成
```
sub2api/
├── backend/ # Go バックエンドサービス
│ ├── cmd/server/ # アプリケーションエントリ
│ ├── internal/ # 内部モジュール
│ │ ├── config/ # 設定
│ │ ├── model/ # データモデル
│ │ ├── service/ # ビジネスロジック
│ │ ├── handler/ # HTTP ハンドラー
│ │ └── gateway/ # API ゲートウェイコア
│ └── resources/ # 静的リソース
├── frontend/ # Vue 3 フロントエンド
│ └── src/
│ ├── api/ # API 呼び出し
│ ├── stores/ # 状態管理
│ ├── views/ # ページコンポーネント
│ └── components/ # 再利用可能なコンポーネント
└── deploy/ # デプロイファイル
├── docker-compose.yml # Docker Compose 設定
├── .env.example # Docker Compose 用環境変数
├── config.example.yaml # バイナリデプロイ用フル設定ファイル
└── install.sh # ワンクリックインストールスクリプト
```
## 免責事項
> **本プロジェクトをご利用の前に、以下をよくお読みください:**
>
> :rotating_light: **利用規約違反のリスク**: 本プロジェクトの使用は Anthropic の利用規約に違反する可能性があります。使用前に Anthropic のユーザー契約をよくお読みください。本プロジェクトの使用に起因するすべてのリスクは、ユーザー自身が負うものとします。
>
> :book: **免責事項**: 本プロジェクトは技術的な学習および研究目的のみで提供されています。作者は、本プロジェクトの使用によるアカウント停止、サービス中断、その他の損失について一切の責任を負いません。
---
## スター履歴
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture>
</a>
---
## ライセンス
MIT License
---
<div align="center">
**このプロジェクトが役に立ったら、ぜひスターをお願いします!**
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -5,6 +5,7 @@ linters:
enable:
- depguard
- errcheck
- gosec
- govet
- ineffassign
- staticcheck
@@ -42,6 +43,22 @@ linters:
desc: "handler must not import gorm"
- pkg: github.com/redis/go-redis/v9
desc: "handler must not import redis"
gosec:
excludes:
- G101
- G103
- G104
- G109
- G115
- G201
- G202
- G301
- G302
- G304
- G306
- G404
severity: high
confidence: high
errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default.
@@ -76,20 +93,13 @@ linters:
check-escaping-errors: true
staticcheck:
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
# Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
dot-import-whitelist:
- fmt
# https://staticcheck.dev/docs/configuration/options/#initialisms
# Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS" ]
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
# Default: ["200", "400", "404", "500"]
http-status-code-whitelist: [ "200", "400", "404", "500" ]
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks
# Example (to disable some checks): [ "all", "-SA1000", "-SA1001"]
# Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks.
# Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
# Temporarily disable style checks to allow CI to pass
# "all" enables every SA/ST/S/QF check; only list the ones to disable.
checks:
- all
- -ST1000 # Package comment format
@@ -97,489 +107,19 @@ linters:
- -ST1020 # Comment on exported method format
- -ST1021 # Comment on exported type format
- -ST1022 # Comment on exported variable format
# Invalid regular expression.
# https://staticcheck.dev/docs/checks/#SA1000
- SA1000
# Invalid template.
# https://staticcheck.dev/docs/checks/#SA1001
- SA1001
# Invalid format in 'time.Parse'.
# https://staticcheck.dev/docs/checks/#SA1002
- SA1002
# Unsupported argument to functions in 'encoding/binary'.
# https://staticcheck.dev/docs/checks/#SA1003
- SA1003
# Suspiciously small untyped constant in 'time.Sleep'.
# https://staticcheck.dev/docs/checks/#SA1004
- SA1004
# Invalid first argument to 'exec.Command'.
# https://staticcheck.dev/docs/checks/#SA1005
- SA1005
# 'Printf' with dynamic first argument and no further arguments.
# https://staticcheck.dev/docs/checks/#SA1006
- SA1006
# Invalid URL in 'net/url.Parse'.
# https://staticcheck.dev/docs/checks/#SA1007
- SA1007
# Non-canonical key in 'http.Header' map.
# https://staticcheck.dev/docs/checks/#SA1008
- SA1008
# '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results.
# https://staticcheck.dev/docs/checks/#SA1010
- SA1010
# Various methods in the "strings" package expect valid UTF-8, but invalid input is provided.
# https://staticcheck.dev/docs/checks/#SA1011
- SA1011
# A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead.
# https://staticcheck.dev/docs/checks/#SA1012
- SA1012
# 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second.
# https://staticcheck.dev/docs/checks/#SA1013
- SA1013
# Non-pointer value passed to 'Unmarshal' or 'Decode'.
# https://staticcheck.dev/docs/checks/#SA1014
- SA1014
# Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions.
# https://staticcheck.dev/docs/checks/#SA1015
- SA1015
# Trapping a signal that cannot be trapped.
# https://staticcheck.dev/docs/checks/#SA1016
- SA1016
# Channels used with 'os/signal.Notify' should be buffered.
# https://staticcheck.dev/docs/checks/#SA1017
- SA1017
# 'strings.Replace' called with 'n == 0', which does nothing.
# https://staticcheck.dev/docs/checks/#SA1018
- SA1018
# Using a deprecated function, variable, constant or field.
# https://staticcheck.dev/docs/checks/#SA1019
- SA1019
# Using an invalid host:port pair with a 'net.Listen'-related function.
# https://staticcheck.dev/docs/checks/#SA1020
- SA1020
# Using 'bytes.Equal' to compare two 'net.IP'.
# https://staticcheck.dev/docs/checks/#SA1021
- SA1021
# Modifying the buffer in an 'io.Writer' implementation.
# https://staticcheck.dev/docs/checks/#SA1023
- SA1023
# A string cutset contains duplicate characters.
# https://staticcheck.dev/docs/checks/#SA1024
- SA1024
# It is not possible to use '(*time.Timer).Reset''s return value correctly.
# https://staticcheck.dev/docs/checks/#SA1025
- SA1025
# Cannot marshal channels or functions.
# https://staticcheck.dev/docs/checks/#SA1026
- SA1026
# Atomic access to 64-bit variable must be 64-bit aligned.
# https://staticcheck.dev/docs/checks/#SA1027
- SA1027
# 'sort.Slice' can only be used on slices.
# https://staticcheck.dev/docs/checks/#SA1028
- SA1028
# Inappropriate key in call to 'context.WithValue'.
# https://staticcheck.dev/docs/checks/#SA1029
- SA1029
# Invalid argument in call to a 'strconv' function.
# https://staticcheck.dev/docs/checks/#SA1030
- SA1030
# Overlapping byte slices passed to an encoder.
# https://staticcheck.dev/docs/checks/#SA1031
- SA1031
# Wrong order of arguments to 'errors.Is'.
# https://staticcheck.dev/docs/checks/#SA1032
- SA1032
# 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition.
# https://staticcheck.dev/docs/checks/#SA2000
- SA2000
# Empty critical section, did you mean to defer the unlock?.
# https://staticcheck.dev/docs/checks/#SA2001
- SA2001
# Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed.
# https://staticcheck.dev/docs/checks/#SA2002
- SA2002
# Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead.
# https://staticcheck.dev/docs/checks/#SA2003
- SA2003
# 'TestMain' doesn't call 'os.Exit', hiding test failures.
# https://staticcheck.dev/docs/checks/#SA3000
- SA3000
# Assigning to 'b.N' in benchmarks distorts the results.
# https://staticcheck.dev/docs/checks/#SA3001
- SA3001
# Binary operator has identical expressions on both sides.
# https://staticcheck.dev/docs/checks/#SA4000
- SA4000
# '&*x' gets simplified to 'x', it does not copy 'x'.
# https://staticcheck.dev/docs/checks/#SA4001
- SA4001
# Comparing unsigned values against negative values is pointless.
# https://staticcheck.dev/docs/checks/#SA4003
- SA4003
# The loop exits unconditionally after one iteration.
# https://staticcheck.dev/docs/checks/#SA4004
- SA4004
# Field assignment that will never be observed. Did you mean to use a pointer receiver?.
# https://staticcheck.dev/docs/checks/#SA4005
- SA4005
# A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?.
# https://staticcheck.dev/docs/checks/#SA4006
- SA4006
# The variable in the loop condition never changes, are you incrementing the wrong variable?.
# https://staticcheck.dev/docs/checks/#SA4008
- SA4008
# A function argument is overwritten before its first use.
# https://staticcheck.dev/docs/checks/#SA4009
- SA4009
# The result of 'append' will never be observed anywhere.
# https://staticcheck.dev/docs/checks/#SA4010
- SA4010
# Break statement with no effect. Did you mean to break out of an outer loop?.
# https://staticcheck.dev/docs/checks/#SA4011
- SA4011
# Comparing a value against NaN even though no value is equal to NaN.
# https://staticcheck.dev/docs/checks/#SA4012
- SA4012
# Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo.
# https://staticcheck.dev/docs/checks/#SA4013
- SA4013
# An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either.
# https://staticcheck.dev/docs/checks/#SA4014
- SA4014
# Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful.
# https://staticcheck.dev/docs/checks/#SA4015
- SA4015
# Certain bitwise operations, such as 'x ^ 0', do not do anything useful.
# https://staticcheck.dev/docs/checks/#SA4016
- SA4016
# Discarding the return values of a function without side effects, making the call pointless.
# https://staticcheck.dev/docs/checks/#SA4017
- SA4017
# Self-assignment of variables.
# https://staticcheck.dev/docs/checks/#SA4018
- SA4018
# Multiple, identical build constraints in the same file.
# https://staticcheck.dev/docs/checks/#SA4019
- SA4019
# Unreachable case clause in a type switch.
# https://staticcheck.dev/docs/checks/#SA4020
- SA4020
# "x = append(y)" is equivalent to "x = y".
# https://staticcheck.dev/docs/checks/#SA4021
- SA4021
# Comparing the address of a variable against nil.
# https://staticcheck.dev/docs/checks/#SA4022
- SA4022
# Impossible comparison of interface value with untyped nil.
# https://staticcheck.dev/docs/checks/#SA4023
- SA4023
# Checking for impossible return value from a builtin function.
# https://staticcheck.dev/docs/checks/#SA4024
- SA4024
# Integer division of literals that results in zero.
# https://staticcheck.dev/docs/checks/#SA4025
- SA4025
# Go constants cannot express negative zero.
# https://staticcheck.dev/docs/checks/#SA4026
- SA4026
# '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL.
# https://staticcheck.dev/docs/checks/#SA4027
- SA4027
# 'x % 1' is always zero.
# https://staticcheck.dev/docs/checks/#SA4028
- SA4028
# Ineffective attempt at sorting slice.
# https://staticcheck.dev/docs/checks/#SA4029
- SA4029
# Ineffective attempt at generating random number.
# https://staticcheck.dev/docs/checks/#SA4030
- SA4030
# Checking never-nil value against nil.
# https://staticcheck.dev/docs/checks/#SA4031
- SA4031
# Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value.
# https://staticcheck.dev/docs/checks/#SA4032
- SA4032
# Assignment to nil map.
# https://staticcheck.dev/docs/checks/#SA5000
- SA5000
# Deferring 'Close' before checking for a possible error.
# https://staticcheck.dev/docs/checks/#SA5001
- SA5001
# The empty for loop ("for {}") spins and can block the scheduler.
# https://staticcheck.dev/docs/checks/#SA5002
- SA5002
# Defers in infinite loops will never execute.
# https://staticcheck.dev/docs/checks/#SA5003
- SA5003
# "for { select { ..." with an empty default branch spins.
# https://staticcheck.dev/docs/checks/#SA5004
- SA5004
# The finalizer references the finalized object, preventing garbage collection.
# https://staticcheck.dev/docs/checks/#SA5005
- SA5005
# Infinite recursive call.
# https://staticcheck.dev/docs/checks/#SA5007
- SA5007
# Invalid struct tag.
# https://staticcheck.dev/docs/checks/#SA5008
- SA5008
# Invalid Printf call.
# https://staticcheck.dev/docs/checks/#SA5009
- SA5009
# Impossible type assertion.
# https://staticcheck.dev/docs/checks/#SA5010
- SA5010
# Possible nil pointer dereference.
# https://staticcheck.dev/docs/checks/#SA5011
- SA5011
# Passing odd-sized slice to function expecting even size.
# https://staticcheck.dev/docs/checks/#SA5012
- SA5012
# Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'.
# https://staticcheck.dev/docs/checks/#SA6000
- SA6000
# Missing an optimization opportunity when indexing maps by byte slices.
# https://staticcheck.dev/docs/checks/#SA6001
- SA6001
# Storing non-pointer values in 'sync.Pool' allocates memory.
# https://staticcheck.dev/docs/checks/#SA6002
- SA6002
# Converting a string to a slice of runes before ranging over it.
# https://staticcheck.dev/docs/checks/#SA6003
- SA6003
# Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'.
# https://staticcheck.dev/docs/checks/#SA6005
- SA6005
# Using io.WriteString to write '[]byte'.
# https://staticcheck.dev/docs/checks/#SA6006
- SA6006
# Defers in range loops may not run when you expect them to.
# https://staticcheck.dev/docs/checks/#SA9001
- SA9001
# Using a non-octal 'os.FileMode' that looks like it was meant to be in octal.
# https://staticcheck.dev/docs/checks/#SA9002
- SA9002
# Empty body in an if or else branch.
# https://staticcheck.dev/docs/checks/#SA9003
- SA9003
# Only the first constant has an explicit type.
# https://staticcheck.dev/docs/checks/#SA9004
- SA9004
# Trying to marshal a struct with no public fields nor custom marshaling.
# https://staticcheck.dev/docs/checks/#SA9005
- SA9005
# Dubious bit shifting of a fixed size integer value.
# https://staticcheck.dev/docs/checks/#SA9006
- SA9006
# Deleting a directory that shouldn't be deleted.
# https://staticcheck.dev/docs/checks/#SA9007
- SA9007
# 'else' branch of a type assertion is probably not reading the right value.
# https://staticcheck.dev/docs/checks/#SA9008
- SA9008
# Ineffectual Go compiler directive.
# https://staticcheck.dev/docs/checks/#SA9009
- SA9009
# NOTE: ST1000, ST1001, ST1003, ST1020, ST1021, ST1022 are disabled above
# Incorrectly formatted error string.
# https://staticcheck.dev/docs/checks/#ST1005
- ST1005
# Poorly chosen receiver name.
# https://staticcheck.dev/docs/checks/#ST1006
- ST1006
# A function's error value should be its last return value.
# https://staticcheck.dev/docs/checks/#ST1008
- ST1008
# Poorly chosen name for variable of type 'time.Duration'.
# https://staticcheck.dev/docs/checks/#ST1011
- ST1011
# Poorly chosen name for error variable.
# https://staticcheck.dev/docs/checks/#ST1012
- ST1012
# Should use constants for HTTP error codes, not magic numbers.
# https://staticcheck.dev/docs/checks/#ST1013
- ST1013
# A switch's default case should be the first or last case.
# https://staticcheck.dev/docs/checks/#ST1015
- ST1015
# Use consistent method receiver names.
# https://staticcheck.dev/docs/checks/#ST1016
- ST1016
# Don't use Yoda conditions.
# https://staticcheck.dev/docs/checks/#ST1017
- ST1017
# Avoid zero-width and control characters in string literals.
# https://staticcheck.dev/docs/checks/#ST1018
- ST1018
# Importing the same package multiple times.
# https://staticcheck.dev/docs/checks/#ST1019
- ST1019
# NOTE: ST1020, ST1021, ST1022 removed (disabled above)
# Redundant type in variable declaration.
# https://staticcheck.dev/docs/checks/#ST1023
- ST1023
# Use plain channel send or receive instead of single-case select.
# https://staticcheck.dev/docs/checks/#S1000
- S1000
# Replace for loop with call to copy.
# https://staticcheck.dev/docs/checks/#S1001
- S1001
# Omit comparison with boolean constant.
# https://staticcheck.dev/docs/checks/#S1002
- S1002
# Replace call to 'strings.Index' with 'strings.Contains'.
# https://staticcheck.dev/docs/checks/#S1003
- S1003
# Replace call to 'bytes.Compare' with 'bytes.Equal'.
# https://staticcheck.dev/docs/checks/#S1004
- S1004
# Drop unnecessary use of the blank identifier.
# https://staticcheck.dev/docs/checks/#S1005
- S1005
# Use "for { ... }" for infinite loops.
# https://staticcheck.dev/docs/checks/#S1006
- S1006
# Simplify regular expression by using raw string literal.
# https://staticcheck.dev/docs/checks/#S1007
- S1007
# Simplify returning boolean expression.
# https://staticcheck.dev/docs/checks/#S1008
- S1008
# Omit redundant nil check on slices, maps, and channels.
# https://staticcheck.dev/docs/checks/#S1009
- S1009
# Omit default slice index.
# https://staticcheck.dev/docs/checks/#S1010
- S1010
# Use a single 'append' to concatenate two slices.
# https://staticcheck.dev/docs/checks/#S1011
- S1011
# Replace 'time.Now().Sub(x)' with 'time.Since(x)'.
# https://staticcheck.dev/docs/checks/#S1012
- S1012
# Use a type conversion instead of manually copying struct fields.
# https://staticcheck.dev/docs/checks/#S1016
- S1016
# Replace manual trimming with 'strings.TrimPrefix'.
# https://staticcheck.dev/docs/checks/#S1017
- S1017
# Use "copy" for sliding elements.
# https://staticcheck.dev/docs/checks/#S1018
- S1018
# Simplify "make" call by omitting redundant arguments.
# https://staticcheck.dev/docs/checks/#S1019
- S1019
# Omit redundant nil check in type assertion.
# https://staticcheck.dev/docs/checks/#S1020
- S1020
# Merge variable declaration and assignment.
# https://staticcheck.dev/docs/checks/#S1021
- S1021
# Omit redundant control flow.
# https://staticcheck.dev/docs/checks/#S1023
- S1023
# Replace 'x.Sub(time.Now())' with 'time.Until(x)'.
# https://staticcheck.dev/docs/checks/#S1024
- S1024
# Don't use 'fmt.Sprintf("%s", x)' unnecessarily.
# https://staticcheck.dev/docs/checks/#S1025
- S1025
# Simplify error construction with 'fmt.Errorf'.
# https://staticcheck.dev/docs/checks/#S1028
- S1028
# Range over the string directly.
# https://staticcheck.dev/docs/checks/#S1029
- S1029
# Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'.
# https://staticcheck.dev/docs/checks/#S1030
- S1030
# Omit redundant nil check around loop.
# https://staticcheck.dev/docs/checks/#S1031
- S1031
# Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'.
# https://staticcheck.dev/docs/checks/#S1032
- S1032
# Unnecessary guard around call to "delete".
# https://staticcheck.dev/docs/checks/#S1033
- S1033
# Use result of type assertion to simplify cases.
# https://staticcheck.dev/docs/checks/#S1034
- S1034
# Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'.
# https://staticcheck.dev/docs/checks/#S1035
- S1035
# Unnecessary guard around map access.
# https://staticcheck.dev/docs/checks/#S1036
- S1036
# Elaborate way of sleeping.
# https://staticcheck.dev/docs/checks/#S1037
- S1037
# Unnecessarily complex way of printing formatted string.
# https://staticcheck.dev/docs/checks/#S1038
- S1038
# Unnecessary use of 'fmt.Sprint'.
# https://staticcheck.dev/docs/checks/#S1039
- S1039
# Type assertion to current type.
# https://staticcheck.dev/docs/checks/#S1040
- S1040
# Apply De Morgan's law.
# https://staticcheck.dev/docs/checks/#QF1001
- QF1001
# Convert untagged switch to tagged switch.
# https://staticcheck.dev/docs/checks/#QF1002
- QF1002
# Convert if/else-if chain to tagged switch.
# https://staticcheck.dev/docs/checks/#QF1003
- QF1003
# Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'.
# https://staticcheck.dev/docs/checks/#QF1004
- QF1004
# Expand call to 'math.Pow'.
# https://staticcheck.dev/docs/checks/#QF1005
- QF1005
# Lift 'if'+'break' into loop condition.
# https://staticcheck.dev/docs/checks/#QF1006
- QF1006
# Merge conditional assignment into variable declaration.
# https://staticcheck.dev/docs/checks/#QF1007
- QF1007
# Omit embedded fields from selector expression.
# https://staticcheck.dev/docs/checks/#QF1008
- QF1008
# Use 'time.Time.Equal' instead of '==' operator.
# https://staticcheck.dev/docs/checks/#QF1009
- QF1009
# Convert slice of bytes to string when printing it.
# https://staticcheck.dev/docs/checks/#QF1010
- QF1010
# Omit redundant type from variable declaration.
# https://staticcheck.dev/docs/checks/#QF1011
- QF1011
# Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'.
# https://staticcheck.dev/docs/checks/#QF1012
- QF1012
unused:
# Mark all struct fields that have been written to as used.
# Default: true
field-writes-are-uses: false
# Treat IncDec statement (e.g. `i++` or `i--`) as both read and write operation instead of just write.
field-writes-are-uses: true
# Default: false
post-statements-are-reads: true
# Mark all exported fields as used.
# default: true
exported-fields-are-used: false
# Mark all function parameters as used.
# default: true
parameters-are-used: true
# Mark all local variables as used.
# default: true
local-variables-are-used: false
# Mark all identifiers inside generated files as used.
# Default: true
generated-is-used: false
exported-fields-are-used: true
# Default: true
parameters-are-used: true
# Default: true
local-variables-are-used: false
# Default: true — must be true, ent generates 130K+ lines of code
generated-is-used: true
formatters:
enable:

View File

@@ -1,5 +0,0 @@
{
"global": {
"exclude": "G704"
}
}

View File

@@ -1,7 +1,14 @@
.PHONY: build test test-unit test-integration test-e2e
.PHONY: build generate test test-unit test-integration test-e2e
VERSION ?= $(shell tr -d '\r\n' < ./cmd/server/VERSION)
LDFLAGS ?= -s -w -X main.Version=$(VERSION)
build:
go build -o bin/server ./cmd/server
CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -trimpath -o bin/server ./cmd/server
generate:
go generate ./ent
go generate ./cmd/server
test:
go test ./...
@@ -14,4 +21,7 @@ test-integration:
go test -tags=integration ./...
test-e2e:
go test -tags=e2e ./...
./scripts/e2e-test.sh
test-e2e-local:
go test -tags=e2e -v -timeout=300s ./internal/integration/...

View File

@@ -17,7 +17,7 @@ func main() {
email := flag.String("email", "", "Admin email to issue a JWT for (defaults to first active admin)")
flag.Parse()
cfg, err := config.Load()
cfg, err := config.LoadForBootstrap()
if err != nil {
log.Fatalf("failed to load config: %v", err)
}
@@ -33,7 +33,7 @@ func main() {
}()
userRepo := repository.NewUserRepository(client, sqlDB)
authService := service.NewAuthService(userRepo, nil, nil, cfg, nil, nil, nil, nil, nil)
authService := service.NewAuthService(client, userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

View File

@@ -1 +1 @@
0.1.83
0.1.112

View File

@@ -8,7 +8,6 @@ import (
"errors"
"flag"
"log"
"log/slog"
"net/http"
"os"
"os/signal"
@@ -19,11 +18,14 @@ import (
_ "github.com/Wei-Shaw/sub2api/ent/runtime"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/setup"
"github.com/Wei-Shaw/sub2api/internal/web"
"github.com/gin-gonic/gin"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
//go:embed VERSION
@@ -38,7 +40,12 @@ var (
)
func init() {
// Read version from embedded VERSION file
// 如果 Version 已通过 ldflags 注入(例如 -X main.Version=...),则不要覆盖。
if strings.TrimSpace(Version) != "" {
return
}
// 默认从 embedded VERSION 文件读取版本号(编译期打包进二进制)。
Version = strings.TrimSpace(embeddedVersion)
if Version == "" {
Version = "0.0.0-dev"
@@ -47,22 +54,9 @@ func init() {
// initLogger configures the default slog handler based on gin.Mode().
// In non-release mode, Debug level logs are enabled.
func initLogger() {
var level slog.Level
if gin.Mode() == gin.ReleaseMode {
level = slog.LevelInfo
} else {
level = slog.LevelDebug
}
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: level,
})
slog.SetDefault(slog.New(handler))
}
func main() {
// Initialize slog logger based on gin mode
initLogger()
logger.InitBootstrap()
defer logger.Sync()
// Parse command line flags
setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
@@ -106,7 +100,7 @@ func runSetupServer() {
r := gin.New()
r.Use(middleware.Recovery())
r.Use(middleware.CORS(config.CORSConfig{}))
r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}))
r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}, nil))
// Register setup routes
setup.RegisterRoutes(r)
@@ -122,16 +116,26 @@ func runSetupServer() {
log.Printf("Setup wizard available at http://%s", addr)
log.Println("Complete the setup wizard to configure Sub2API")
if err := r.Run(addr); err != nil {
server := &http.Server{
Addr: addr,
Handler: h2c.NewHandler(r, &http2.Server{}),
ReadHeaderTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Failed to start setup server: %v", err)
}
}
func runMainServer() {
cfg, err := config.Load()
cfg, err := config.LoadForBootstrap()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
if err := logger.Init(logger.OptionsFromConfig(cfg.Log)); err != nil {
log.Fatalf("Failed to initialize logger: %v", err)
}
if cfg.RunMode == config.RunModeSimple {
log.Println("⚠️ WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED")
}

View File

@@ -7,11 +7,13 @@ import (
"context"
"log"
"net/http"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
@@ -34,12 +36,16 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
// Business layer ProviderSets
repository.ProviderSet,
service.ProviderSet,
payment.ProviderSet,
middleware.ProviderSet,
handler.ProviderSet,
// Server layer ProviderSet
server.ProviderSet,
// Privacy client factory for OpenAI training opt-out
providePrivacyClientFactory,
// BuildInfo provider
provideServiceBuildInfo,
@@ -52,6 +58,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
return nil, nil
}
func providePrivacyClientFactory() service.PrivacyClientFactory {
return repository.CreatePrivacyReqClient
}
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
return service.BuildInfo{
Version: buildInfo.Version,
@@ -67,28 +77,38 @@ func provideCleanup(
opsAlertEvaluator *service.OpsAlertEvaluatorService,
opsCleanup *service.OpsCleanupService,
opsScheduledReport *service.OpsScheduledReportService,
opsSystemLogSink *service.OpsSystemLogSink,
schedulerSnapshot *service.SchedulerSnapshotService,
tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
pricing *service.PricingService,
emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
subscriptionService *service.SubscriptionService,
oauth *service.OAuthService,
openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Cleanup steps in reverse dependency order
cleanupSteps := []struct {
type cleanupStep struct {
name string
fn func() error
}{
}
// 应用层清理步骤可并行执行基础设施资源Redis/Ent最后按顺序关闭。
parallelSteps := []cleanupStep{
{"OpsScheduledReportService", func() error {
if opsScheduledReport != nil {
opsScheduledReport.Stop()
@@ -101,6 +121,12 @@ func provideCleanup(
}
return nil
}},
{"OpsSystemLogSink", func() error {
if opsSystemLogSink != nil {
opsSystemLogSink.Stop()
}
return nil
}},
{"OpsAlertEvaluatorService", func() error {
if opsAlertEvaluator != nil {
opsAlertEvaluator.Stop()
@@ -131,6 +157,12 @@ func provideCleanup(
}
return nil
}},
{"IdempotencyCleanupService", func() error {
if idempotencyCleanup != nil {
idempotencyCleanup.Stop()
}
return nil
}},
{"TokenRefreshService", func() error {
tokenRefresh.Stop()
return nil
@@ -143,6 +175,12 @@ func provideCleanup(
subscriptionExpiry.Stop()
return nil
}},
{"SubscriptionService", func() error {
if subscriptionService != nil {
subscriptionService.Stop()
}
return nil
}},
{"PricingService", func() error {
pricing.Stop()
return nil
@@ -155,6 +193,12 @@ func provideCleanup(
billingCache.Stop()
return nil
}},
{"UsageRecordWorkerPool", func() error {
if usageRecordWorkerPool != nil {
usageRecordWorkerPool.Stop()
}
return nil
}},
{"OAuthService", func() error {
oauth.Stop()
return nil
@@ -171,23 +215,78 @@ func provideCleanup(
antigravityOAuth.Stop()
return nil
}},
{"OpenAIWSPool", func() error {
if openAIGateway != nil {
openAIGateway.CloseOpenAIWSPool()
}
return nil
}},
{"ScheduledTestRunnerService", func() error {
if scheduledTestRunner != nil {
scheduledTestRunner.Stop()
}
return nil
}},
{"BackupService", func() error {
if backupSvc != nil {
backupSvc.Stop()
}
return nil
}},
{"PaymentOrderExpiryService", func() error {
if paymentOrderExpiry != nil {
paymentOrderExpiry.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{
{"Redis", func() error {
if rdb == nil {
return nil
}
return rdb.Close()
}},
{"Ent", func() error {
if entClient == nil {
return nil
}
return entClient.Close()
}},
}
for _, step := range cleanupSteps {
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
// Continue with remaining cleanup steps even if one fails
} else {
runParallel := func(steps []cleanupStep) {
var wg sync.WaitGroup
for i := range steps {
step := steps[i]
wg.Add(1)
go func() {
defer wg.Done()
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
return
}
log.Printf("[Cleanup] %s succeeded", step.name)
}()
}
wg.Wait()
}
runSequential := func(steps []cleanupStep) {
for i := range steps {
step := steps[i]
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
continue
}
log.Printf("[Cleanup] %s succeeded", step.name)
}
}
runParallel(parallelSteps)
runSequential(infraSteps)
// Check if context timed out
select {
case <-ctx.Done():

View File

@@ -12,6 +12,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
@@ -19,6 +20,7 @@ import (
"github.com/redis/go-redis/v9"
"log"
"net/http"
"sync"
"time"
)
@@ -47,7 +49,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
redisClient := repository.ProvideRedis(configConfig)
refreshTokenCache := repository.NewRefreshTokenCache(redisClient)
settingRepository := repository.NewSettingRepository(client)
settingService := service.NewSettingService(settingRepository, configConfig)
groupRepository := repository.NewGroupRepository(client, db)
proxyRepository := repository.NewProxyRepository(client, db)
settingService := service.ProvideSettingService(settingRepository, groupRepository, proxyRepository, configConfig)
emailCache := repository.NewEmailCache(redisClient)
emailService := service.NewEmailService(settingRepository, emailCache)
turnstileVerifier := repository.NewTurnstileVerifier()
@@ -56,17 +60,16 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
promoCodeRepository := repository.NewPromoCodeRepository(client)
billingCache := repository.NewBillingCache(redisClient)
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
apiKeyRepository := repository.NewAPIKeyRepository(client)
groupRepository := repository.NewGroupRepository(client, db)
apiKeyRepository := repository.NewAPIKeyRepository(client, db)
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, configConfig)
userGroupRateRepository := repository.NewUserGroupRateRepository(db)
apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig)
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
authService := service.NewAuthService(userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService)
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
authService := service.NewAuthService(client, userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService, subscriptionService)
userService := service.NewUserService(userRepository, settingRepository, apiKeyAuthCacheInvalidator, billingCache)
redeemCache := repository.NewRedeemCache(redisClient)
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
secretEncryptor, err := repository.NewAESEncryptor(configConfig)
@@ -76,7 +79,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
totpCache := repository.NewTotpCache(redisClient)
totpService := service.NewTotpService(userRepository, secretEncryptor, totpCache, settingService, emailService, emailQueueService)
authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService, promoService, redeemService, totpService)
userHandler := handler.NewUserHandler(userService)
userHandler := handler.NewUserHandler(userService, emailService, emailCache)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageLogRepository := repository.NewUsageLogRepository(client, db)
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
@@ -96,23 +99,27 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
}
dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig)
dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService)
schedulerCache := repository.NewSchedulerCache(redisClient)
schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
proxyRepository := repository.NewProxyRepository(client, db)
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator)
privacyClientFactory := providePrivacyClientFactory()
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService)
groupHandler := admin.NewGroupHandler(adminService)
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
rpmCache := repository.NewRPMCache(redisClient)
groupCapacityService := service.NewGroupCapacityService(accountRepository, groupRepository, concurrencyService, sessionLimitCache, rpmCache)
groupHandler := admin.NewGroupHandler(adminService, dashboardService, groupCapacityService)
claudeOAuthClient := repository.NewClaudeOAuthClient()
oAuthService := service.NewOAuthService(proxyRepository, claudeOAuthClient)
openAIOAuthClient := repository.NewOpenAIOAuthClient()
openAIOAuthService := service.NewOpenAIOAuthService(proxyRepository, openAIOAuthClient)
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient()
geminiOAuthService := service.NewGeminiOAuthService(proxyRepository, geminiOAuthClient, geminiCliCodeAssistClient, configConfig)
driveClient := repository.NewGeminiDriveClient()
geminiOAuthService := service.NewGeminiOAuthService(proxyRepository, geminiOAuthClient, geminiCliCodeAssistClient, driveClient, configConfig)
antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository)
tempUnschedCache := repository.NewTempUnschedCache(redisClient)
@@ -125,26 +132,37 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
usageCache := service.NewUsageCache()
identityCache := repository.NewIdentityCache(redisClient)
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache)
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService)
tlsFingerprintProfileRepository := repository.NewTLSFingerprintProfileRepository(client)
tlsFingerprintProfileCache := repository.NewTLSFingerprintProfileCache(redisClient)
tlsFingerprintProfileService := service.NewTLSFingerprintProfileService(tlsFingerprintProfileRepository, tlsFingerprintProfileCache)
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache, tlsFingerprintProfileService)
oAuthRefreshAPI := service.NewOAuthRefreshAPI(accountRepository, geminiTokenCache)
geminiTokenProvider := service.ProvideGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService, oAuthRefreshAPI)
gatewayCache := repository.NewGatewayCache(redisClient)
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig)
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, schedulerSnapshotService, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
antigravityTokenProvider := service.ProvideAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService, oAuthRefreshAPI, tempUnschedCache)
internal500CounterCache := repository.NewInternal500CounterCache(redisClient)
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, schedulerSnapshotService, antigravityTokenProvider, rateLimitService, httpUpstream, settingService, internal500CounterCache)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService)
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, compositeTokenCacheInvalidator)
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator)
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
dataManagementService := service.NewDataManagementService()
dataManagementHandler := admin.NewDataManagementHandler(dataManagementService)
backupObjectStoreFactory := repository.NewS3BackupStoreFactory()
dbDumper := repository.NewPgDumper(configConfig)
backupService := service.ProvideBackupService(settingRepository, configConfig, secretEncryptor, backupObjectStoreFactory, dbDumper)
backupHandler := admin.NewBackupHandler(backupService, userService)
oAuthHandler := admin.NewOAuthHandler(oAuthService)
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService)
proxyHandler := admin.NewProxyHandler(adminService)
adminRedeemHandler := admin.NewRedeemHandler(adminService)
adminRedeemHandler := admin.NewRedeemHandler(adminService, redeemService)
promoHandler := admin.NewPromoHandler(promoService)
opsRepository := repository.NewOpsRepository(db)
usageBillingRepository := repository.NewUsageBillingRepository(client, db)
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient)
if err != nil {
@@ -153,20 +171,26 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
billingService := service.NewBillingService(configConfig, pricingService)
identityService := service.NewIdentityService(identityCache)
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
claudeTokenProvider := service.NewClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService)
claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oAuthRefreshAPI)
digestSessionStore := service.NewDigestSessionStore()
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, digestSessionStore)
openAITokenProvider := service.NewOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider)
channelRepository := repository.NewChannelRepository(db)
channelService := service.NewChannelService(channelRepository, apiKeyAuthCacheInvalidator)
modelPricingResolver := service.NewModelPricingResolver(channelService, billingService)
balanceNotifyService := service.ProvideBalanceNotifyService(emailService, settingRepository, accountRepository)
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)
openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oAuthRefreshAPI)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, modelPricingResolver, channelService, balanceNotifyService)
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
opsHandler := admin.NewOpsHandler(opsService)
updateCache := repository.NewUpdateCache(redisClient)
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
serviceBuildInfo := provideServiceBuildInfo(buildInfo)
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo)
systemHandler := handler.ProvideSystemHandler(updateService)
idempotencyRepository := repository.NewIdempotencyRepository(client, db)
systemOperationLockService := service.ProvideSystemOperationLockService(idempotencyRepository, configConfig)
systemHandler := handler.ProvideSystemHandler(updateService, systemOperationLockService)
adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
usageCleanupRepository := repository.NewUsageCleanupRepository(client, db)
usageCleanupService := service.ProvideUsageCleanupService(usageCleanupRepository, timingWheelService, dashboardAggregationService, configConfig)
@@ -179,12 +203,37 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient)
errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache)
errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler)
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, apiKeyService, errorPassthroughService, configConfig)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, errorPassthroughService, configConfig)
tlsFingerprintProfileHandler := admin.NewTLSFingerprintProfileHandler(tlsFingerprintProfileService)
adminAPIKeyHandler := admin.NewAdminAPIKeyHandler(adminService)
scheduledTestPlanRepository := repository.NewScheduledTestPlanRepository(db)
scheduledTestResultRepository := repository.NewScheduledTestResultRepository(db)
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
channelHandler := admin.NewChannelHandler(channelService, billingService)
registry := payment.ProvideRegistry()
encryptionKey, err := payment.ProvideEncryptionKey(configConfig)
if err != nil {
return nil, err
}
defaultLoadBalancer := payment.ProvideDefaultLoadBalancer(client, encryptionKey)
paymentConfigService := service.ProvidePaymentConfigService(client, settingRepository, encryptionKey)
paymentService := service.NewPaymentService(client, registry, defaultLoadBalancer, redeemService, subscriptionService, paymentConfigService, userRepository, groupRepository)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService)
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService)
paymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, paymentHandler)
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, userMessageQueueService, configConfig, settingService)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, configConfig)
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
totpHandler := handler.NewTotpHandler(totpService)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler)
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService)
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, idempotencyCoordinator, idempotencyCleanupService)
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
@@ -195,10 +244,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig)
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig)
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oAuthRefreshAPI)
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService)
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService)
application := &Application{
Server: httpServer,
Cleanup: v,
@@ -213,6 +263,10 @@ type Application struct {
Cleanup func()
}
func providePrivacyClientFactory() service.PrivacyClientFactory {
return repository.CreatePrivacyReqClient
}
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
return service.BuildInfo{
Version: buildInfo.Version,
@@ -228,27 +282,37 @@ func provideCleanup(
opsAlertEvaluator *service.OpsAlertEvaluatorService,
opsCleanup *service.OpsCleanupService,
opsScheduledReport *service.OpsScheduledReportService,
opsSystemLogSink *service.OpsSystemLogSink,
schedulerSnapshot *service.SchedulerSnapshotService,
tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
pricing *service.PricingService,
emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
subscriptionService *service.SubscriptionService,
oauth *service.OAuthService,
openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cleanupSteps := []struct {
type cleanupStep struct {
name string
fn func() error
}{
}
parallelSteps := []cleanupStep{
{"OpsScheduledReportService", func() error {
if opsScheduledReport != nil {
opsScheduledReport.Stop()
@@ -261,6 +325,12 @@ func provideCleanup(
}
return nil
}},
{"OpsSystemLogSink", func() error {
if opsSystemLogSink != nil {
opsSystemLogSink.Stop()
}
return nil
}},
{"OpsAlertEvaluatorService", func() error {
if opsAlertEvaluator != nil {
opsAlertEvaluator.Stop()
@@ -291,6 +361,12 @@ func provideCleanup(
}
return nil
}},
{"IdempotencyCleanupService", func() error {
if idempotencyCleanup != nil {
idempotencyCleanup.Stop()
}
return nil
}},
{"TokenRefreshService", func() error {
tokenRefresh.Stop()
return nil
@@ -303,6 +379,12 @@ func provideCleanup(
subscriptionExpiry.Stop()
return nil
}},
{"SubscriptionService", func() error {
if subscriptionService != nil {
subscriptionService.Stop()
}
return nil
}},
{"PricingService", func() error {
pricing.Stop()
return nil
@@ -315,6 +397,12 @@ func provideCleanup(
billingCache.Stop()
return nil
}},
{"UsageRecordWorkerPool", func() error {
if usageRecordWorkerPool != nil {
usageRecordWorkerPool.Stop()
}
return nil
}},
{"OAuthService", func() error {
oauth.Stop()
return nil
@@ -331,23 +419,78 @@ func provideCleanup(
antigravityOAuth.Stop()
return nil
}},
{"OpenAIWSPool", func() error {
if openAIGateway != nil {
openAIGateway.CloseOpenAIWSPool()
}
return nil
}},
{"ScheduledTestRunnerService", func() error {
if scheduledTestRunner != nil {
scheduledTestRunner.Stop()
}
return nil
}},
{"BackupService", func() error {
if backupSvc != nil {
backupSvc.Stop()
}
return nil
}},
{"PaymentOrderExpiryService", func() error {
if paymentOrderExpiry != nil {
paymentOrderExpiry.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{
{"Redis", func() error {
if rdb == nil {
return nil
}
return rdb.Close()
}},
{"Ent", func() error {
if entClient == nil {
return nil
}
return entClient.Close()
}},
}
for _, step := range cleanupSteps {
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
runParallel := func(steps []cleanupStep) {
var wg sync.WaitGroup
for i := range steps {
step := steps[i]
wg.Add(1)
go func() {
defer wg.Done()
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
return
}
log.Printf("[Cleanup] %s succeeded", step.name)
}()
}
wg.Wait()
}
} else {
runSequential := func(steps []cleanupStep) {
for i := range steps {
step := steps[i]
if err := step.fn(); err != nil {
log.Printf("[Cleanup] %s failed: %v", step.name, err)
continue
}
log.Printf("[Cleanup] %s succeeded", step.name)
}
}
runParallel(parallelSteps)
runSequential(infraSteps)
select {
case <-ctx.Done():
log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds")

View File

@@ -0,0 +1,84 @@
package main
import (
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/stretchr/testify/require"
)
func TestProvideServiceBuildInfo(t *testing.T) {
in := handler.BuildInfo{
Version: "v-test",
BuildType: "release",
}
out := provideServiceBuildInfo(in)
require.Equal(t, in.Version, out.Version)
require.Equal(t, in.BuildType, out.BuildType)
}
func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
cfg := &config.Config{}
oauthSvc := service.NewOAuthService(nil, nil)
openAIOAuthSvc := service.NewOpenAIOAuthService(nil, nil)
geminiOAuthSvc := service.NewGeminiOAuthService(nil, nil, nil, nil, cfg)
antigravityOAuthSvc := service.NewAntigravityOAuthService(nil)
tokenRefreshSvc := service.NewTokenRefreshService(
nil,
oauthSvc,
openAIOAuthSvc,
geminiOAuthSvc,
antigravityOAuthSvc,
nil,
nil,
cfg,
nil,
)
accountExpirySvc := service.NewAccountExpiryService(nil, time.Second)
subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second)
pricingSvc := service.NewPricingService(cfg, nil)
emailQueueSvc := service.NewEmailQueueService(nil, 1)
billingCacheSvc := service.NewBillingCacheService(nil, nil, nil, nil, cfg)
idempotencyCleanupSvc := service.NewIdempotencyCleanupService(nil, cfg)
schedulerSnapshotSvc := service.NewSchedulerSnapshotService(nil, nil, nil, nil, cfg)
opsSystemLogSinkSvc := service.NewOpsSystemLogSink(nil)
cleanup := provideCleanup(
nil, // entClient
nil, // redis
&service.OpsMetricsCollector{},
&service.OpsAggregationService{},
&service.OpsAlertEvaluatorService{},
&service.OpsCleanupService{},
&service.OpsScheduledReportService{},
opsSystemLogSinkSvc,
schedulerSnapshotSvc,
tokenRefreshSvc,
accountExpirySvc,
subscriptionExpirySvc,
&service.UsageCleanupService{},
idempotencyCleanupSvc,
pricingSvc,
emailQueueSvc,
billingCacheSvc,
&service.UsageRecordWorkerPool{},
&service.SubscriptionService{},
oauthSvc,
openAIOAuthSvc,
geminiOAuthSvc,
antigravityOAuthSvc,
nil, // openAIGateway
nil, // scheduledTestRunner
nil, // backupSvc
nil, // paymentOrderExpiry
)
require.NotPanics(t, func() {
cleanup()
})
}

View File

@@ -41,6 +41,8 @@ type Account struct {
ProxyID *int64 `json:"proxy_id,omitempty"`
// Concurrency holds the value of the "concurrency" field.
Concurrency int `json:"concurrency,omitempty"`
// LoadFactor holds the value of the "load_factor" field.
LoadFactor *int `json:"load_factor,omitempty"`
// Priority holds the value of the "priority" field.
Priority int `json:"priority,omitempty"`
// RateMultiplier holds the value of the "rate_multiplier" field.
@@ -63,6 +65,10 @@ type Account struct {
RateLimitResetAt *time.Time `json:"rate_limit_reset_at,omitempty"`
// OverloadUntil holds the value of the "overload_until" field.
OverloadUntil *time.Time `json:"overload_until,omitempty"`
// TempUnschedulableUntil holds the value of the "temp_unschedulable_until" field.
TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"`
// TempUnschedulableReason holds the value of the "temp_unschedulable_reason" field.
TempUnschedulableReason *string `json:"temp_unschedulable_reason,omitempty"`
// SessionWindowStart holds the value of the "session_window_start" field.
SessionWindowStart *time.Time `json:"session_window_start,omitempty"`
// SessionWindowEnd holds the value of the "session_window_end" field.
@@ -139,11 +145,11 @@ func (*Account) scanValues(columns []string) ([]any, error) {
values[i] = new(sql.NullBool)
case account.FieldRateMultiplier:
values[i] = new(sql.NullFloat64)
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
values[i] = new(sql.NullInt64)
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus:
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
values[i] = new(sql.NullString)
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldTempUnschedulableUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
@@ -239,6 +245,13 @@ func (_m *Account) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Concurrency = int(value.Int64)
}
case account.FieldLoadFactor:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field load_factor", values[i])
} else if value.Valid {
_m.LoadFactor = new(int)
*_m.LoadFactor = int(value.Int64)
}
case account.FieldPriority:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field priority", values[i])
@@ -311,6 +324,20 @@ func (_m *Account) assignValues(columns []string, values []any) error {
_m.OverloadUntil = new(time.Time)
*_m.OverloadUntil = value.Time
}
case account.FieldTempUnschedulableUntil:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field temp_unschedulable_until", values[i])
} else if value.Valid {
_m.TempUnschedulableUntil = new(time.Time)
*_m.TempUnschedulableUntil = value.Time
}
case account.FieldTempUnschedulableReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field temp_unschedulable_reason", values[i])
} else if value.Valid {
_m.TempUnschedulableReason = new(string)
*_m.TempUnschedulableReason = value.String
}
case account.FieldSessionWindowStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field session_window_start", values[i])
@@ -427,6 +454,11 @@ func (_m *Account) String() string {
builder.WriteString("concurrency=")
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
builder.WriteString(", ")
if v := _m.LoadFactor; v != nil {
builder.WriteString("load_factor=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("priority=")
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
builder.WriteString(", ")
@@ -472,6 +504,16 @@ func (_m *Account) String() string {
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.TempUnschedulableUntil; v != nil {
builder.WriteString("temp_unschedulable_until=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.TempUnschedulableReason; v != nil {
builder.WriteString("temp_unschedulable_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.SessionWindowStart; v != nil {
builder.WriteString("session_window_start=")
builder.WriteString(v.Format(time.ANSIC))

View File

@@ -37,6 +37,8 @@ const (
FieldProxyID = "proxy_id"
// FieldConcurrency holds the string denoting the concurrency field in the database.
FieldConcurrency = "concurrency"
// FieldLoadFactor holds the string denoting the load_factor field in the database.
FieldLoadFactor = "load_factor"
// FieldPriority holds the string denoting the priority field in the database.
FieldPriority = "priority"
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
@@ -59,6 +61,10 @@ const (
FieldRateLimitResetAt = "rate_limit_reset_at"
// FieldOverloadUntil holds the string denoting the overload_until field in the database.
FieldOverloadUntil = "overload_until"
// FieldTempUnschedulableUntil holds the string denoting the temp_unschedulable_until field in the database.
FieldTempUnschedulableUntil = "temp_unschedulable_until"
// FieldTempUnschedulableReason holds the string denoting the temp_unschedulable_reason field in the database.
FieldTempUnschedulableReason = "temp_unschedulable_reason"
// FieldSessionWindowStart holds the string denoting the session_window_start field in the database.
FieldSessionWindowStart = "session_window_start"
// FieldSessionWindowEnd holds the string denoting the session_window_end field in the database.
@@ -117,6 +123,7 @@ var Columns = []string{
FieldExtra,
FieldProxyID,
FieldConcurrency,
FieldLoadFactor,
FieldPriority,
FieldRateMultiplier,
FieldStatus,
@@ -128,6 +135,8 @@ var Columns = []string{
FieldRateLimitedAt,
FieldRateLimitResetAt,
FieldOverloadUntil,
FieldTempUnschedulableUntil,
FieldTempUnschedulableReason,
FieldSessionWindowStart,
FieldSessionWindowEnd,
FieldSessionWindowStatus,
@@ -244,6 +253,11 @@ func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
}
// ByLoadFactor orders the results by the load_factor field.
func ByLoadFactor(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLoadFactor, opts...).ToFunc()
}
// ByPriority orders the results by the priority field.
func ByPriority(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPriority, opts...).ToFunc()
@@ -299,6 +313,16 @@ func ByOverloadUntil(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOverloadUntil, opts...).ToFunc()
}
// ByTempUnschedulableUntil orders the results by the temp_unschedulable_until field.
func ByTempUnschedulableUntil(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTempUnschedulableUntil, opts...).ToFunc()
}
// ByTempUnschedulableReason orders the results by the temp_unschedulable_reason field.
func ByTempUnschedulableReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTempUnschedulableReason, opts...).ToFunc()
}
// BySessionWindowStart orders the results by the session_window_start field.
func BySessionWindowStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSessionWindowStart, opts...).ToFunc()

View File

@@ -100,6 +100,11 @@ func Concurrency(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
}
// LoadFactor applies equality check predicate on the "load_factor" field. It's identical to LoadFactorEQ.
func LoadFactor(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
}
// Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
func Priority(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldPriority, v))
@@ -155,6 +160,16 @@ func OverloadUntil(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldOverloadUntil, v))
}
// TempUnschedulableUntil applies equality check predicate on the "temp_unschedulable_until" field. It's identical to TempUnschedulableUntilEQ.
func TempUnschedulableUntil(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableReason applies equality check predicate on the "temp_unschedulable_reason" field. It's identical to TempUnschedulableReasonEQ.
func TempUnschedulableReason(v string) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableReason, v))
}
// SessionWindowStart applies equality check predicate on the "session_window_start" field. It's identical to SessionWindowStartEQ.
func SessionWindowStart(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))
@@ -640,6 +655,56 @@ func ConcurrencyLTE(v int) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldConcurrency, v))
}
// LoadFactorEQ applies the EQ predicate on the "load_factor" field.
func LoadFactorEQ(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
}
// LoadFactorNEQ applies the NEQ predicate on the "load_factor" field.
func LoadFactorNEQ(v int) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldLoadFactor, v))
}
// LoadFactorIn applies the In predicate on the "load_factor" field.
func LoadFactorIn(vs ...int) predicate.Account {
return predicate.Account(sql.FieldIn(FieldLoadFactor, vs...))
}
// LoadFactorNotIn applies the NotIn predicate on the "load_factor" field.
func LoadFactorNotIn(vs ...int) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldLoadFactor, vs...))
}
// LoadFactorGT applies the GT predicate on the "load_factor" field.
func LoadFactorGT(v int) predicate.Account {
return predicate.Account(sql.FieldGT(FieldLoadFactor, v))
}
// LoadFactorGTE applies the GTE predicate on the "load_factor" field.
func LoadFactorGTE(v int) predicate.Account {
return predicate.Account(sql.FieldGTE(FieldLoadFactor, v))
}
// LoadFactorLT applies the LT predicate on the "load_factor" field.
func LoadFactorLT(v int) predicate.Account {
return predicate.Account(sql.FieldLT(FieldLoadFactor, v))
}
// LoadFactorLTE applies the LTE predicate on the "load_factor" field.
func LoadFactorLTE(v int) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldLoadFactor, v))
}
// LoadFactorIsNil applies the IsNil predicate on the "load_factor" field.
func LoadFactorIsNil() predicate.Account {
return predicate.Account(sql.FieldIsNull(FieldLoadFactor))
}
// LoadFactorNotNil applies the NotNil predicate on the "load_factor" field.
func LoadFactorNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldLoadFactor))
}
// PriorityEQ applies the EQ predicate on the "priority" field.
func PriorityEQ(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldPriority, v))
@@ -1130,6 +1195,131 @@ func OverloadUntilNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldOverloadUntil))
}
// TempUnschedulableUntilEQ applies the EQ predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilNEQ applies the NEQ predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilNEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilIn applies the In predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilIn(vs ...time.Time) predicate.Account {
return predicate.Account(sql.FieldIn(FieldTempUnschedulableUntil, vs...))
}
// TempUnschedulableUntilNotIn applies the NotIn predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilNotIn(vs ...time.Time) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldTempUnschedulableUntil, vs...))
}
// TempUnschedulableUntilGT applies the GT predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilGT(v time.Time) predicate.Account {
return predicate.Account(sql.FieldGT(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilGTE applies the GTE predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilGTE(v time.Time) predicate.Account {
return predicate.Account(sql.FieldGTE(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilLT applies the LT predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilLT(v time.Time) predicate.Account {
return predicate.Account(sql.FieldLT(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilLTE applies the LTE predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilLTE(v time.Time) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldTempUnschedulableUntil, v))
}
// TempUnschedulableUntilIsNil applies the IsNil predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilIsNil() predicate.Account {
return predicate.Account(sql.FieldIsNull(FieldTempUnschedulableUntil))
}
// TempUnschedulableUntilNotNil applies the NotNil predicate on the "temp_unschedulable_until" field.
func TempUnschedulableUntilNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldTempUnschedulableUntil))
}
// TempUnschedulableReasonEQ applies the EQ predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonEQ(v string) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonNEQ applies the NEQ predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonNEQ(v string) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonIn applies the In predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonIn(vs ...string) predicate.Account {
return predicate.Account(sql.FieldIn(FieldTempUnschedulableReason, vs...))
}
// TempUnschedulableReasonNotIn applies the NotIn predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonNotIn(vs ...string) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldTempUnschedulableReason, vs...))
}
// TempUnschedulableReasonGT applies the GT predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonGT(v string) predicate.Account {
return predicate.Account(sql.FieldGT(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonGTE applies the GTE predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonGTE(v string) predicate.Account {
return predicate.Account(sql.FieldGTE(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonLT applies the LT predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonLT(v string) predicate.Account {
return predicate.Account(sql.FieldLT(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonLTE applies the LTE predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonLTE(v string) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonContains applies the Contains predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonContains(v string) predicate.Account {
return predicate.Account(sql.FieldContains(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonHasPrefix applies the HasPrefix predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonHasPrefix(v string) predicate.Account {
return predicate.Account(sql.FieldHasPrefix(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonHasSuffix applies the HasSuffix predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonHasSuffix(v string) predicate.Account {
return predicate.Account(sql.FieldHasSuffix(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonIsNil applies the IsNil predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonIsNil() predicate.Account {
return predicate.Account(sql.FieldIsNull(FieldTempUnschedulableReason))
}
// TempUnschedulableReasonNotNil applies the NotNil predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldTempUnschedulableReason))
}
// TempUnschedulableReasonEqualFold applies the EqualFold predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonEqualFold(v string) predicate.Account {
return predicate.Account(sql.FieldEqualFold(FieldTempUnschedulableReason, v))
}
// TempUnschedulableReasonContainsFold applies the ContainsFold predicate on the "temp_unschedulable_reason" field.
func TempUnschedulableReasonContainsFold(v string) predicate.Account {
return predicate.Account(sql.FieldContainsFold(FieldTempUnschedulableReason, v))
}
// SessionWindowStartEQ applies the EQ predicate on the "session_window_start" field.
func SessionWindowStartEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))

View File

@@ -139,6 +139,20 @@ func (_c *AccountCreate) SetNillableConcurrency(v *int) *AccountCreate {
return _c
}
// SetLoadFactor sets the "load_factor" field.
func (_c *AccountCreate) SetLoadFactor(v int) *AccountCreate {
_c.mutation.SetLoadFactor(v)
return _c
}
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
func (_c *AccountCreate) SetNillableLoadFactor(v *int) *AccountCreate {
if v != nil {
_c.SetLoadFactor(*v)
}
return _c
}
// SetPriority sets the "priority" field.
func (_c *AccountCreate) SetPriority(v int) *AccountCreate {
_c.mutation.SetPriority(v)
@@ -293,6 +307,34 @@ func (_c *AccountCreate) SetNillableOverloadUntil(v *time.Time) *AccountCreate {
return _c
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (_c *AccountCreate) SetTempUnschedulableUntil(v time.Time) *AccountCreate {
_c.mutation.SetTempUnschedulableUntil(v)
return _c
}
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
func (_c *AccountCreate) SetNillableTempUnschedulableUntil(v *time.Time) *AccountCreate {
if v != nil {
_c.SetTempUnschedulableUntil(*v)
}
return _c
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (_c *AccountCreate) SetTempUnschedulableReason(v string) *AccountCreate {
_c.mutation.SetTempUnschedulableReason(v)
return _c
}
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
func (_c *AccountCreate) SetNillableTempUnschedulableReason(v *string) *AccountCreate {
if v != nil {
_c.SetTempUnschedulableReason(*v)
}
return _c
}
// SetSessionWindowStart sets the "session_window_start" field.
func (_c *AccountCreate) SetSessionWindowStart(v time.Time) *AccountCreate {
_c.mutation.SetSessionWindowStart(v)
@@ -595,6 +637,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
_node.Concurrency = value
}
if value, ok := _c.mutation.LoadFactor(); ok {
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
_node.LoadFactor = &value
}
if value, ok := _c.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value)
_node.Priority = value
@@ -639,6 +685,14 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldOverloadUntil, field.TypeTime, value)
_node.OverloadUntil = &value
}
if value, ok := _c.mutation.TempUnschedulableUntil(); ok {
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
_node.TempUnschedulableUntil = &value
}
if value, ok := _c.mutation.TempUnschedulableReason(); ok {
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
_node.TempUnschedulableReason = &value
}
if value, ok := _c.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
_node.SessionWindowStart = &value
@@ -900,6 +954,30 @@ func (u *AccountUpsert) AddConcurrency(v int) *AccountUpsert {
return u
}
// SetLoadFactor sets the "load_factor" field.
func (u *AccountUpsert) SetLoadFactor(v int) *AccountUpsert {
u.Set(account.FieldLoadFactor, v)
return u
}
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
func (u *AccountUpsert) UpdateLoadFactor() *AccountUpsert {
u.SetExcluded(account.FieldLoadFactor)
return u
}
// AddLoadFactor adds v to the "load_factor" field.
func (u *AccountUpsert) AddLoadFactor(v int) *AccountUpsert {
u.Add(account.FieldLoadFactor, v)
return u
}
// ClearLoadFactor clears the value of the "load_factor" field.
func (u *AccountUpsert) ClearLoadFactor() *AccountUpsert {
u.SetNull(account.FieldLoadFactor)
return u
}
// SetPriority sets the "priority" field.
func (u *AccountUpsert) SetPriority(v int) *AccountUpsert {
u.Set(account.FieldPriority, v)
@@ -1080,6 +1158,42 @@ func (u *AccountUpsert) ClearOverloadUntil() *AccountUpsert {
return u
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (u *AccountUpsert) SetTempUnschedulableUntil(v time.Time) *AccountUpsert {
u.Set(account.FieldTempUnschedulableUntil, v)
return u
}
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
func (u *AccountUpsert) UpdateTempUnschedulableUntil() *AccountUpsert {
u.SetExcluded(account.FieldTempUnschedulableUntil)
return u
}
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
func (u *AccountUpsert) ClearTempUnschedulableUntil() *AccountUpsert {
u.SetNull(account.FieldTempUnschedulableUntil)
return u
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (u *AccountUpsert) SetTempUnschedulableReason(v string) *AccountUpsert {
u.Set(account.FieldTempUnschedulableReason, v)
return u
}
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
func (u *AccountUpsert) UpdateTempUnschedulableReason() *AccountUpsert {
u.SetExcluded(account.FieldTempUnschedulableReason)
return u
}
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
func (u *AccountUpsert) ClearTempUnschedulableReason() *AccountUpsert {
u.SetNull(account.FieldTempUnschedulableReason)
return u
}
// SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsert) SetSessionWindowStart(v time.Time) *AccountUpsert {
u.Set(account.FieldSessionWindowStart, v)
@@ -1347,6 +1461,34 @@ func (u *AccountUpsertOne) UpdateConcurrency() *AccountUpsertOne {
})
}
// SetLoadFactor sets the "load_factor" field.
func (u *AccountUpsertOne) SetLoadFactor(v int) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetLoadFactor(v)
})
}
// AddLoadFactor adds v to the "load_factor" field.
func (u *AccountUpsertOne) AddLoadFactor(v int) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.AddLoadFactor(v)
})
}
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateLoadFactor() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateLoadFactor()
})
}
// ClearLoadFactor clears the value of the "load_factor" field.
func (u *AccountUpsertOne) ClearLoadFactor() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.ClearLoadFactor()
})
}
// SetPriority sets the "priority" field.
func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
@@ -1557,6 +1699,48 @@ func (u *AccountUpsertOne) ClearOverloadUntil() *AccountUpsertOne {
})
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (u *AccountUpsertOne) SetTempUnschedulableUntil(v time.Time) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetTempUnschedulableUntil(v)
})
}
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateTempUnschedulableUntil() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateTempUnschedulableUntil()
})
}
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
func (u *AccountUpsertOne) ClearTempUnschedulableUntil() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.ClearTempUnschedulableUntil()
})
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (u *AccountUpsertOne) SetTempUnschedulableReason(v string) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetTempUnschedulableReason(v)
})
}
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateTempUnschedulableReason() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateTempUnschedulableReason()
})
}
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
func (u *AccountUpsertOne) ClearTempUnschedulableReason() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.ClearTempUnschedulableReason()
})
}
// SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsertOne) SetSessionWindowStart(v time.Time) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
@@ -1999,6 +2183,34 @@ func (u *AccountUpsertBulk) UpdateConcurrency() *AccountUpsertBulk {
})
}
// SetLoadFactor sets the "load_factor" field.
func (u *AccountUpsertBulk) SetLoadFactor(v int) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetLoadFactor(v)
})
}
// AddLoadFactor adds v to the "load_factor" field.
func (u *AccountUpsertBulk) AddLoadFactor(v int) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.AddLoadFactor(v)
})
}
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateLoadFactor() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateLoadFactor()
})
}
// ClearLoadFactor clears the value of the "load_factor" field.
func (u *AccountUpsertBulk) ClearLoadFactor() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.ClearLoadFactor()
})
}
// SetPriority sets the "priority" field.
func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
@@ -2209,6 +2421,48 @@ func (u *AccountUpsertBulk) ClearOverloadUntil() *AccountUpsertBulk {
})
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (u *AccountUpsertBulk) SetTempUnschedulableUntil(v time.Time) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetTempUnschedulableUntil(v)
})
}
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateTempUnschedulableUntil() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateTempUnschedulableUntil()
})
}
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
func (u *AccountUpsertBulk) ClearTempUnschedulableUntil() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.ClearTempUnschedulableUntil()
})
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (u *AccountUpsertBulk) SetTempUnschedulableReason(v string) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetTempUnschedulableReason(v)
})
}
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateTempUnschedulableReason() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateTempUnschedulableReason()
})
}
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
func (u *AccountUpsertBulk) ClearTempUnschedulableReason() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.ClearTempUnschedulableReason()
})
}
// SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsertBulk) SetSessionWindowStart(v time.Time) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {

View File

@@ -172,6 +172,33 @@ func (_u *AccountUpdate) AddConcurrency(v int) *AccountUpdate {
return _u
}
// SetLoadFactor sets the "load_factor" field.
func (_u *AccountUpdate) SetLoadFactor(v int) *AccountUpdate {
_u.mutation.ResetLoadFactor()
_u.mutation.SetLoadFactor(v)
return _u
}
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableLoadFactor(v *int) *AccountUpdate {
if v != nil {
_u.SetLoadFactor(*v)
}
return _u
}
// AddLoadFactor adds value to the "load_factor" field.
func (_u *AccountUpdate) AddLoadFactor(v int) *AccountUpdate {
_u.mutation.AddLoadFactor(v)
return _u
}
// ClearLoadFactor clears the value of the "load_factor" field.
func (_u *AccountUpdate) ClearLoadFactor() *AccountUpdate {
_u.mutation.ClearLoadFactor()
return _u
}
// SetPriority sets the "priority" field.
func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate {
_u.mutation.ResetPriority()
@@ -376,6 +403,46 @@ func (_u *AccountUpdate) ClearOverloadUntil() *AccountUpdate {
return _u
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (_u *AccountUpdate) SetTempUnschedulableUntil(v time.Time) *AccountUpdate {
_u.mutation.SetTempUnschedulableUntil(v)
return _u
}
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableTempUnschedulableUntil(v *time.Time) *AccountUpdate {
if v != nil {
_u.SetTempUnschedulableUntil(*v)
}
return _u
}
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
func (_u *AccountUpdate) ClearTempUnschedulableUntil() *AccountUpdate {
_u.mutation.ClearTempUnschedulableUntil()
return _u
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (_u *AccountUpdate) SetTempUnschedulableReason(v string) *AccountUpdate {
_u.mutation.SetTempUnschedulableReason(v)
return _u
}
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableTempUnschedulableReason(v *string) *AccountUpdate {
if v != nil {
_u.SetTempUnschedulableReason(*v)
}
return _u
}
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
func (_u *AccountUpdate) ClearTempUnschedulableReason() *AccountUpdate {
_u.mutation.ClearTempUnschedulableReason()
return _u
}
// SetSessionWindowStart sets the "session_window_start" field.
func (_u *AccountUpdate) SetSessionWindowStart(v time.Time) *AccountUpdate {
_u.mutation.SetSessionWindowStart(v)
@@ -644,6 +711,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedConcurrency(); ok {
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
}
if value, ok := _u.mutation.LoadFactor(); ok {
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedLoadFactor(); ok {
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
}
if _u.mutation.LoadFactorCleared() {
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
}
if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value)
}
@@ -701,6 +777,18 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.OverloadUntilCleared() {
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime)
}
if value, ok := _u.mutation.TempUnschedulableUntil(); ok {
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
}
if _u.mutation.TempUnschedulableUntilCleared() {
_spec.ClearField(account.FieldTempUnschedulableUntil, field.TypeTime)
}
if value, ok := _u.mutation.TempUnschedulableReason(); ok {
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
}
if _u.mutation.TempUnschedulableReasonCleared() {
_spec.ClearField(account.FieldTempUnschedulableReason, field.TypeString)
}
if value, ok := _u.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
}
@@ -1011,6 +1099,33 @@ func (_u *AccountUpdateOne) AddConcurrency(v int) *AccountUpdateOne {
return _u
}
// SetLoadFactor sets the "load_factor" field.
func (_u *AccountUpdateOne) SetLoadFactor(v int) *AccountUpdateOne {
_u.mutation.ResetLoadFactor()
_u.mutation.SetLoadFactor(v)
return _u
}
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableLoadFactor(v *int) *AccountUpdateOne {
if v != nil {
_u.SetLoadFactor(*v)
}
return _u
}
// AddLoadFactor adds value to the "load_factor" field.
func (_u *AccountUpdateOne) AddLoadFactor(v int) *AccountUpdateOne {
_u.mutation.AddLoadFactor(v)
return _u
}
// ClearLoadFactor clears the value of the "load_factor" field.
func (_u *AccountUpdateOne) ClearLoadFactor() *AccountUpdateOne {
_u.mutation.ClearLoadFactor()
return _u
}
// SetPriority sets the "priority" field.
func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne {
_u.mutation.ResetPriority()
@@ -1215,6 +1330,46 @@ func (_u *AccountUpdateOne) ClearOverloadUntil() *AccountUpdateOne {
return _u
}
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
func (_u *AccountUpdateOne) SetTempUnschedulableUntil(v time.Time) *AccountUpdateOne {
_u.mutation.SetTempUnschedulableUntil(v)
return _u
}
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableTempUnschedulableUntil(v *time.Time) *AccountUpdateOne {
if v != nil {
_u.SetTempUnschedulableUntil(*v)
}
return _u
}
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
func (_u *AccountUpdateOne) ClearTempUnschedulableUntil() *AccountUpdateOne {
_u.mutation.ClearTempUnschedulableUntil()
return _u
}
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
func (_u *AccountUpdateOne) SetTempUnschedulableReason(v string) *AccountUpdateOne {
_u.mutation.SetTempUnschedulableReason(v)
return _u
}
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableTempUnschedulableReason(v *string) *AccountUpdateOne {
if v != nil {
_u.SetTempUnschedulableReason(*v)
}
return _u
}
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
func (_u *AccountUpdateOne) ClearTempUnschedulableReason() *AccountUpdateOne {
_u.mutation.ClearTempUnschedulableReason()
return _u
}
// SetSessionWindowStart sets the "session_window_start" field.
func (_u *AccountUpdateOne) SetSessionWindowStart(v time.Time) *AccountUpdateOne {
_u.mutation.SetSessionWindowStart(v)
@@ -1513,6 +1668,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if value, ok := _u.mutation.AddedConcurrency(); ok {
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
}
if value, ok := _u.mutation.LoadFactor(); ok {
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedLoadFactor(); ok {
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
}
if _u.mutation.LoadFactorCleared() {
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
}
if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value)
}
@@ -1570,6 +1734,18 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if _u.mutation.OverloadUntilCleared() {
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime)
}
if value, ok := _u.mutation.TempUnschedulableUntil(); ok {
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
}
if _u.mutation.TempUnschedulableUntilCleared() {
_spec.ClearField(account.FieldTempUnschedulableUntil, field.TypeTime)
}
if value, ok := _u.mutation.TempUnschedulableReason(); ok {
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
}
if _u.mutation.TempUnschedulableReasonCleared() {
_spec.ClearField(account.FieldTempUnschedulableReason, field.TypeString)
}
if value, ok := _u.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
}

View File

@@ -25,6 +25,8 @@ type Announcement struct {
Content string `json:"content,omitempty"`
// 状态: draft, active, archived
Status string `json:"status,omitempty"`
// 通知模式: silent(仅铃铛), popup(弹窗提醒)
NotifyMode string `json:"notify_mode,omitempty"`
// 展示条件JSON 规则)
Targeting domain.AnnouncementTargeting `json:"targeting,omitempty"`
// 开始展示时间(为空表示立即生效)
@@ -72,7 +74,7 @@ func (*Announcement) scanValues(columns []string) ([]any, error) {
values[i] = new([]byte)
case announcement.FieldID, announcement.FieldCreatedBy, announcement.FieldUpdatedBy:
values[i] = new(sql.NullInt64)
case announcement.FieldTitle, announcement.FieldContent, announcement.FieldStatus:
case announcement.FieldTitle, announcement.FieldContent, announcement.FieldStatus, announcement.FieldNotifyMode:
values[i] = new(sql.NullString)
case announcement.FieldStartsAt, announcement.FieldEndsAt, announcement.FieldCreatedAt, announcement.FieldUpdatedAt:
values[i] = new(sql.NullTime)
@@ -115,6 +117,12 @@ func (_m *Announcement) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Status = value.String
}
case announcement.FieldNotifyMode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field notify_mode", values[i])
} else if value.Valid {
_m.NotifyMode = value.String
}
case announcement.FieldTargeting:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field targeting", values[i])
@@ -213,6 +221,9 @@ func (_m *Announcement) String() string {
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
builder.WriteString("notify_mode=")
builder.WriteString(_m.NotifyMode)
builder.WriteString(", ")
builder.WriteString("targeting=")
builder.WriteString(fmt.Sprintf("%v", _m.Targeting))
builder.WriteString(", ")

View File

@@ -20,6 +20,8 @@ const (
FieldContent = "content"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldNotifyMode holds the string denoting the notify_mode field in the database.
FieldNotifyMode = "notify_mode"
// FieldTargeting holds the string denoting the targeting field in the database.
FieldTargeting = "targeting"
// FieldStartsAt holds the string denoting the starts_at field in the database.
@@ -53,6 +55,7 @@ var Columns = []string{
FieldTitle,
FieldContent,
FieldStatus,
FieldNotifyMode,
FieldTargeting,
FieldStartsAt,
FieldEndsAt,
@@ -81,6 +84,10 @@ var (
DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error
// DefaultNotifyMode holds the default value on creation for the "notify_mode" field.
DefaultNotifyMode string
// NotifyModeValidator is a validator for the "notify_mode" field. It is called by the builders before save.
NotifyModeValidator 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.
@@ -112,6 +119,11 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByNotifyMode orders the results by the notify_mode field.
func ByNotifyMode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldNotifyMode, opts...).ToFunc()
}
// ByStartsAt orders the results by the starts_at field.
func ByStartsAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStartsAt, opts...).ToFunc()

View File

@@ -70,6 +70,11 @@ func Status(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
}
// NotifyMode applies equality check predicate on the "notify_mode" field. It's identical to NotifyModeEQ.
func NotifyMode(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldNotifyMode, v))
}
// StartsAt applies equality check predicate on the "starts_at" field. It's identical to StartsAtEQ.
func StartsAt(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
@@ -295,6 +300,71 @@ func StatusContainsFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContainsFold(FieldStatus, v))
}
// NotifyModeEQ applies the EQ predicate on the "notify_mode" field.
func NotifyModeEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldNotifyMode, v))
}
// NotifyModeNEQ applies the NEQ predicate on the "notify_mode" field.
func NotifyModeNEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldNotifyMode, v))
}
// NotifyModeIn applies the In predicate on the "notify_mode" field.
func NotifyModeIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldNotifyMode, vs...))
}
// NotifyModeNotIn applies the NotIn predicate on the "notify_mode" field.
func NotifyModeNotIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldNotifyMode, vs...))
}
// NotifyModeGT applies the GT predicate on the "notify_mode" field.
func NotifyModeGT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldNotifyMode, v))
}
// NotifyModeGTE applies the GTE predicate on the "notify_mode" field.
func NotifyModeGTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldNotifyMode, v))
}
// NotifyModeLT applies the LT predicate on the "notify_mode" field.
func NotifyModeLT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldNotifyMode, v))
}
// NotifyModeLTE applies the LTE predicate on the "notify_mode" field.
func NotifyModeLTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldNotifyMode, v))
}
// NotifyModeContains applies the Contains predicate on the "notify_mode" field.
func NotifyModeContains(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContains(FieldNotifyMode, v))
}
// NotifyModeHasPrefix applies the HasPrefix predicate on the "notify_mode" field.
func NotifyModeHasPrefix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasPrefix(FieldNotifyMode, v))
}
// NotifyModeHasSuffix applies the HasSuffix predicate on the "notify_mode" field.
func NotifyModeHasSuffix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasSuffix(FieldNotifyMode, v))
}
// NotifyModeEqualFold applies the EqualFold predicate on the "notify_mode" field.
func NotifyModeEqualFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEqualFold(FieldNotifyMode, v))
}
// NotifyModeContainsFold applies the ContainsFold predicate on the "notify_mode" field.
func NotifyModeContainsFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContainsFold(FieldNotifyMode, v))
}
// TargetingIsNil applies the IsNil predicate on the "targeting" field.
func TargetingIsNil() predicate.Announcement {
return predicate.Announcement(sql.FieldIsNull(FieldTargeting))

View File

@@ -50,6 +50,20 @@ func (_c *AnnouncementCreate) SetNillableStatus(v *string) *AnnouncementCreate {
return _c
}
// SetNotifyMode sets the "notify_mode" field.
func (_c *AnnouncementCreate) SetNotifyMode(v string) *AnnouncementCreate {
_c.mutation.SetNotifyMode(v)
return _c
}
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
func (_c *AnnouncementCreate) SetNillableNotifyMode(v *string) *AnnouncementCreate {
if v != nil {
_c.SetNotifyMode(*v)
}
return _c
}
// SetTargeting sets the "targeting" field.
func (_c *AnnouncementCreate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementCreate {
_c.mutation.SetTargeting(v)
@@ -202,6 +216,10 @@ func (_c *AnnouncementCreate) defaults() {
v := announcement.DefaultStatus
_c.mutation.SetStatus(v)
}
if _, ok := _c.mutation.NotifyMode(); !ok {
v := announcement.DefaultNotifyMode
_c.mutation.SetNotifyMode(v)
}
if _, ok := _c.mutation.CreatedAt(); !ok {
v := announcement.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
@@ -238,6 +256,14 @@ func (_c *AnnouncementCreate) check() error {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
}
}
if _, ok := _c.mutation.NotifyMode(); !ok {
return &ValidationError{Name: "notify_mode", err: errors.New(`ent: missing required field "Announcement.notify_mode"`)}
}
if v, ok := _c.mutation.NotifyMode(); ok {
if err := announcement.NotifyModeValidator(v); err != nil {
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Announcement.created_at"`)}
}
@@ -283,6 +309,10 @@ func (_c *AnnouncementCreate) createSpec() (*Announcement, *sqlgraph.CreateSpec)
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
_node.Status = value
}
if value, ok := _c.mutation.NotifyMode(); ok {
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
_node.NotifyMode = value
}
if value, ok := _c.mutation.Targeting(); ok {
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
_node.Targeting = value
@@ -415,6 +445,18 @@ func (u *AnnouncementUpsert) UpdateStatus() *AnnouncementUpsert {
return u
}
// SetNotifyMode sets the "notify_mode" field.
func (u *AnnouncementUpsert) SetNotifyMode(v string) *AnnouncementUpsert {
u.Set(announcement.FieldNotifyMode, v)
return u
}
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
func (u *AnnouncementUpsert) UpdateNotifyMode() *AnnouncementUpsert {
u.SetExcluded(announcement.FieldNotifyMode)
return u
}
// SetTargeting sets the "targeting" field.
func (u *AnnouncementUpsert) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsert {
u.Set(announcement.FieldTargeting, v)
@@ -616,6 +658,20 @@ func (u *AnnouncementUpsertOne) UpdateStatus() *AnnouncementUpsertOne {
})
}
// SetNotifyMode sets the "notify_mode" field.
func (u *AnnouncementUpsertOne) SetNotifyMode(v string) *AnnouncementUpsertOne {
return u.Update(func(s *AnnouncementUpsert) {
s.SetNotifyMode(v)
})
}
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
func (u *AnnouncementUpsertOne) UpdateNotifyMode() *AnnouncementUpsertOne {
return u.Update(func(s *AnnouncementUpsert) {
s.UpdateNotifyMode()
})
}
// SetTargeting sets the "targeting" field.
func (u *AnnouncementUpsertOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertOne {
return u.Update(func(s *AnnouncementUpsert) {
@@ -1002,6 +1058,20 @@ func (u *AnnouncementUpsertBulk) UpdateStatus() *AnnouncementUpsertBulk {
})
}
// SetNotifyMode sets the "notify_mode" field.
func (u *AnnouncementUpsertBulk) SetNotifyMode(v string) *AnnouncementUpsertBulk {
return u.Update(func(s *AnnouncementUpsert) {
s.SetNotifyMode(v)
})
}
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
func (u *AnnouncementUpsertBulk) UpdateNotifyMode() *AnnouncementUpsertBulk {
return u.Update(func(s *AnnouncementUpsert) {
s.UpdateNotifyMode()
})
}
// SetTargeting sets the "targeting" field.
func (u *AnnouncementUpsertBulk) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertBulk {
return u.Update(func(s *AnnouncementUpsert) {

View File

@@ -72,6 +72,20 @@ func (_u *AnnouncementUpdate) SetNillableStatus(v *string) *AnnouncementUpdate {
return _u
}
// SetNotifyMode sets the "notify_mode" field.
func (_u *AnnouncementUpdate) SetNotifyMode(v string) *AnnouncementUpdate {
_u.mutation.SetNotifyMode(v)
return _u
}
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableNotifyMode(v *string) *AnnouncementUpdate {
if v != nil {
_u.SetNotifyMode(*v)
}
return _u
}
// SetTargeting sets the "targeting" field.
func (_u *AnnouncementUpdate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdate {
_u.mutation.SetTargeting(v)
@@ -286,6 +300,11 @@ func (_u *AnnouncementUpdate) check() error {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
}
}
if v, ok := _u.mutation.NotifyMode(); ok {
if err := announcement.NotifyModeValidator(v); err != nil {
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
}
}
return nil
}
@@ -310,6 +329,9 @@ func (_u *AnnouncementUpdate) sqlSave(ctx context.Context) (_node int, err error
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.NotifyMode(); ok {
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
}
if value, ok := _u.mutation.Targeting(); ok {
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
}
@@ -456,6 +478,20 @@ func (_u *AnnouncementUpdateOne) SetNillableStatus(v *string) *AnnouncementUpdat
return _u
}
// SetNotifyMode sets the "notify_mode" field.
func (_u *AnnouncementUpdateOne) SetNotifyMode(v string) *AnnouncementUpdateOne {
_u.mutation.SetNotifyMode(v)
return _u
}
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableNotifyMode(v *string) *AnnouncementUpdateOne {
if v != nil {
_u.SetNotifyMode(*v)
}
return _u
}
// SetTargeting sets the "targeting" field.
func (_u *AnnouncementUpdateOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdateOne {
_u.mutation.SetTargeting(v)
@@ -683,6 +719,11 @@ func (_u *AnnouncementUpdateOne) check() error {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
}
}
if v, ok := _u.mutation.NotifyMode(); ok {
if err := announcement.NotifyModeValidator(v); err != nil {
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
}
}
return nil
}
@@ -724,6 +765,9 @@ func (_u *AnnouncementUpdateOne) sqlSave(ctx context.Context) (_node *Announceme
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.NotifyMode(); ok {
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
}
if value, ok := _u.mutation.Targeting(); ok {
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
}

View File

@@ -36,6 +36,8 @@ type APIKey struct {
GroupID *int64 `json:"group_id,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// Last usage time of this API key
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
// Allowed IPs/CIDRs, e.g. ["192.168.1.100", "10.0.0.0/8"]
IPWhitelist []string `json:"ip_whitelist,omitempty"`
// Blocked IPs/CIDRs
@@ -46,6 +48,24 @@ type APIKey struct {
QuotaUsed float64 `json:"quota_used,omitempty"`
// Expiration time for this API key (null = never expires)
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// Rate limit in USD per 5 hours (0 = unlimited)
RateLimit5h float64 `json:"rate_limit_5h,omitempty"`
// Rate limit in USD per day (0 = unlimited)
RateLimit1d float64 `json:"rate_limit_1d,omitempty"`
// Rate limit in USD per 7 days (0 = unlimited)
RateLimit7d float64 `json:"rate_limit_7d,omitempty"`
// Used amount in USD for the current 5h window
Usage5h float64 `json:"usage_5h,omitempty"`
// Used amount in USD for the current 1d window
Usage1d float64 `json:"usage_1d,omitempty"`
// Used amount in USD for the current 7d window
Usage7d float64 `json:"usage_7d,omitempty"`
// Start time of the current 5h rate limit window
Window5hStart *time.Time `json:"window_5h_start,omitempty"`
// Start time of the current 1d rate limit window
Window1dStart *time.Time `json:"window_1d_start,omitempty"`
// Start time of the current 7d rate limit window
Window7dStart *time.Time `json:"window_7d_start,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the APIKeyQuery when eager-loading is set.
Edges APIKeyEdges `json:"edges"`
@@ -103,13 +123,13 @@ func (*APIKey) scanValues(columns []string) ([]any, error) {
switch columns[i] {
case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist:
values[i] = new([]byte)
case apikey.FieldQuota, apikey.FieldQuotaUsed:
case apikey.FieldQuota, apikey.FieldQuotaUsed, apikey.FieldRateLimit5h, apikey.FieldRateLimit1d, apikey.FieldRateLimit7d, apikey.FieldUsage5h, apikey.FieldUsage1d, apikey.FieldUsage7d:
values[i] = new(sql.NullFloat64)
case apikey.FieldID, apikey.FieldUserID, apikey.FieldGroupID:
values[i] = new(sql.NullInt64)
case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus:
values[i] = new(sql.NullString)
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldExpiresAt:
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt, apikey.FieldWindow5hStart, apikey.FieldWindow1dStart, apikey.FieldWindow7dStart:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
@@ -182,6 +202,13 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.Status = value.String
}
case apikey.FieldLastUsedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field last_used_at", values[i])
} else if value.Valid {
_m.LastUsedAt = new(time.Time)
*_m.LastUsedAt = value.Time
}
case apikey.FieldIPWhitelist:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field ip_whitelist", values[i])
@@ -217,6 +244,63 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
_m.ExpiresAt = new(time.Time)
*_m.ExpiresAt = value.Time
}
case apikey.FieldRateLimit5h:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_5h", values[i])
} else if value.Valid {
_m.RateLimit5h = value.Float64
}
case apikey.FieldRateLimit1d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_1d", values[i])
} else if value.Valid {
_m.RateLimit1d = value.Float64
}
case apikey.FieldRateLimit7d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_7d", values[i])
} else if value.Valid {
_m.RateLimit7d = value.Float64
}
case apikey.FieldUsage5h:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_5h", values[i])
} else if value.Valid {
_m.Usage5h = value.Float64
}
case apikey.FieldUsage1d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_1d", values[i])
} else if value.Valid {
_m.Usage1d = value.Float64
}
case apikey.FieldUsage7d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_7d", values[i])
} else if value.Valid {
_m.Usage7d = value.Float64
}
case apikey.FieldWindow5hStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_5h_start", values[i])
} else if value.Valid {
_m.Window5hStart = new(time.Time)
*_m.Window5hStart = value.Time
}
case apikey.FieldWindow1dStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_1d_start", values[i])
} else if value.Valid {
_m.Window1dStart = new(time.Time)
*_m.Window1dStart = value.Time
}
case apikey.FieldWindow7dStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_7d_start", values[i])
} else if value.Valid {
_m.Window7dStart = new(time.Time)
*_m.Window7dStart = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
@@ -296,6 +380,11 @@ func (_m *APIKey) String() string {
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
if v := _m.LastUsedAt; v != nil {
builder.WriteString("last_used_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("ip_whitelist=")
builder.WriteString(fmt.Sprintf("%v", _m.IPWhitelist))
builder.WriteString(", ")
@@ -312,6 +401,39 @@ func (_m *APIKey) String() string {
builder.WriteString("expires_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("rate_limit_5h=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit5h))
builder.WriteString(", ")
builder.WriteString("rate_limit_1d=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit1d))
builder.WriteString(", ")
builder.WriteString("rate_limit_7d=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit7d))
builder.WriteString(", ")
builder.WriteString("usage_5h=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage5h))
builder.WriteString(", ")
builder.WriteString("usage_1d=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage1d))
builder.WriteString(", ")
builder.WriteString("usage_7d=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage7d))
builder.WriteString(", ")
if v := _m.Window5hStart; v != nil {
builder.WriteString("window_5h_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.Window1dStart; v != nil {
builder.WriteString("window_1d_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.Window7dStart; v != nil {
builder.WriteString("window_7d_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteByte(')')
return builder.String()
}

View File

@@ -31,6 +31,8 @@ const (
FieldGroupID = "group_id"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldLastUsedAt holds the string denoting the last_used_at field in the database.
FieldLastUsedAt = "last_used_at"
// FieldIPWhitelist holds the string denoting the ip_whitelist field in the database.
FieldIPWhitelist = "ip_whitelist"
// FieldIPBlacklist holds the string denoting the ip_blacklist field in the database.
@@ -41,6 +43,24 @@ const (
FieldQuotaUsed = "quota_used"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// FieldRateLimit5h holds the string denoting the rate_limit_5h field in the database.
FieldRateLimit5h = "rate_limit_5h"
// FieldRateLimit1d holds the string denoting the rate_limit_1d field in the database.
FieldRateLimit1d = "rate_limit_1d"
// FieldRateLimit7d holds the string denoting the rate_limit_7d field in the database.
FieldRateLimit7d = "rate_limit_7d"
// FieldUsage5h holds the string denoting the usage_5h field in the database.
FieldUsage5h = "usage_5h"
// FieldUsage1d holds the string denoting the usage_1d field in the database.
FieldUsage1d = "usage_1d"
// FieldUsage7d holds the string denoting the usage_7d field in the database.
FieldUsage7d = "usage_7d"
// FieldWindow5hStart holds the string denoting the window_5h_start field in the database.
FieldWindow5hStart = "window_5h_start"
// FieldWindow1dStart holds the string denoting the window_1d_start field in the database.
FieldWindow1dStart = "window_1d_start"
// FieldWindow7dStart holds the string denoting the window_7d_start field in the database.
FieldWindow7dStart = "window_7d_start"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// EdgeGroup holds the string denoting the group edge name in mutations.
@@ -83,11 +103,21 @@ var Columns = []string{
FieldName,
FieldGroupID,
FieldStatus,
FieldLastUsedAt,
FieldIPWhitelist,
FieldIPBlacklist,
FieldQuota,
FieldQuotaUsed,
FieldExpiresAt,
FieldRateLimit5h,
FieldRateLimit1d,
FieldRateLimit7d,
FieldUsage5h,
FieldUsage1d,
FieldUsage7d,
FieldWindow5hStart,
FieldWindow1dStart,
FieldWindow7dStart,
}
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -126,6 +156,18 @@ var (
DefaultQuota float64
// DefaultQuotaUsed holds the default value on creation for the "quota_used" field.
DefaultQuotaUsed float64
// DefaultRateLimit5h holds the default value on creation for the "rate_limit_5h" field.
DefaultRateLimit5h float64
// DefaultRateLimit1d holds the default value on creation for the "rate_limit_1d" field.
DefaultRateLimit1d float64
// DefaultRateLimit7d holds the default value on creation for the "rate_limit_7d" field.
DefaultRateLimit7d float64
// DefaultUsage5h holds the default value on creation for the "usage_5h" field.
DefaultUsage5h float64
// DefaultUsage1d holds the default value on creation for the "usage_1d" field.
DefaultUsage1d float64
// DefaultUsage7d holds the default value on creation for the "usage_7d" field.
DefaultUsage7d float64
)
// OrderOption defines the ordering options for the APIKey queries.
@@ -176,6 +218,11 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByLastUsedAt orders the results by the last_used_at field.
func ByLastUsedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastUsedAt, opts...).ToFunc()
}
// ByQuota orders the results by the quota field.
func ByQuota(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQuota, opts...).ToFunc()
@@ -191,6 +238,51 @@ func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}
// ByRateLimit5h orders the results by the rate_limit_5h field.
func ByRateLimit5h(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit5h, opts...).ToFunc()
}
// ByRateLimit1d orders the results by the rate_limit_1d field.
func ByRateLimit1d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit1d, opts...).ToFunc()
}
// ByRateLimit7d orders the results by the rate_limit_7d field.
func ByRateLimit7d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit7d, opts...).ToFunc()
}
// ByUsage5h orders the results by the usage_5h field.
func ByUsage5h(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage5h, opts...).ToFunc()
}
// ByUsage1d orders the results by the usage_1d field.
func ByUsage1d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage1d, opts...).ToFunc()
}
// ByUsage7d orders the results by the usage_7d field.
func ByUsage7d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage7d, opts...).ToFunc()
}
// ByWindow5hStart orders the results by the window_5h_start field.
func ByWindow5hStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow5hStart, opts...).ToFunc()
}
// ByWindow1dStart orders the results by the window_1d_start field.
func ByWindow1dStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow1dStart, opts...).ToFunc()
}
// ByWindow7dStart orders the results by the window_7d_start field.
func ByWindow7dStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow7dStart, opts...).ToFunc()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {

View File

@@ -95,6 +95,11 @@ func Status(v string) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldStatus, v))
}
// LastUsedAt applies equality check predicate on the "last_used_at" field. It's identical to LastUsedAtEQ.
func LastUsedAt(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldLastUsedAt, v))
}
// Quota applies equality check predicate on the "quota" field. It's identical to QuotaEQ.
func Quota(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldQuota, v))
@@ -110,6 +115,51 @@ func ExpiresAt(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldExpiresAt, v))
}
// RateLimit5h applies equality check predicate on the "rate_limit_5h" field. It's identical to RateLimit5hEQ.
func RateLimit5h(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
}
// RateLimit1d applies equality check predicate on the "rate_limit_1d" field. It's identical to RateLimit1dEQ.
func RateLimit1d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
}
// RateLimit7d applies equality check predicate on the "rate_limit_7d" field. It's identical to RateLimit7dEQ.
func RateLimit7d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
}
// Usage5h applies equality check predicate on the "usage_5h" field. It's identical to Usage5hEQ.
func Usage5h(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
}
// Usage1d applies equality check predicate on the "usage_1d" field. It's identical to Usage1dEQ.
func Usage1d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
}
// Usage7d applies equality check predicate on the "usage_7d" field. It's identical to Usage7dEQ.
func Usage7d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
}
// Window5hStart applies equality check predicate on the "window_5h_start" field. It's identical to Window5hStartEQ.
func Window5hStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
}
// Window1dStart applies equality check predicate on the "window_1d_start" field. It's identical to Window1dStartEQ.
func Window1dStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
}
// Window7dStart applies equality check predicate on the "window_7d_start" field. It's identical to Window7dStartEQ.
func Window7dStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v))
@@ -485,6 +535,56 @@ func StatusContainsFold(v string) predicate.APIKey {
return predicate.APIKey(sql.FieldContainsFold(FieldStatus, v))
}
// LastUsedAtEQ applies the EQ predicate on the "last_used_at" field.
func LastUsedAtEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldLastUsedAt, v))
}
// LastUsedAtNEQ applies the NEQ predicate on the "last_used_at" field.
func LastUsedAtNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldLastUsedAt, v))
}
// LastUsedAtIn applies the In predicate on the "last_used_at" field.
func LastUsedAtIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldLastUsedAt, vs...))
}
// LastUsedAtNotIn applies the NotIn predicate on the "last_used_at" field.
func LastUsedAtNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldLastUsedAt, vs...))
}
// LastUsedAtGT applies the GT predicate on the "last_used_at" field.
func LastUsedAtGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldLastUsedAt, v))
}
// LastUsedAtGTE applies the GTE predicate on the "last_used_at" field.
func LastUsedAtGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldLastUsedAt, v))
}
// LastUsedAtLT applies the LT predicate on the "last_used_at" field.
func LastUsedAtLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldLastUsedAt, v))
}
// LastUsedAtLTE applies the LTE predicate on the "last_used_at" field.
func LastUsedAtLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldLastUsedAt, v))
}
// LastUsedAtIsNil applies the IsNil predicate on the "last_used_at" field.
func LastUsedAtIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldLastUsedAt))
}
// LastUsedAtNotNil applies the NotNil predicate on the "last_used_at" field.
func LastUsedAtNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldLastUsedAt))
}
// IPWhitelistIsNil applies the IsNil predicate on the "ip_whitelist" field.
func IPWhitelistIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldIPWhitelist))
@@ -635,6 +735,396 @@ func ExpiresAtNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldExpiresAt))
}
// RateLimit5hEQ applies the EQ predicate on the "rate_limit_5h" field.
func RateLimit5hEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
}
// RateLimit5hNEQ applies the NEQ predicate on the "rate_limit_5h" field.
func RateLimit5hNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit5h, v))
}
// RateLimit5hIn applies the In predicate on the "rate_limit_5h" field.
func RateLimit5hIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit5h, vs...))
}
// RateLimit5hNotIn applies the NotIn predicate on the "rate_limit_5h" field.
func RateLimit5hNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit5h, vs...))
}
// RateLimit5hGT applies the GT predicate on the "rate_limit_5h" field.
func RateLimit5hGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit5h, v))
}
// RateLimit5hGTE applies the GTE predicate on the "rate_limit_5h" field.
func RateLimit5hGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit5h, v))
}
// RateLimit5hLT applies the LT predicate on the "rate_limit_5h" field.
func RateLimit5hLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit5h, v))
}
// RateLimit5hLTE applies the LTE predicate on the "rate_limit_5h" field.
func RateLimit5hLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit5h, v))
}
// RateLimit1dEQ applies the EQ predicate on the "rate_limit_1d" field.
func RateLimit1dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
}
// RateLimit1dNEQ applies the NEQ predicate on the "rate_limit_1d" field.
func RateLimit1dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit1d, v))
}
// RateLimit1dIn applies the In predicate on the "rate_limit_1d" field.
func RateLimit1dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit1d, vs...))
}
// RateLimit1dNotIn applies the NotIn predicate on the "rate_limit_1d" field.
func RateLimit1dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit1d, vs...))
}
// RateLimit1dGT applies the GT predicate on the "rate_limit_1d" field.
func RateLimit1dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit1d, v))
}
// RateLimit1dGTE applies the GTE predicate on the "rate_limit_1d" field.
func RateLimit1dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit1d, v))
}
// RateLimit1dLT applies the LT predicate on the "rate_limit_1d" field.
func RateLimit1dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit1d, v))
}
// RateLimit1dLTE applies the LTE predicate on the "rate_limit_1d" field.
func RateLimit1dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit1d, v))
}
// RateLimit7dEQ applies the EQ predicate on the "rate_limit_7d" field.
func RateLimit7dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
}
// RateLimit7dNEQ applies the NEQ predicate on the "rate_limit_7d" field.
func RateLimit7dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit7d, v))
}
// RateLimit7dIn applies the In predicate on the "rate_limit_7d" field.
func RateLimit7dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit7d, vs...))
}
// RateLimit7dNotIn applies the NotIn predicate on the "rate_limit_7d" field.
func RateLimit7dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit7d, vs...))
}
// RateLimit7dGT applies the GT predicate on the "rate_limit_7d" field.
func RateLimit7dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit7d, v))
}
// RateLimit7dGTE applies the GTE predicate on the "rate_limit_7d" field.
func RateLimit7dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit7d, v))
}
// RateLimit7dLT applies the LT predicate on the "rate_limit_7d" field.
func RateLimit7dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit7d, v))
}
// RateLimit7dLTE applies the LTE predicate on the "rate_limit_7d" field.
func RateLimit7dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit7d, v))
}
// Usage5hEQ applies the EQ predicate on the "usage_5h" field.
func Usage5hEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
}
// Usage5hNEQ applies the NEQ predicate on the "usage_5h" field.
func Usage5hNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage5h, v))
}
// Usage5hIn applies the In predicate on the "usage_5h" field.
func Usage5hIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage5h, vs...))
}
// Usage5hNotIn applies the NotIn predicate on the "usage_5h" field.
func Usage5hNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage5h, vs...))
}
// Usage5hGT applies the GT predicate on the "usage_5h" field.
func Usage5hGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage5h, v))
}
// Usage5hGTE applies the GTE predicate on the "usage_5h" field.
func Usage5hGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage5h, v))
}
// Usage5hLT applies the LT predicate on the "usage_5h" field.
func Usage5hLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage5h, v))
}
// Usage5hLTE applies the LTE predicate on the "usage_5h" field.
func Usage5hLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage5h, v))
}
// Usage1dEQ applies the EQ predicate on the "usage_1d" field.
func Usage1dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
}
// Usage1dNEQ applies the NEQ predicate on the "usage_1d" field.
func Usage1dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage1d, v))
}
// Usage1dIn applies the In predicate on the "usage_1d" field.
func Usage1dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage1d, vs...))
}
// Usage1dNotIn applies the NotIn predicate on the "usage_1d" field.
func Usage1dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage1d, vs...))
}
// Usage1dGT applies the GT predicate on the "usage_1d" field.
func Usage1dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage1d, v))
}
// Usage1dGTE applies the GTE predicate on the "usage_1d" field.
func Usage1dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage1d, v))
}
// Usage1dLT applies the LT predicate on the "usage_1d" field.
func Usage1dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage1d, v))
}
// Usage1dLTE applies the LTE predicate on the "usage_1d" field.
func Usage1dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage1d, v))
}
// Usage7dEQ applies the EQ predicate on the "usage_7d" field.
func Usage7dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
}
// Usage7dNEQ applies the NEQ predicate on the "usage_7d" field.
func Usage7dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage7d, v))
}
// Usage7dIn applies the In predicate on the "usage_7d" field.
func Usage7dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage7d, vs...))
}
// Usage7dNotIn applies the NotIn predicate on the "usage_7d" field.
func Usage7dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage7d, vs...))
}
// Usage7dGT applies the GT predicate on the "usage_7d" field.
func Usage7dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage7d, v))
}
// Usage7dGTE applies the GTE predicate on the "usage_7d" field.
func Usage7dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage7d, v))
}
// Usage7dLT applies the LT predicate on the "usage_7d" field.
func Usage7dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage7d, v))
}
// Usage7dLTE applies the LTE predicate on the "usage_7d" field.
func Usage7dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage7d, v))
}
// Window5hStartEQ applies the EQ predicate on the "window_5h_start" field.
func Window5hStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
}
// Window5hStartNEQ applies the NEQ predicate on the "window_5h_start" field.
func Window5hStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow5hStart, v))
}
// Window5hStartIn applies the In predicate on the "window_5h_start" field.
func Window5hStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow5hStart, vs...))
}
// Window5hStartNotIn applies the NotIn predicate on the "window_5h_start" field.
func Window5hStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow5hStart, vs...))
}
// Window5hStartGT applies the GT predicate on the "window_5h_start" field.
func Window5hStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow5hStart, v))
}
// Window5hStartGTE applies the GTE predicate on the "window_5h_start" field.
func Window5hStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow5hStart, v))
}
// Window5hStartLT applies the LT predicate on the "window_5h_start" field.
func Window5hStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow5hStart, v))
}
// Window5hStartLTE applies the LTE predicate on the "window_5h_start" field.
func Window5hStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow5hStart, v))
}
// Window5hStartIsNil applies the IsNil predicate on the "window_5h_start" field.
func Window5hStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow5hStart))
}
// Window5hStartNotNil applies the NotNil predicate on the "window_5h_start" field.
func Window5hStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow5hStart))
}
// Window1dStartEQ applies the EQ predicate on the "window_1d_start" field.
func Window1dStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
}
// Window1dStartNEQ applies the NEQ predicate on the "window_1d_start" field.
func Window1dStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow1dStart, v))
}
// Window1dStartIn applies the In predicate on the "window_1d_start" field.
func Window1dStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow1dStart, vs...))
}
// Window1dStartNotIn applies the NotIn predicate on the "window_1d_start" field.
func Window1dStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow1dStart, vs...))
}
// Window1dStartGT applies the GT predicate on the "window_1d_start" field.
func Window1dStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow1dStart, v))
}
// Window1dStartGTE applies the GTE predicate on the "window_1d_start" field.
func Window1dStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow1dStart, v))
}
// Window1dStartLT applies the LT predicate on the "window_1d_start" field.
func Window1dStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow1dStart, v))
}
// Window1dStartLTE applies the LTE predicate on the "window_1d_start" field.
func Window1dStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow1dStart, v))
}
// Window1dStartIsNil applies the IsNil predicate on the "window_1d_start" field.
func Window1dStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow1dStart))
}
// Window1dStartNotNil applies the NotNil predicate on the "window_1d_start" field.
func Window1dStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow1dStart))
}
// Window7dStartEQ applies the EQ predicate on the "window_7d_start" field.
func Window7dStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
}
// Window7dStartNEQ applies the NEQ predicate on the "window_7d_start" field.
func Window7dStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow7dStart, v))
}
// Window7dStartIn applies the In predicate on the "window_7d_start" field.
func Window7dStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow7dStart, vs...))
}
// Window7dStartNotIn applies the NotIn predicate on the "window_7d_start" field.
func Window7dStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow7dStart, vs...))
}
// Window7dStartGT applies the GT predicate on the "window_7d_start" field.
func Window7dStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow7dStart, v))
}
// Window7dStartGTE applies the GTE predicate on the "window_7d_start" field.
func Window7dStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow7dStart, v))
}
// Window7dStartLT applies the LT predicate on the "window_7d_start" field.
func Window7dStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow7dStart, v))
}
// Window7dStartLTE applies the LTE predicate on the "window_7d_start" field.
func Window7dStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow7dStart, v))
}
// Window7dStartIsNil applies the IsNil predicate on the "window_7d_start" field.
func Window7dStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow7dStart))
}
// Window7dStartNotNil applies the NotNil predicate on the "window_7d_start" field.
func Window7dStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow7dStart))
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.APIKey {
return predicate.APIKey(func(s *sql.Selector) {

View File

@@ -113,6 +113,20 @@ func (_c *APIKeyCreate) SetNillableStatus(v *string) *APIKeyCreate {
return _c
}
// SetLastUsedAt sets the "last_used_at" field.
func (_c *APIKeyCreate) SetLastUsedAt(v time.Time) *APIKeyCreate {
_c.mutation.SetLastUsedAt(v)
return _c
}
// SetNillableLastUsedAt sets the "last_used_at" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableLastUsedAt(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetLastUsedAt(*v)
}
return _c
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (_c *APIKeyCreate) SetIPWhitelist(v []string) *APIKeyCreate {
_c.mutation.SetIPWhitelist(v)
@@ -167,6 +181,132 @@ func (_c *APIKeyCreate) SetNillableExpiresAt(v *time.Time) *APIKeyCreate {
return _c
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_c *APIKeyCreate) SetRateLimit5h(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit5h(v)
return _c
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit5h(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit5h(*v)
}
return _c
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_c *APIKeyCreate) SetRateLimit1d(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit1d(v)
return _c
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit1d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit1d(*v)
}
return _c
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_c *APIKeyCreate) SetRateLimit7d(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit7d(v)
return _c
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit7d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit7d(*v)
}
return _c
}
// SetUsage5h sets the "usage_5h" field.
func (_c *APIKeyCreate) SetUsage5h(v float64) *APIKeyCreate {
_c.mutation.SetUsage5h(v)
return _c
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage5h(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage5h(*v)
}
return _c
}
// SetUsage1d sets the "usage_1d" field.
func (_c *APIKeyCreate) SetUsage1d(v float64) *APIKeyCreate {
_c.mutation.SetUsage1d(v)
return _c
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage1d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage1d(*v)
}
return _c
}
// SetUsage7d sets the "usage_7d" field.
func (_c *APIKeyCreate) SetUsage7d(v float64) *APIKeyCreate {
_c.mutation.SetUsage7d(v)
return _c
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage7d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage7d(*v)
}
return _c
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_c *APIKeyCreate) SetWindow5hStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow5hStart(v)
return _c
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow5hStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow5hStart(*v)
}
return _c
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_c *APIKeyCreate) SetWindow1dStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow1dStart(v)
return _c
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow1dStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow1dStart(*v)
}
return _c
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_c *APIKeyCreate) SetWindow7dStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow7dStart(v)
return _c
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow7dStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow7dStart(*v)
}
return _c
}
// SetUser sets the "user" edge to the User entity.
func (_c *APIKeyCreate) SetUser(v *User) *APIKeyCreate {
return _c.SetUserID(v.ID)
@@ -255,6 +395,30 @@ func (_c *APIKeyCreate) defaults() error {
v := apikey.DefaultQuotaUsed
_c.mutation.SetQuotaUsed(v)
}
if _, ok := _c.mutation.RateLimit5h(); !ok {
v := apikey.DefaultRateLimit5h
_c.mutation.SetRateLimit5h(v)
}
if _, ok := _c.mutation.RateLimit1d(); !ok {
v := apikey.DefaultRateLimit1d
_c.mutation.SetRateLimit1d(v)
}
if _, ok := _c.mutation.RateLimit7d(); !ok {
v := apikey.DefaultRateLimit7d
_c.mutation.SetRateLimit7d(v)
}
if _, ok := _c.mutation.Usage5h(); !ok {
v := apikey.DefaultUsage5h
_c.mutation.SetUsage5h(v)
}
if _, ok := _c.mutation.Usage1d(); !ok {
v := apikey.DefaultUsage1d
_c.mutation.SetUsage1d(v)
}
if _, ok := _c.mutation.Usage7d(); !ok {
v := apikey.DefaultUsage7d
_c.mutation.SetUsage7d(v)
}
return nil
}
@@ -299,6 +463,24 @@ func (_c *APIKeyCreate) check() error {
if _, ok := _c.mutation.QuotaUsed(); !ok {
return &ValidationError{Name: "quota_used", err: errors.New(`ent: missing required field "APIKey.quota_used"`)}
}
if _, ok := _c.mutation.RateLimit5h(); !ok {
return &ValidationError{Name: "rate_limit_5h", err: errors.New(`ent: missing required field "APIKey.rate_limit_5h"`)}
}
if _, ok := _c.mutation.RateLimit1d(); !ok {
return &ValidationError{Name: "rate_limit_1d", err: errors.New(`ent: missing required field "APIKey.rate_limit_1d"`)}
}
if _, ok := _c.mutation.RateLimit7d(); !ok {
return &ValidationError{Name: "rate_limit_7d", err: errors.New(`ent: missing required field "APIKey.rate_limit_7d"`)}
}
if _, ok := _c.mutation.Usage5h(); !ok {
return &ValidationError{Name: "usage_5h", err: errors.New(`ent: missing required field "APIKey.usage_5h"`)}
}
if _, ok := _c.mutation.Usage1d(); !ok {
return &ValidationError{Name: "usage_1d", err: errors.New(`ent: missing required field "APIKey.usage_1d"`)}
}
if _, ok := _c.mutation.Usage7d(); !ok {
return &ValidationError{Name: "usage_7d", err: errors.New(`ent: missing required field "APIKey.usage_7d"`)}
}
if len(_c.mutation.UserIDs()) == 0 {
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "APIKey.user"`)}
}
@@ -353,6 +535,10 @@ func (_c *APIKeyCreate) createSpec() (*APIKey, *sqlgraph.CreateSpec) {
_spec.SetField(apikey.FieldStatus, field.TypeString, value)
_node.Status = value
}
if value, ok := _c.mutation.LastUsedAt(); ok {
_spec.SetField(apikey.FieldLastUsedAt, field.TypeTime, value)
_node.LastUsedAt = &value
}
if value, ok := _c.mutation.IPWhitelist(); ok {
_spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value)
_node.IPWhitelist = value
@@ -373,6 +559,42 @@ func (_c *APIKeyCreate) createSpec() (*APIKey, *sqlgraph.CreateSpec) {
_spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value)
_node.ExpiresAt = &value
}
if value, ok := _c.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
_node.RateLimit5h = value
}
if value, ok := _c.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
_node.RateLimit1d = value
}
if value, ok := _c.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
_node.RateLimit7d = value
}
if value, ok := _c.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
_node.Usage5h = value
}
if value, ok := _c.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
_node.Usage1d = value
}
if value, ok := _c.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
_node.Usage7d = value
}
if value, ok := _c.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
_node.Window5hStart = &value
}
if value, ok := _c.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
_node.Window1dStart = &value
}
if value, ok := _c.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
_node.Window7dStart = &value
}
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -571,6 +793,24 @@ func (u *APIKeyUpsert) UpdateStatus() *APIKeyUpsert {
return u
}
// SetLastUsedAt sets the "last_used_at" field.
func (u *APIKeyUpsert) SetLastUsedAt(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldLastUsedAt, v)
return u
}
// UpdateLastUsedAt sets the "last_used_at" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateLastUsedAt() *APIKeyUpsert {
u.SetExcluded(apikey.FieldLastUsedAt)
return u
}
// ClearLastUsedAt clears the value of the "last_used_at" field.
func (u *APIKeyUpsert) ClearLastUsedAt() *APIKeyUpsert {
u.SetNull(apikey.FieldLastUsedAt)
return u
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (u *APIKeyUpsert) SetIPWhitelist(v []string) *APIKeyUpsert {
u.Set(apikey.FieldIPWhitelist, v)
@@ -661,6 +901,168 @@ func (u *APIKeyUpsert) ClearExpiresAt() *APIKeyUpsert {
return u
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsert) SetRateLimit5h(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit5h, v)
return u
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit5h() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit5h)
return u
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsert) AddRateLimit5h(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit5h, v)
return u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsert) SetRateLimit1d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit1d, v)
return u
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit1d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit1d)
return u
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsert) AddRateLimit1d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit1d, v)
return u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsert) SetRateLimit7d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit7d, v)
return u
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit7d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit7d)
return u
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsert) AddRateLimit7d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit7d, v)
return u
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsert) SetUsage5h(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage5h, v)
return u
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage5h() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage5h)
return u
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsert) AddUsage5h(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage5h, v)
return u
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsert) SetUsage1d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage1d, v)
return u
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage1d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage1d)
return u
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsert) AddUsage1d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage1d, v)
return u
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsert) SetUsage7d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage7d, v)
return u
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage7d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage7d)
return u
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsert) AddUsage7d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage7d, v)
return u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsert) SetWindow5hStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow5hStart, v)
return u
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow5hStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow5hStart)
return u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsert) ClearWindow5hStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow5hStart)
return u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsert) SetWindow1dStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow1dStart, v)
return u
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow1dStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow1dStart)
return u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsert) ClearWindow1dStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow1dStart)
return u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsert) SetWindow7dStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow7dStart, v)
return u
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow7dStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow7dStart)
return u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsert) ClearWindow7dStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow7dStart)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
@@ -818,6 +1220,27 @@ func (u *APIKeyUpsertOne) UpdateStatus() *APIKeyUpsertOne {
})
}
// SetLastUsedAt sets the "last_used_at" field.
func (u *APIKeyUpsertOne) SetLastUsedAt(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetLastUsedAt(v)
})
}
// UpdateLastUsedAt sets the "last_used_at" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateLastUsedAt() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateLastUsedAt()
})
}
// ClearLastUsedAt clears the value of the "last_used_at" field.
func (u *APIKeyUpsertOne) ClearLastUsedAt() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearLastUsedAt()
})
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (u *APIKeyUpsertOne) SetIPWhitelist(v []string) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
@@ -923,6 +1346,195 @@ func (u *APIKeyUpsertOne) ClearExpiresAt() *APIKeyUpsertOne {
})
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsertOne) SetRateLimit5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit5h(v)
})
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsertOne) AddRateLimit5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit5h(v)
})
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit5h() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit5h()
})
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsertOne) SetRateLimit1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit1d(v)
})
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsertOne) AddRateLimit1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit1d(v)
})
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit1d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit1d()
})
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsertOne) SetRateLimit7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit7d(v)
})
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsertOne) AddRateLimit7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit7d(v)
})
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit7d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit7d()
})
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsertOne) SetUsage5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage5h(v)
})
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsertOne) AddUsage5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage5h(v)
})
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage5h() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage5h()
})
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsertOne) SetUsage1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage1d(v)
})
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsertOne) AddUsage1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage1d(v)
})
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage1d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage1d()
})
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsertOne) SetUsage7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage7d(v)
})
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsertOne) AddUsage7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage7d(v)
})
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage7d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage7d()
})
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsertOne) SetWindow5hStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow5hStart(v)
})
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow5hStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow5hStart()
})
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsertOne) ClearWindow5hStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow5hStart()
})
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsertOne) SetWindow1dStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow1dStart(v)
})
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow1dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow1dStart()
})
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsertOne) ClearWindow1dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow1dStart()
})
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsertOne) SetWindow7dStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow7dStart(v)
})
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow7dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow7dStart()
})
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsertOne) ClearWindow7dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow7dStart()
})
}
// Exec executes the query.
func (u *APIKeyUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
@@ -1246,6 +1858,27 @@ func (u *APIKeyUpsertBulk) UpdateStatus() *APIKeyUpsertBulk {
})
}
// SetLastUsedAt sets the "last_used_at" field.
func (u *APIKeyUpsertBulk) SetLastUsedAt(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetLastUsedAt(v)
})
}
// UpdateLastUsedAt sets the "last_used_at" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateLastUsedAt() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateLastUsedAt()
})
}
// ClearLastUsedAt clears the value of the "last_used_at" field.
func (u *APIKeyUpsertBulk) ClearLastUsedAt() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearLastUsedAt()
})
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (u *APIKeyUpsertBulk) SetIPWhitelist(v []string) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
@@ -1351,6 +1984,195 @@ func (u *APIKeyUpsertBulk) ClearExpiresAt() *APIKeyUpsertBulk {
})
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsertBulk) SetRateLimit5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit5h(v)
})
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsertBulk) AddRateLimit5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit5h(v)
})
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit5h() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit5h()
})
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsertBulk) SetRateLimit1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit1d(v)
})
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsertBulk) AddRateLimit1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit1d(v)
})
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit1d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit1d()
})
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsertBulk) SetRateLimit7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit7d(v)
})
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsertBulk) AddRateLimit7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit7d(v)
})
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit7d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit7d()
})
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsertBulk) SetUsage5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage5h(v)
})
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsertBulk) AddUsage5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage5h(v)
})
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage5h() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage5h()
})
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsertBulk) SetUsage1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage1d(v)
})
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsertBulk) AddUsage1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage1d(v)
})
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage1d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage1d()
})
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsertBulk) SetUsage7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage7d(v)
})
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsertBulk) AddUsage7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage7d(v)
})
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage7d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage7d()
})
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsertBulk) SetWindow5hStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow5hStart(v)
})
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow5hStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow5hStart()
})
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsertBulk) ClearWindow5hStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow5hStart()
})
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsertBulk) SetWindow1dStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow1dStart(v)
})
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow1dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow1dStart()
})
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsertBulk) ClearWindow1dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow1dStart()
})
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsertBulk) SetWindow7dStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow7dStart(v)
})
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow7dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow7dStart()
})
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsertBulk) ClearWindow7dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow7dStart()
})
}
// Exec executes the query.
func (u *APIKeyUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {

View File

@@ -134,6 +134,26 @@ func (_u *APIKeyUpdate) SetNillableStatus(v *string) *APIKeyUpdate {
return _u
}
// SetLastUsedAt sets the "last_used_at" field.
func (_u *APIKeyUpdate) SetLastUsedAt(v time.Time) *APIKeyUpdate {
_u.mutation.SetLastUsedAt(v)
return _u
}
// SetNillableLastUsedAt sets the "last_used_at" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableLastUsedAt(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetLastUsedAt(*v)
}
return _u
}
// ClearLastUsedAt clears the value of the "last_used_at" field.
func (_u *APIKeyUpdate) ClearLastUsedAt() *APIKeyUpdate {
_u.mutation.ClearLastUsedAt()
return _u
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (_u *APIKeyUpdate) SetIPWhitelist(v []string) *APIKeyUpdate {
_u.mutation.SetIPWhitelist(v)
@@ -232,6 +252,192 @@ func (_u *APIKeyUpdate) ClearExpiresAt() *APIKeyUpdate {
return _u
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_u *APIKeyUpdate) SetRateLimit5h(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit5h()
_u.mutation.SetRateLimit5h(v)
return _u
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit5h(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit5h(*v)
}
return _u
}
// AddRateLimit5h adds value to the "rate_limit_5h" field.
func (_u *APIKeyUpdate) AddRateLimit5h(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit5h(v)
return _u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_u *APIKeyUpdate) SetRateLimit1d(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit1d()
_u.mutation.SetRateLimit1d(v)
return _u
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit1d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit1d(*v)
}
return _u
}
// AddRateLimit1d adds value to the "rate_limit_1d" field.
func (_u *APIKeyUpdate) AddRateLimit1d(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit1d(v)
return _u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_u *APIKeyUpdate) SetRateLimit7d(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit7d()
_u.mutation.SetRateLimit7d(v)
return _u
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit7d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit7d(*v)
}
return _u
}
// AddRateLimit7d adds value to the "rate_limit_7d" field.
func (_u *APIKeyUpdate) AddRateLimit7d(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit7d(v)
return _u
}
// SetUsage5h sets the "usage_5h" field.
func (_u *APIKeyUpdate) SetUsage5h(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage5h()
_u.mutation.SetUsage5h(v)
return _u
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage5h(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage5h(*v)
}
return _u
}
// AddUsage5h adds value to the "usage_5h" field.
func (_u *APIKeyUpdate) AddUsage5h(v float64) *APIKeyUpdate {
_u.mutation.AddUsage5h(v)
return _u
}
// SetUsage1d sets the "usage_1d" field.
func (_u *APIKeyUpdate) SetUsage1d(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage1d()
_u.mutation.SetUsage1d(v)
return _u
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage1d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage1d(*v)
}
return _u
}
// AddUsage1d adds value to the "usage_1d" field.
func (_u *APIKeyUpdate) AddUsage1d(v float64) *APIKeyUpdate {
_u.mutation.AddUsage1d(v)
return _u
}
// SetUsage7d sets the "usage_7d" field.
func (_u *APIKeyUpdate) SetUsage7d(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage7d()
_u.mutation.SetUsage7d(v)
return _u
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage7d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage7d(*v)
}
return _u
}
// AddUsage7d adds value to the "usage_7d" field.
func (_u *APIKeyUpdate) AddUsage7d(v float64) *APIKeyUpdate {
_u.mutation.AddUsage7d(v)
return _u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_u *APIKeyUpdate) SetWindow5hStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow5hStart(v)
return _u
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow5hStart(*v)
}
return _u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (_u *APIKeyUpdate) ClearWindow5hStart() *APIKeyUpdate {
_u.mutation.ClearWindow5hStart()
return _u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_u *APIKeyUpdate) SetWindow1dStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow1dStart(v)
return _u
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow1dStart(*v)
}
return _u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (_u *APIKeyUpdate) ClearWindow1dStart() *APIKeyUpdate {
_u.mutation.ClearWindow1dStart()
return _u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_u *APIKeyUpdate) SetWindow7dStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow7dStart(v)
return _u
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow7dStart(*v)
}
return _u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (_u *APIKeyUpdate) ClearWindow7dStart() *APIKeyUpdate {
_u.mutation.ClearWindow7dStart()
return _u
}
// SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate {
return _u.SetUserID(v.ID)
@@ -390,6 +596,12 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(apikey.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.LastUsedAt(); ok {
_spec.SetField(apikey.FieldLastUsedAt, field.TypeTime, value)
}
if _u.mutation.LastUsedAtCleared() {
_spec.ClearField(apikey.FieldLastUsedAt, field.TypeTime)
}
if value, ok := _u.mutation.IPWhitelist(); ok {
_spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value)
}
@@ -430,6 +642,60 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
}
if value, ok := _u.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage5h(); ok {
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage1d(); ok {
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage7d(); ok {
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
}
if _u.mutation.Window5hStartCleared() {
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
}
if value, ok := _u.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
}
if _u.mutation.Window1dStartCleared() {
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
}
if value, ok := _u.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
}
if _u.mutation.Window7dStartCleared() {
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
@@ -655,6 +921,26 @@ func (_u *APIKeyUpdateOne) SetNillableStatus(v *string) *APIKeyUpdateOne {
return _u
}
// SetLastUsedAt sets the "last_used_at" field.
func (_u *APIKeyUpdateOne) SetLastUsedAt(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetLastUsedAt(v)
return _u
}
// SetNillableLastUsedAt sets the "last_used_at" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableLastUsedAt(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetLastUsedAt(*v)
}
return _u
}
// ClearLastUsedAt clears the value of the "last_used_at" field.
func (_u *APIKeyUpdateOne) ClearLastUsedAt() *APIKeyUpdateOne {
_u.mutation.ClearLastUsedAt()
return _u
}
// SetIPWhitelist sets the "ip_whitelist" field.
func (_u *APIKeyUpdateOne) SetIPWhitelist(v []string) *APIKeyUpdateOne {
_u.mutation.SetIPWhitelist(v)
@@ -753,6 +1039,192 @@ func (_u *APIKeyUpdateOne) ClearExpiresAt() *APIKeyUpdateOne {
return _u
}
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_u *APIKeyUpdateOne) SetRateLimit5h(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit5h()
_u.mutation.SetRateLimit5h(v)
return _u
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit5h(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit5h(*v)
}
return _u
}
// AddRateLimit5h adds value to the "rate_limit_5h" field.
func (_u *APIKeyUpdateOne) AddRateLimit5h(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit5h(v)
return _u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_u *APIKeyUpdateOne) SetRateLimit1d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit1d()
_u.mutation.SetRateLimit1d(v)
return _u
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit1d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit1d(*v)
}
return _u
}
// AddRateLimit1d adds value to the "rate_limit_1d" field.
func (_u *APIKeyUpdateOne) AddRateLimit1d(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit1d(v)
return _u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_u *APIKeyUpdateOne) SetRateLimit7d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit7d()
_u.mutation.SetRateLimit7d(v)
return _u
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit7d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit7d(*v)
}
return _u
}
// AddRateLimit7d adds value to the "rate_limit_7d" field.
func (_u *APIKeyUpdateOne) AddRateLimit7d(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit7d(v)
return _u
}
// SetUsage5h sets the "usage_5h" field.
func (_u *APIKeyUpdateOne) SetUsage5h(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage5h()
_u.mutation.SetUsage5h(v)
return _u
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage5h(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage5h(*v)
}
return _u
}
// AddUsage5h adds value to the "usage_5h" field.
func (_u *APIKeyUpdateOne) AddUsage5h(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage5h(v)
return _u
}
// SetUsage1d sets the "usage_1d" field.
func (_u *APIKeyUpdateOne) SetUsage1d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage1d()
_u.mutation.SetUsage1d(v)
return _u
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage1d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage1d(*v)
}
return _u
}
// AddUsage1d adds value to the "usage_1d" field.
func (_u *APIKeyUpdateOne) AddUsage1d(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage1d(v)
return _u
}
// SetUsage7d sets the "usage_7d" field.
func (_u *APIKeyUpdateOne) SetUsage7d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage7d()
_u.mutation.SetUsage7d(v)
return _u
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage7d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage7d(*v)
}
return _u
}
// AddUsage7d adds value to the "usage_7d" field.
func (_u *APIKeyUpdateOne) AddUsage7d(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage7d(v)
return _u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_u *APIKeyUpdateOne) SetWindow5hStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow5hStart(v)
return _u
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow5hStart(*v)
}
return _u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (_u *APIKeyUpdateOne) ClearWindow5hStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow5hStart()
return _u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_u *APIKeyUpdateOne) SetWindow1dStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow1dStart(v)
return _u
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow1dStart(*v)
}
return _u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (_u *APIKeyUpdateOne) ClearWindow1dStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow1dStart()
return _u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_u *APIKeyUpdateOne) SetWindow7dStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow7dStart(v)
return _u
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow7dStart(*v)
}
return _u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (_u *APIKeyUpdateOne) ClearWindow7dStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow7dStart()
return _u
}
// SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne {
return _u.SetUserID(v.ID)
@@ -941,6 +1413,12 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(apikey.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.LastUsedAt(); ok {
_spec.SetField(apikey.FieldLastUsedAt, field.TypeTime, value)
}
if _u.mutation.LastUsedAtCleared() {
_spec.ClearField(apikey.FieldLastUsedAt, field.TypeTime)
}
if value, ok := _u.mutation.IPWhitelist(); ok {
_spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value)
}
@@ -981,6 +1459,60 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
}
if value, ok := _u.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage5h(); ok {
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage1d(); ok {
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage7d(); ok {
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
}
if _u.mutation.Window5hStartCleared() {
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
}
if value, ok := _u.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
}
if _u.mutation.Window1dStartCleared() {
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
}
if value, ok := _u.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
}
if _u.mutation.Window7dStartCleared() {
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,

File diff suppressed because it is too large Load Diff

View File

@@ -19,11 +19,18 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -98,11 +105,18 @@ func checkColumn(t, c string) error {
announcementread.Table: announcementread.ValidColumn,
errorpassthroughrule.Table: errorpassthroughrule.ValidColumn,
group.Table: group.ValidColumn,
idempotencyrecord.Table: idempotencyrecord.ValidColumn,
paymentauditlog.Table: paymentauditlog.ValidColumn,
paymentorder.Table: paymentorder.ValidColumn,
paymentproviderinstance.Table: paymentproviderinstance.ValidColumn,
promocode.Table: promocode.ValidColumn,
promocodeusage.Table: promocodeusage.ValidColumn,
proxy.Table: proxy.ValidColumn,
redeemcode.Table: redeemcode.ValidColumn,
securitysecret.Table: securitysecret.ValidColumn,
setting.Table: setting.ValidColumn,
subscriptionplan.Table: subscriptionplan.ValidColumn,
tlsfingerprintprofile.Table: tlsfingerprintprofile.ValidColumn,
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
usagelog.Table: usagelog.ValidColumn,
user.Table: user.ValidColumn,

View File

@@ -11,6 +11,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// Group is the model entity for the Group schema.
@@ -68,6 +69,16 @@ type Group struct {
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
// 分组显示排序,数值越小越靠前
SortOrder int `json:"sort_order,omitempty"`
// 是否允许 /v1/messages 调度到此 OpenAI 分组
AllowMessagesDispatch bool `json:"allow_messages_dispatch,omitempty"`
// 仅允许非 apikey 类型账号关联到此分组
RequireOauthOnly bool `json:"require_oauth_only,omitempty"`
// 调度时仅允许 privacy 已成功设置的账号
RequirePrivacySet bool `json:"require_privacy_set,omitempty"`
// 默认映射模型 ID当账号级映射找不到时使用此值
DefaultMappedModel string `json:"default_mapped_model,omitempty"`
// OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型
MessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the GroupQuery when eager-loading is set.
Edges GroupEdges `json:"edges"`
@@ -174,15 +185,15 @@ func (*Group) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case group.FieldModelRouting, group.FieldSupportedModelScopes:
case group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig:
values[i] = new([]byte)
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject:
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
values[i] = new(sql.NullBool)
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k:
values[i] = new(sql.NullFloat64)
case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder:
values[i] = new(sql.NullInt64)
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType:
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType, group.FieldDefaultMappedModel:
values[i] = new(sql.NullString)
case group.FieldCreatedAt, group.FieldUpdatedAt, group.FieldDeletedAt:
values[i] = new(sql.NullTime)
@@ -371,6 +382,38 @@ func (_m *Group) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.SortOrder = int(value.Int64)
}
case group.FieldAllowMessagesDispatch:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field allow_messages_dispatch", values[i])
} else if value.Valid {
_m.AllowMessagesDispatch = value.Bool
}
case group.FieldRequireOauthOnly:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field require_oauth_only", values[i])
} else if value.Valid {
_m.RequireOauthOnly = value.Bool
}
case group.FieldRequirePrivacySet:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field require_privacy_set", values[i])
} else if value.Valid {
_m.RequirePrivacySet = value.Bool
}
case group.FieldDefaultMappedModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field default_mapped_model", values[i])
} else if value.Valid {
_m.DefaultMappedModel = value.String
}
case group.FieldMessagesDispatchModelConfig:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field messages_dispatch_model_config", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.MessagesDispatchModelConfig); err != nil {
return fmt.Errorf("unmarshal field messages_dispatch_model_config: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
@@ -541,6 +584,21 @@ func (_m *Group) String() string {
builder.WriteString(", ")
builder.WriteString("sort_order=")
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
builder.WriteString(", ")
builder.WriteString("allow_messages_dispatch=")
builder.WriteString(fmt.Sprintf("%v", _m.AllowMessagesDispatch))
builder.WriteString(", ")
builder.WriteString("require_oauth_only=")
builder.WriteString(fmt.Sprintf("%v", _m.RequireOauthOnly))
builder.WriteString(", ")
builder.WriteString("require_privacy_set=")
builder.WriteString(fmt.Sprintf("%v", _m.RequirePrivacySet))
builder.WriteString(", ")
builder.WriteString("default_mapped_model=")
builder.WriteString(_m.DefaultMappedModel)
builder.WriteString(", ")
builder.WriteString("messages_dispatch_model_config=")
builder.WriteString(fmt.Sprintf("%v", _m.MessagesDispatchModelConfig))
builder.WriteByte(')')
return builder.String()
}

View File

@@ -8,6 +8,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
const (
@@ -65,6 +66,16 @@ const (
FieldSupportedModelScopes = "supported_model_scopes"
// FieldSortOrder holds the string denoting the sort_order field in the database.
FieldSortOrder = "sort_order"
// FieldAllowMessagesDispatch holds the string denoting the allow_messages_dispatch field in the database.
FieldAllowMessagesDispatch = "allow_messages_dispatch"
// FieldRequireOauthOnly holds the string denoting the require_oauth_only field in the database.
FieldRequireOauthOnly = "require_oauth_only"
// FieldRequirePrivacySet holds the string denoting the require_privacy_set field in the database.
FieldRequirePrivacySet = "require_privacy_set"
// FieldDefaultMappedModel holds the string denoting the default_mapped_model field in the database.
FieldDefaultMappedModel = "default_mapped_model"
// FieldMessagesDispatchModelConfig holds the string denoting the messages_dispatch_model_config field in the database.
FieldMessagesDispatchModelConfig = "messages_dispatch_model_config"
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
EdgeAPIKeys = "api_keys"
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
@@ -165,6 +176,11 @@ var Columns = []string{
FieldMcpXMLInject,
FieldSupportedModelScopes,
FieldSortOrder,
FieldAllowMessagesDispatch,
FieldRequireOauthOnly,
FieldRequirePrivacySet,
FieldDefaultMappedModel,
FieldMessagesDispatchModelConfig,
}
var (
@@ -230,6 +246,18 @@ var (
DefaultSupportedModelScopes []string
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
DefaultSortOrder int
// DefaultAllowMessagesDispatch holds the default value on creation for the "allow_messages_dispatch" field.
DefaultAllowMessagesDispatch bool
// DefaultRequireOauthOnly holds the default value on creation for the "require_oauth_only" field.
DefaultRequireOauthOnly bool
// DefaultRequirePrivacySet holds the default value on creation for the "require_privacy_set" field.
DefaultRequirePrivacySet bool
// DefaultDefaultMappedModel holds the default value on creation for the "default_mapped_model" field.
DefaultDefaultMappedModel string
// DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
DefaultMappedModelValidator func(string) error
// DefaultMessagesDispatchModelConfig holds the default value on creation for the "messages_dispatch_model_config" field.
DefaultMessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig
)
// OrderOption defines the ordering options for the Group queries.
@@ -355,6 +383,26 @@ func BySortOrder(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
}
// ByAllowMessagesDispatch orders the results by the allow_messages_dispatch field.
func ByAllowMessagesDispatch(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAllowMessagesDispatch, opts...).ToFunc()
}
// ByRequireOauthOnly orders the results by the require_oauth_only field.
func ByRequireOauthOnly(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRequireOauthOnly, opts...).ToFunc()
}
// ByRequirePrivacySet orders the results by the require_privacy_set field.
func ByRequirePrivacySet(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRequirePrivacySet, opts...).ToFunc()
}
// ByDefaultMappedModel orders the results by the default_mapped_model field.
func ByDefaultMappedModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDefaultMappedModel, opts...).ToFunc()
}
// ByAPIKeysCount orders the results by api_keys count.
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {

View File

@@ -170,6 +170,26 @@ func SortOrder(v int) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldSortOrder, v))
}
// AllowMessagesDispatch applies equality check predicate on the "allow_messages_dispatch" field. It's identical to AllowMessagesDispatchEQ.
func AllowMessagesDispatch(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldAllowMessagesDispatch, v))
}
// RequireOauthOnly applies equality check predicate on the "require_oauth_only" field. It's identical to RequireOauthOnlyEQ.
func RequireOauthOnly(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
}
// RequirePrivacySet applies equality check predicate on the "require_privacy_set" field. It's identical to RequirePrivacySetEQ.
func RequirePrivacySet(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldRequirePrivacySet, v))
}
// DefaultMappedModel applies equality check predicate on the "default_mapped_model" field. It's identical to DefaultMappedModelEQ.
func DefaultMappedModel(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldDefaultMappedModel, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldCreatedAt, v))
@@ -1205,6 +1225,101 @@ func SortOrderLTE(v int) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldSortOrder, v))
}
// AllowMessagesDispatchEQ applies the EQ predicate on the "allow_messages_dispatch" field.
func AllowMessagesDispatchEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldAllowMessagesDispatch, v))
}
// AllowMessagesDispatchNEQ applies the NEQ predicate on the "allow_messages_dispatch" field.
func AllowMessagesDispatchNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldAllowMessagesDispatch, v))
}
// RequireOauthOnlyEQ applies the EQ predicate on the "require_oauth_only" field.
func RequireOauthOnlyEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
}
// RequireOauthOnlyNEQ applies the NEQ predicate on the "require_oauth_only" field.
func RequireOauthOnlyNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldRequireOauthOnly, v))
}
// RequirePrivacySetEQ applies the EQ predicate on the "require_privacy_set" field.
func RequirePrivacySetEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldRequirePrivacySet, v))
}
// RequirePrivacySetNEQ applies the NEQ predicate on the "require_privacy_set" field.
func RequirePrivacySetNEQ(v bool) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldRequirePrivacySet, v))
}
// DefaultMappedModelEQ applies the EQ predicate on the "default_mapped_model" field.
func DefaultMappedModelEQ(v string) predicate.Group {
return predicate.Group(sql.FieldEQ(FieldDefaultMappedModel, v))
}
// DefaultMappedModelNEQ applies the NEQ predicate on the "default_mapped_model" field.
func DefaultMappedModelNEQ(v string) predicate.Group {
return predicate.Group(sql.FieldNEQ(FieldDefaultMappedModel, v))
}
// DefaultMappedModelIn applies the In predicate on the "default_mapped_model" field.
func DefaultMappedModelIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldIn(FieldDefaultMappedModel, vs...))
}
// DefaultMappedModelNotIn applies the NotIn predicate on the "default_mapped_model" field.
func DefaultMappedModelNotIn(vs ...string) predicate.Group {
return predicate.Group(sql.FieldNotIn(FieldDefaultMappedModel, vs...))
}
// DefaultMappedModelGT applies the GT predicate on the "default_mapped_model" field.
func DefaultMappedModelGT(v string) predicate.Group {
return predicate.Group(sql.FieldGT(FieldDefaultMappedModel, v))
}
// DefaultMappedModelGTE applies the GTE predicate on the "default_mapped_model" field.
func DefaultMappedModelGTE(v string) predicate.Group {
return predicate.Group(sql.FieldGTE(FieldDefaultMappedModel, v))
}
// DefaultMappedModelLT applies the LT predicate on the "default_mapped_model" field.
func DefaultMappedModelLT(v string) predicate.Group {
return predicate.Group(sql.FieldLT(FieldDefaultMappedModel, v))
}
// DefaultMappedModelLTE applies the LTE predicate on the "default_mapped_model" field.
func DefaultMappedModelLTE(v string) predicate.Group {
return predicate.Group(sql.FieldLTE(FieldDefaultMappedModel, v))
}
// DefaultMappedModelContains applies the Contains predicate on the "default_mapped_model" field.
func DefaultMappedModelContains(v string) predicate.Group {
return predicate.Group(sql.FieldContains(FieldDefaultMappedModel, v))
}
// DefaultMappedModelHasPrefix applies the HasPrefix predicate on the "default_mapped_model" field.
func DefaultMappedModelHasPrefix(v string) predicate.Group {
return predicate.Group(sql.FieldHasPrefix(FieldDefaultMappedModel, v))
}
// DefaultMappedModelHasSuffix applies the HasSuffix predicate on the "default_mapped_model" field.
func DefaultMappedModelHasSuffix(v string) predicate.Group {
return predicate.Group(sql.FieldHasSuffix(FieldDefaultMappedModel, v))
}
// DefaultMappedModelEqualFold applies the EqualFold predicate on the "default_mapped_model" field.
func DefaultMappedModelEqualFold(v string) predicate.Group {
return predicate.Group(sql.FieldEqualFold(FieldDefaultMappedModel, v))
}
// DefaultMappedModelContainsFold applies the ContainsFold predicate on the "default_mapped_model" field.
func DefaultMappedModelContainsFold(v string) predicate.Group {
return predicate.Group(sql.FieldContainsFold(FieldDefaultMappedModel, v))
}
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
func HasAPIKeys() predicate.Group {
return predicate.Group(func(s *sql.Selector) {

View File

@@ -18,6 +18,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// GroupCreate is the builder for creating a Group entity.
@@ -354,6 +355,76 @@ func (_c *GroupCreate) SetNillableSortOrder(v *int) *GroupCreate {
return _c
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (_c *GroupCreate) SetAllowMessagesDispatch(v bool) *GroupCreate {
_c.mutation.SetAllowMessagesDispatch(v)
return _c
}
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
func (_c *GroupCreate) SetNillableAllowMessagesDispatch(v *bool) *GroupCreate {
if v != nil {
_c.SetAllowMessagesDispatch(*v)
}
return _c
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (_c *GroupCreate) SetRequireOauthOnly(v bool) *GroupCreate {
_c.mutation.SetRequireOauthOnly(v)
return _c
}
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
func (_c *GroupCreate) SetNillableRequireOauthOnly(v *bool) *GroupCreate {
if v != nil {
_c.SetRequireOauthOnly(*v)
}
return _c
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (_c *GroupCreate) SetRequirePrivacySet(v bool) *GroupCreate {
_c.mutation.SetRequirePrivacySet(v)
return _c
}
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
func (_c *GroupCreate) SetNillableRequirePrivacySet(v *bool) *GroupCreate {
if v != nil {
_c.SetRequirePrivacySet(*v)
}
return _c
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (_c *GroupCreate) SetDefaultMappedModel(v string) *GroupCreate {
_c.mutation.SetDefaultMappedModel(v)
return _c
}
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
func (_c *GroupCreate) SetNillableDefaultMappedModel(v *string) *GroupCreate {
if v != nil {
_c.SetDefaultMappedModel(*v)
}
return _c
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (_c *GroupCreate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupCreate {
_c.mutation.SetMessagesDispatchModelConfig(v)
return _c
}
// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
func (_c *GroupCreate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupCreate {
if v != nil {
_c.SetMessagesDispatchModelConfig(*v)
}
return _c
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
_c.mutation.AddAPIKeyIDs(ids...)
@@ -539,6 +610,26 @@ func (_c *GroupCreate) defaults() error {
v := group.DefaultSortOrder
_c.mutation.SetSortOrder(v)
}
if _, ok := _c.mutation.AllowMessagesDispatch(); !ok {
v := group.DefaultAllowMessagesDispatch
_c.mutation.SetAllowMessagesDispatch(v)
}
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
v := group.DefaultRequireOauthOnly
_c.mutation.SetRequireOauthOnly(v)
}
if _, ok := _c.mutation.RequirePrivacySet(); !ok {
v := group.DefaultRequirePrivacySet
_c.mutation.SetRequirePrivacySet(v)
}
if _, ok := _c.mutation.DefaultMappedModel(); !ok {
v := group.DefaultDefaultMappedModel
_c.mutation.SetDefaultMappedModel(v)
}
if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok {
v := group.DefaultMessagesDispatchModelConfig
_c.mutation.SetMessagesDispatchModelConfig(v)
}
return nil
}
@@ -606,6 +697,26 @@ func (_c *GroupCreate) check() error {
if _, ok := _c.mutation.SortOrder(); !ok {
return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "Group.sort_order"`)}
}
if _, ok := _c.mutation.AllowMessagesDispatch(); !ok {
return &ValidationError{Name: "allow_messages_dispatch", err: errors.New(`ent: missing required field "Group.allow_messages_dispatch"`)}
}
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
return &ValidationError{Name: "require_oauth_only", err: errors.New(`ent: missing required field "Group.require_oauth_only"`)}
}
if _, ok := _c.mutation.RequirePrivacySet(); !ok {
return &ValidationError{Name: "require_privacy_set", err: errors.New(`ent: missing required field "Group.require_privacy_set"`)}
}
if _, ok := _c.mutation.DefaultMappedModel(); !ok {
return &ValidationError{Name: "default_mapped_model", err: errors.New(`ent: missing required field "Group.default_mapped_model"`)}
}
if v, ok := _c.mutation.DefaultMappedModel(); ok {
if err := group.DefaultMappedModelValidator(v); err != nil {
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
}
}
if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok {
return &ValidationError{Name: "messages_dispatch_model_config", err: errors.New(`ent: missing required field "Group.messages_dispatch_model_config"`)}
}
return nil
}
@@ -733,6 +844,26 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
_spec.SetField(group.FieldSortOrder, field.TypeInt, value)
_node.SortOrder = value
}
if value, ok := _c.mutation.AllowMessagesDispatch(); ok {
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
_node.AllowMessagesDispatch = value
}
if value, ok := _c.mutation.RequireOauthOnly(); ok {
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
_node.RequireOauthOnly = value
}
if value, ok := _c.mutation.RequirePrivacySet(); ok {
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
_node.RequirePrivacySet = value
}
if value, ok := _c.mutation.DefaultMappedModel(); ok {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
_node.DefaultMappedModel = value
}
if value, ok := _c.mutation.MessagesDispatchModelConfig(); ok {
_spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
_node.MessagesDispatchModelConfig = value
}
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1309,6 +1440,66 @@ func (u *GroupUpsert) AddSortOrder(v int) *GroupUpsert {
return u
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (u *GroupUpsert) SetAllowMessagesDispatch(v bool) *GroupUpsert {
u.Set(group.FieldAllowMessagesDispatch, v)
return u
}
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
func (u *GroupUpsert) UpdateAllowMessagesDispatch() *GroupUpsert {
u.SetExcluded(group.FieldAllowMessagesDispatch)
return u
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (u *GroupUpsert) SetRequireOauthOnly(v bool) *GroupUpsert {
u.Set(group.FieldRequireOauthOnly, v)
return u
}
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
func (u *GroupUpsert) UpdateRequireOauthOnly() *GroupUpsert {
u.SetExcluded(group.FieldRequireOauthOnly)
return u
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (u *GroupUpsert) SetRequirePrivacySet(v bool) *GroupUpsert {
u.Set(group.FieldRequirePrivacySet, v)
return u
}
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
func (u *GroupUpsert) UpdateRequirePrivacySet() *GroupUpsert {
u.SetExcluded(group.FieldRequirePrivacySet)
return u
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (u *GroupUpsert) SetDefaultMappedModel(v string) *GroupUpsert {
u.Set(group.FieldDefaultMappedModel, v)
return u
}
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
func (u *GroupUpsert) UpdateDefaultMappedModel() *GroupUpsert {
u.SetExcluded(group.FieldDefaultMappedModel)
return u
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (u *GroupUpsert) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsert {
u.Set(group.FieldMessagesDispatchModelConfig, v)
return u
}
// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
func (u *GroupUpsert) UpdateMessagesDispatchModelConfig() *GroupUpsert {
u.SetExcluded(group.FieldMessagesDispatchModelConfig)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
@@ -1844,6 +2035,76 @@ func (u *GroupUpsertOne) UpdateSortOrder() *GroupUpsertOne {
})
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (u *GroupUpsertOne) SetAllowMessagesDispatch(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetAllowMessagesDispatch(v)
})
}
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateAllowMessagesDispatch() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateAllowMessagesDispatch()
})
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (u *GroupUpsertOne) SetRequireOauthOnly(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetRequireOauthOnly(v)
})
}
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateRequireOauthOnly() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateRequireOauthOnly()
})
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (u *GroupUpsertOne) SetRequirePrivacySet(v bool) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetRequirePrivacySet(v)
})
}
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateRequirePrivacySet() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateRequirePrivacySet()
})
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (u *GroupUpsertOne) SetDefaultMappedModel(v string) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetDefaultMappedModel(v)
})
}
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateDefaultMappedModel() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateDefaultMappedModel()
})
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (u *GroupUpsertOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.SetMessagesDispatchModelConfig(v)
})
}
// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
func (u *GroupUpsertOne) UpdateMessagesDispatchModelConfig() *GroupUpsertOne {
return u.Update(func(s *GroupUpsert) {
s.UpdateMessagesDispatchModelConfig()
})
}
// Exec executes the query.
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
@@ -2545,6 +2806,76 @@ func (u *GroupUpsertBulk) UpdateSortOrder() *GroupUpsertBulk {
})
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (u *GroupUpsertBulk) SetAllowMessagesDispatch(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetAllowMessagesDispatch(v)
})
}
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateAllowMessagesDispatch() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateAllowMessagesDispatch()
})
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (u *GroupUpsertBulk) SetRequireOauthOnly(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetRequireOauthOnly(v)
})
}
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateRequireOauthOnly() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateRequireOauthOnly()
})
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (u *GroupUpsertBulk) SetRequirePrivacySet(v bool) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetRequirePrivacySet(v)
})
}
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateRequirePrivacySet() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateRequirePrivacySet()
})
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (u *GroupUpsertBulk) SetDefaultMappedModel(v string) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetDefaultMappedModel(v)
})
}
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateDefaultMappedModel() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateDefaultMappedModel()
})
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (u *GroupUpsertBulk) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.SetMessagesDispatchModelConfig(v)
})
}
// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
func (u *GroupUpsertBulk) UpdateMessagesDispatchModelConfig() *GroupUpsertBulk {
return u.Update(func(s *GroupUpsert) {
s.UpdateMessagesDispatchModelConfig()
})
}
// Exec executes the query.
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {

View File

@@ -20,6 +20,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// GroupUpdate is the builder for updating Group entities.
@@ -496,6 +497,76 @@ func (_u *GroupUpdate) AddSortOrder(v int) *GroupUpdate {
return _u
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (_u *GroupUpdate) SetAllowMessagesDispatch(v bool) *GroupUpdate {
_u.mutation.SetAllowMessagesDispatch(v)
return _u
}
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableAllowMessagesDispatch(v *bool) *GroupUpdate {
if v != nil {
_u.SetAllowMessagesDispatch(*v)
}
return _u
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (_u *GroupUpdate) SetRequireOauthOnly(v bool) *GroupUpdate {
_u.mutation.SetRequireOauthOnly(v)
return _u
}
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableRequireOauthOnly(v *bool) *GroupUpdate {
if v != nil {
_u.SetRequireOauthOnly(*v)
}
return _u
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (_u *GroupUpdate) SetRequirePrivacySet(v bool) *GroupUpdate {
_u.mutation.SetRequirePrivacySet(v)
return _u
}
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableRequirePrivacySet(v *bool) *GroupUpdate {
if v != nil {
_u.SetRequirePrivacySet(*v)
}
return _u
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (_u *GroupUpdate) SetDefaultMappedModel(v string) *GroupUpdate {
_u.mutation.SetDefaultMappedModel(v)
return _u
}
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableDefaultMappedModel(v *string) *GroupUpdate {
if v != nil {
_u.SetDefaultMappedModel(*v)
}
return _u
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (_u *GroupUpdate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate {
_u.mutation.SetMessagesDispatchModelConfig(v)
return _u
}
// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate {
if v != nil {
_u.SetMessagesDispatchModelConfig(*v)
}
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -781,6 +852,11 @@ func (_u *GroupUpdate) check() error {
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
}
}
if v, ok := _u.mutation.DefaultMappedModel(); ok {
if err := group.DefaultMappedModelValidator(v); err != nil {
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
}
}
return nil
}
@@ -939,6 +1015,21 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AllowMessagesDispatch(); ok {
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
}
if value, ok := _u.mutation.RequireOauthOnly(); ok {
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
}
if value, ok := _u.mutation.RequirePrivacySet(); ok {
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
}
if value, ok := _u.mutation.DefaultMappedModel(); ok {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
}
if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok {
_spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1714,6 +1805,76 @@ func (_u *GroupUpdateOne) AddSortOrder(v int) *GroupUpdateOne {
return _u
}
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
func (_u *GroupUpdateOne) SetAllowMessagesDispatch(v bool) *GroupUpdateOne {
_u.mutation.SetAllowMessagesDispatch(v)
return _u
}
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableAllowMessagesDispatch(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetAllowMessagesDispatch(*v)
}
return _u
}
// SetRequireOauthOnly sets the "require_oauth_only" field.
func (_u *GroupUpdateOne) SetRequireOauthOnly(v bool) *GroupUpdateOne {
_u.mutation.SetRequireOauthOnly(v)
return _u
}
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableRequireOauthOnly(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetRequireOauthOnly(*v)
}
return _u
}
// SetRequirePrivacySet sets the "require_privacy_set" field.
func (_u *GroupUpdateOne) SetRequirePrivacySet(v bool) *GroupUpdateOne {
_u.mutation.SetRequirePrivacySet(v)
return _u
}
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableRequirePrivacySet(v *bool) *GroupUpdateOne {
if v != nil {
_u.SetRequirePrivacySet(*v)
}
return _u
}
// SetDefaultMappedModel sets the "default_mapped_model" field.
func (_u *GroupUpdateOne) SetDefaultMappedModel(v string) *GroupUpdateOne {
_u.mutation.SetDefaultMappedModel(v)
return _u
}
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableDefaultMappedModel(v *string) *GroupUpdateOne {
if v != nil {
_u.SetDefaultMappedModel(*v)
}
return _u
}
// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
func (_u *GroupUpdateOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne {
_u.mutation.SetMessagesDispatchModelConfig(v)
return _u
}
// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne {
if v != nil {
_u.SetMessagesDispatchModelConfig(*v)
}
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -2012,6 +2173,11 @@ func (_u *GroupUpdateOne) check() error {
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
}
}
if v, ok := _u.mutation.DefaultMappedModel(); ok {
if err := group.DefaultMappedModelValidator(v); err != nil {
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
}
}
return nil
}
@@ -2187,6 +2353,21 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AllowMessagesDispatch(); ok {
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
}
if value, ok := _u.mutation.RequireOauthOnly(); ok {
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
}
if value, ok := _u.mutation.RequirePrivacySet(); ok {
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
}
if value, ok := _u.mutation.DefaultMappedModel(); ok {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
}
if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok {
_spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,

View File

@@ -93,6 +93,54 @@ func (f GroupFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.GroupMutation", m)
}
// The IdempotencyRecordFunc type is an adapter to allow the use of ordinary
// function as IdempotencyRecord mutator.
type IdempotencyRecordFunc func(context.Context, *ent.IdempotencyRecordMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f IdempotencyRecordFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.IdempotencyRecordMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.IdempotencyRecordMutation", m)
}
// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary
// function as PaymentAuditLog mutator.
type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentAuditLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentAuditLogMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentAuditLogMutation", m)
}
// The PaymentOrderFunc type is an adapter to allow the use of ordinary
// function as PaymentOrder mutator.
type PaymentOrderFunc func(context.Context, *ent.PaymentOrderMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentOrderFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentOrderMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentOrderMutation", m)
}
// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary
// function as PaymentProviderInstance mutator.
type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentProviderInstanceFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentProviderInstanceMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentProviderInstanceMutation", m)
}
// The PromoCodeFunc type is an adapter to allow the use of ordinary
// function as PromoCode mutator.
type PromoCodeFunc func(context.Context, *ent.PromoCodeMutation) (ent.Value, error)
@@ -141,6 +189,18 @@ func (f RedeemCodeFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value,
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.RedeemCodeMutation", m)
}
// The SecuritySecretFunc type is an adapter to allow the use of ordinary
// function as SecuritySecret mutator.
type SecuritySecretFunc func(context.Context, *ent.SecuritySecretMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f SecuritySecretFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.SecuritySecretMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SecuritySecretMutation", m)
}
// The SettingFunc type is an adapter to allow the use of ordinary
// function as Setting mutator.
type SettingFunc func(context.Context, *ent.SettingMutation) (ent.Value, error)
@@ -153,6 +213,30 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m)
}
// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary
// function as SubscriptionPlan mutator.
type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f SubscriptionPlanFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.SubscriptionPlanMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SubscriptionPlanMutation", m)
}
// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary
// function as TLSFingerprintProfile mutator.
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f TLSFingerprintProfileFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.TLSFingerprintProfileMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TLSFingerprintProfileMutation", m)
}
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary
// function as UsageCleanupTask mutator.
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskMutation) (ent.Value, error)

View File

@@ -0,0 +1,228 @@
// 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/idempotencyrecord"
)
// IdempotencyRecord is the model entity for the IdempotencyRecord schema.
type IdempotencyRecord struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,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"`
// Scope holds the value of the "scope" field.
Scope string `json:"scope,omitempty"`
// IdempotencyKeyHash holds the value of the "idempotency_key_hash" field.
IdempotencyKeyHash string `json:"idempotency_key_hash,omitempty"`
// RequestFingerprint holds the value of the "request_fingerprint" field.
RequestFingerprint string `json:"request_fingerprint,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// ResponseStatus holds the value of the "response_status" field.
ResponseStatus *int `json:"response_status,omitempty"`
// ResponseBody holds the value of the "response_body" field.
ResponseBody *string `json:"response_body,omitempty"`
// ErrorReason holds the value of the "error_reason" field.
ErrorReason *string `json:"error_reason,omitempty"`
// LockedUntil holds the value of the "locked_until" field.
LockedUntil *time.Time `json:"locked_until,omitempty"`
// ExpiresAt holds the value of the "expires_at" field.
ExpiresAt time.Time `json:"expires_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*IdempotencyRecord) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case idempotencyrecord.FieldID, idempotencyrecord.FieldResponseStatus:
values[i] = new(sql.NullInt64)
case idempotencyrecord.FieldScope, idempotencyrecord.FieldIdempotencyKeyHash, idempotencyrecord.FieldRequestFingerprint, idempotencyrecord.FieldStatus, idempotencyrecord.FieldResponseBody, idempotencyrecord.FieldErrorReason:
values[i] = new(sql.NullString)
case idempotencyrecord.FieldCreatedAt, idempotencyrecord.FieldUpdatedAt, idempotencyrecord.FieldLockedUntil, idempotencyrecord.FieldExpiresAt:
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 IdempotencyRecord fields.
func (_m *IdempotencyRecord) 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 idempotencyrecord.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 idempotencyrecord.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 idempotencyrecord.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 idempotencyrecord.FieldScope:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field scope", values[i])
} else if value.Valid {
_m.Scope = value.String
}
case idempotencyrecord.FieldIdempotencyKeyHash:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field idempotency_key_hash", values[i])
} else if value.Valid {
_m.IdempotencyKeyHash = value.String
}
case idempotencyrecord.FieldRequestFingerprint:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field request_fingerprint", values[i])
} else if value.Valid {
_m.RequestFingerprint = value.String
}
case idempotencyrecord.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 idempotencyrecord.FieldResponseStatus:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field response_status", values[i])
} else if value.Valid {
_m.ResponseStatus = new(int)
*_m.ResponseStatus = int(value.Int64)
}
case idempotencyrecord.FieldResponseBody:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field response_body", values[i])
} else if value.Valid {
_m.ResponseBody = new(string)
*_m.ResponseBody = value.String
}
case idempotencyrecord.FieldErrorReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field error_reason", values[i])
} else if value.Valid {
_m.ErrorReason = new(string)
*_m.ErrorReason = value.String
}
case idempotencyrecord.FieldLockedUntil:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field locked_until", values[i])
} else if value.Valid {
_m.LockedUntil = new(time.Time)
*_m.LockedUntil = value.Time
}
case idempotencyrecord.FieldExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
} else if value.Valid {
_m.ExpiresAt = 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 IdempotencyRecord.
// This includes values selected through modifiers, order, etc.
func (_m *IdempotencyRecord) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this IdempotencyRecord.
// Note that you need to call IdempotencyRecord.Unwrap() before calling this method if this IdempotencyRecord
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *IdempotencyRecord) Update() *IdempotencyRecordUpdateOne {
return NewIdempotencyRecordClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the IdempotencyRecord 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 *IdempotencyRecord) Unwrap() *IdempotencyRecord {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: IdempotencyRecord is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *IdempotencyRecord) String() string {
var builder strings.Builder
builder.WriteString("IdempotencyRecord(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
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(", ")
builder.WriteString("scope=")
builder.WriteString(_m.Scope)
builder.WriteString(", ")
builder.WriteString("idempotency_key_hash=")
builder.WriteString(_m.IdempotencyKeyHash)
builder.WriteString(", ")
builder.WriteString("request_fingerprint=")
builder.WriteString(_m.RequestFingerprint)
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
if v := _m.ResponseStatus; v != nil {
builder.WriteString("response_status=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.ResponseBody; v != nil {
builder.WriteString("response_body=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.ErrorReason; v != nil {
builder.WriteString("error_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.LockedUntil; v != nil {
builder.WriteString("locked_until=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("expires_at=")
builder.WriteString(_m.ExpiresAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// IdempotencyRecords is a parsable slice of IdempotencyRecord.
type IdempotencyRecords []*IdempotencyRecord

View File

@@ -0,0 +1,148 @@
// Code generated by ent, DO NOT EDIT.
package idempotencyrecord
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the idempotencyrecord type in the database.
Label = "idempotency_record"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// 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"
// FieldScope holds the string denoting the scope field in the database.
FieldScope = "scope"
// FieldIdempotencyKeyHash holds the string denoting the idempotency_key_hash field in the database.
FieldIdempotencyKeyHash = "idempotency_key_hash"
// FieldRequestFingerprint holds the string denoting the request_fingerprint field in the database.
FieldRequestFingerprint = "request_fingerprint"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldResponseStatus holds the string denoting the response_status field in the database.
FieldResponseStatus = "response_status"
// FieldResponseBody holds the string denoting the response_body field in the database.
FieldResponseBody = "response_body"
// FieldErrorReason holds the string denoting the error_reason field in the database.
FieldErrorReason = "error_reason"
// FieldLockedUntil holds the string denoting the locked_until field in the database.
FieldLockedUntil = "locked_until"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// Table holds the table name of the idempotencyrecord in the database.
Table = "idempotency_records"
)
// Columns holds all SQL columns for idempotencyrecord fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldScope,
FieldIdempotencyKeyHash,
FieldRequestFingerprint,
FieldStatus,
FieldResponseStatus,
FieldResponseBody,
FieldErrorReason,
FieldLockedUntil,
FieldExpiresAt,
}
// 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 (
// 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
// ScopeValidator is a validator for the "scope" field. It is called by the builders before save.
ScopeValidator func(string) error
// IdempotencyKeyHashValidator is a validator for the "idempotency_key_hash" field. It is called by the builders before save.
IdempotencyKeyHashValidator func(string) error
// RequestFingerprintValidator is a validator for the "request_fingerprint" field. It is called by the builders before save.
RequestFingerprintValidator func(string) error
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error
// ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save.
ErrorReasonValidator func(string) error
)
// OrderOption defines the ordering options for the IdempotencyRecord 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()
}
// 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()
}
// ByScope orders the results by the scope field.
func ByScope(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldScope, opts...).ToFunc()
}
// ByIdempotencyKeyHash orders the results by the idempotency_key_hash field.
func ByIdempotencyKeyHash(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIdempotencyKeyHash, opts...).ToFunc()
}
// ByRequestFingerprint orders the results by the request_fingerprint field.
func ByRequestFingerprint(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRequestFingerprint, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByResponseStatus orders the results by the response_status field.
func ByResponseStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldResponseStatus, opts...).ToFunc()
}
// ByResponseBody orders the results by the response_body field.
func ByResponseBody(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldResponseBody, opts...).ToFunc()
}
// ByErrorReason orders the results by the error_reason field.
func ByErrorReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldErrorReason, opts...).ToFunc()
}
// ByLockedUntil orders the results by the locked_until field.
func ByLockedUntil(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLockedUntil, opts...).ToFunc()
}
// ByExpiresAt orders the results by the expires_at field.
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}

View File

@@ -0,0 +1,755 @@
// Code generated by ent, DO NOT EDIT.
package idempotencyrecord
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.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldID, id))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldUpdatedAt, v))
}
// Scope applies equality check predicate on the "scope" field. It's identical to ScopeEQ.
func Scope(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldScope, v))
}
// IdempotencyKeyHash applies equality check predicate on the "idempotency_key_hash" field. It's identical to IdempotencyKeyHashEQ.
func IdempotencyKeyHash(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldIdempotencyKeyHash, v))
}
// RequestFingerprint applies equality check predicate on the "request_fingerprint" field. It's identical to RequestFingerprintEQ.
func RequestFingerprint(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldRequestFingerprint, v))
}
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
func Status(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldStatus, v))
}
// ResponseStatus applies equality check predicate on the "response_status" field. It's identical to ResponseStatusEQ.
func ResponseStatus(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseStatus, v))
}
// ResponseBody applies equality check predicate on the "response_body" field. It's identical to ResponseBodyEQ.
func ResponseBody(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseBody, v))
}
// ErrorReason applies equality check predicate on the "error_reason" field. It's identical to ErrorReasonEQ.
func ErrorReason(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldErrorReason, v))
}
// LockedUntil applies equality check predicate on the "locked_until" field. It's identical to LockedUntilEQ.
func LockedUntil(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldLockedUntil, v))
}
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
func ExpiresAt(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldExpiresAt, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldUpdatedAt, v))
}
// ScopeEQ applies the EQ predicate on the "scope" field.
func ScopeEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldScope, v))
}
// ScopeNEQ applies the NEQ predicate on the "scope" field.
func ScopeNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldScope, v))
}
// ScopeIn applies the In predicate on the "scope" field.
func ScopeIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldScope, vs...))
}
// ScopeNotIn applies the NotIn predicate on the "scope" field.
func ScopeNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldScope, vs...))
}
// ScopeGT applies the GT predicate on the "scope" field.
func ScopeGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldScope, v))
}
// ScopeGTE applies the GTE predicate on the "scope" field.
func ScopeGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldScope, v))
}
// ScopeLT applies the LT predicate on the "scope" field.
func ScopeLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldScope, v))
}
// ScopeLTE applies the LTE predicate on the "scope" field.
func ScopeLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldScope, v))
}
// ScopeContains applies the Contains predicate on the "scope" field.
func ScopeContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldScope, v))
}
// ScopeHasPrefix applies the HasPrefix predicate on the "scope" field.
func ScopeHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldScope, v))
}
// ScopeHasSuffix applies the HasSuffix predicate on the "scope" field.
func ScopeHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldScope, v))
}
// ScopeEqualFold applies the EqualFold predicate on the "scope" field.
func ScopeEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldScope, v))
}
// ScopeContainsFold applies the ContainsFold predicate on the "scope" field.
func ScopeContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldScope, v))
}
// IdempotencyKeyHashEQ applies the EQ predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashNEQ applies the NEQ predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashIn applies the In predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldIdempotencyKeyHash, vs...))
}
// IdempotencyKeyHashNotIn applies the NotIn predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldIdempotencyKeyHash, vs...))
}
// IdempotencyKeyHashGT applies the GT predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashGTE applies the GTE predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashLT applies the LT predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashLTE applies the LTE predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashContains applies the Contains predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashHasPrefix applies the HasPrefix predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashHasSuffix applies the HasSuffix predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashEqualFold applies the EqualFold predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldIdempotencyKeyHash, v))
}
// IdempotencyKeyHashContainsFold applies the ContainsFold predicate on the "idempotency_key_hash" field.
func IdempotencyKeyHashContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldIdempotencyKeyHash, v))
}
// RequestFingerprintEQ applies the EQ predicate on the "request_fingerprint" field.
func RequestFingerprintEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldRequestFingerprint, v))
}
// RequestFingerprintNEQ applies the NEQ predicate on the "request_fingerprint" field.
func RequestFingerprintNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldRequestFingerprint, v))
}
// RequestFingerprintIn applies the In predicate on the "request_fingerprint" field.
func RequestFingerprintIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldRequestFingerprint, vs...))
}
// RequestFingerprintNotIn applies the NotIn predicate on the "request_fingerprint" field.
func RequestFingerprintNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldRequestFingerprint, vs...))
}
// RequestFingerprintGT applies the GT predicate on the "request_fingerprint" field.
func RequestFingerprintGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldRequestFingerprint, v))
}
// RequestFingerprintGTE applies the GTE predicate on the "request_fingerprint" field.
func RequestFingerprintGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldRequestFingerprint, v))
}
// RequestFingerprintLT applies the LT predicate on the "request_fingerprint" field.
func RequestFingerprintLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldRequestFingerprint, v))
}
// RequestFingerprintLTE applies the LTE predicate on the "request_fingerprint" field.
func RequestFingerprintLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldRequestFingerprint, v))
}
// RequestFingerprintContains applies the Contains predicate on the "request_fingerprint" field.
func RequestFingerprintContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldRequestFingerprint, v))
}
// RequestFingerprintHasPrefix applies the HasPrefix predicate on the "request_fingerprint" field.
func RequestFingerprintHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldRequestFingerprint, v))
}
// RequestFingerprintHasSuffix applies the HasSuffix predicate on the "request_fingerprint" field.
func RequestFingerprintHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldRequestFingerprint, v))
}
// RequestFingerprintEqualFold applies the EqualFold predicate on the "request_fingerprint" field.
func RequestFingerprintEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldRequestFingerprint, v))
}
// RequestFingerprintContainsFold applies the ContainsFold predicate on the "request_fingerprint" field.
func RequestFingerprintContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldRequestFingerprint, v))
}
// StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldStatus, v))
}
// StatusNEQ applies the NEQ predicate on the "status" field.
func StatusNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldStatus, v))
}
// StatusIn applies the In predicate on the "status" field.
func StatusIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldStatus, vs...))
}
// StatusNotIn applies the NotIn predicate on the "status" field.
func StatusNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldStatus, vs...))
}
// StatusGT applies the GT predicate on the "status" field.
func StatusGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldStatus, v))
}
// StatusGTE applies the GTE predicate on the "status" field.
func StatusGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldStatus, v))
}
// StatusLT applies the LT predicate on the "status" field.
func StatusLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldStatus, v))
}
// StatusLTE applies the LTE predicate on the "status" field.
func StatusLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldStatus, v))
}
// StatusContains applies the Contains predicate on the "status" field.
func StatusContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldStatus, v))
}
// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
func StatusHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldStatus, v))
}
// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
func StatusHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldStatus, v))
}
// StatusEqualFold applies the EqualFold predicate on the "status" field.
func StatusEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldStatus, v))
}
// StatusContainsFold applies the ContainsFold predicate on the "status" field.
func StatusContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldStatus, v))
}
// ResponseStatusEQ applies the EQ predicate on the "response_status" field.
func ResponseStatusEQ(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseStatus, v))
}
// ResponseStatusNEQ applies the NEQ predicate on the "response_status" field.
func ResponseStatusNEQ(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldResponseStatus, v))
}
// ResponseStatusIn applies the In predicate on the "response_status" field.
func ResponseStatusIn(vs ...int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldResponseStatus, vs...))
}
// ResponseStatusNotIn applies the NotIn predicate on the "response_status" field.
func ResponseStatusNotIn(vs ...int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldResponseStatus, vs...))
}
// ResponseStatusGT applies the GT predicate on the "response_status" field.
func ResponseStatusGT(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldResponseStatus, v))
}
// ResponseStatusGTE applies the GTE predicate on the "response_status" field.
func ResponseStatusGTE(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldResponseStatus, v))
}
// ResponseStatusLT applies the LT predicate on the "response_status" field.
func ResponseStatusLT(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldResponseStatus, v))
}
// ResponseStatusLTE applies the LTE predicate on the "response_status" field.
func ResponseStatusLTE(v int) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldResponseStatus, v))
}
// ResponseStatusIsNil applies the IsNil predicate on the "response_status" field.
func ResponseStatusIsNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldResponseStatus))
}
// ResponseStatusNotNil applies the NotNil predicate on the "response_status" field.
func ResponseStatusNotNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldResponseStatus))
}
// ResponseBodyEQ applies the EQ predicate on the "response_body" field.
func ResponseBodyEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseBody, v))
}
// ResponseBodyNEQ applies the NEQ predicate on the "response_body" field.
func ResponseBodyNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldResponseBody, v))
}
// ResponseBodyIn applies the In predicate on the "response_body" field.
func ResponseBodyIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldResponseBody, vs...))
}
// ResponseBodyNotIn applies the NotIn predicate on the "response_body" field.
func ResponseBodyNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldResponseBody, vs...))
}
// ResponseBodyGT applies the GT predicate on the "response_body" field.
func ResponseBodyGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldResponseBody, v))
}
// ResponseBodyGTE applies the GTE predicate on the "response_body" field.
func ResponseBodyGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldResponseBody, v))
}
// ResponseBodyLT applies the LT predicate on the "response_body" field.
func ResponseBodyLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldResponseBody, v))
}
// ResponseBodyLTE applies the LTE predicate on the "response_body" field.
func ResponseBodyLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldResponseBody, v))
}
// ResponseBodyContains applies the Contains predicate on the "response_body" field.
func ResponseBodyContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldResponseBody, v))
}
// ResponseBodyHasPrefix applies the HasPrefix predicate on the "response_body" field.
func ResponseBodyHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldResponseBody, v))
}
// ResponseBodyHasSuffix applies the HasSuffix predicate on the "response_body" field.
func ResponseBodyHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldResponseBody, v))
}
// ResponseBodyIsNil applies the IsNil predicate on the "response_body" field.
func ResponseBodyIsNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldResponseBody))
}
// ResponseBodyNotNil applies the NotNil predicate on the "response_body" field.
func ResponseBodyNotNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldResponseBody))
}
// ResponseBodyEqualFold applies the EqualFold predicate on the "response_body" field.
func ResponseBodyEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldResponseBody, v))
}
// ResponseBodyContainsFold applies the ContainsFold predicate on the "response_body" field.
func ResponseBodyContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldResponseBody, v))
}
// ErrorReasonEQ applies the EQ predicate on the "error_reason" field.
func ErrorReasonEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldErrorReason, v))
}
// ErrorReasonNEQ applies the NEQ predicate on the "error_reason" field.
func ErrorReasonNEQ(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldErrorReason, v))
}
// ErrorReasonIn applies the In predicate on the "error_reason" field.
func ErrorReasonIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldErrorReason, vs...))
}
// ErrorReasonNotIn applies the NotIn predicate on the "error_reason" field.
func ErrorReasonNotIn(vs ...string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldErrorReason, vs...))
}
// ErrorReasonGT applies the GT predicate on the "error_reason" field.
func ErrorReasonGT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldErrorReason, v))
}
// ErrorReasonGTE applies the GTE predicate on the "error_reason" field.
func ErrorReasonGTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldErrorReason, v))
}
// ErrorReasonLT applies the LT predicate on the "error_reason" field.
func ErrorReasonLT(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldErrorReason, v))
}
// ErrorReasonLTE applies the LTE predicate on the "error_reason" field.
func ErrorReasonLTE(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldErrorReason, v))
}
// ErrorReasonContains applies the Contains predicate on the "error_reason" field.
func ErrorReasonContains(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContains(FieldErrorReason, v))
}
// ErrorReasonHasPrefix applies the HasPrefix predicate on the "error_reason" field.
func ErrorReasonHasPrefix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldErrorReason, v))
}
// ErrorReasonHasSuffix applies the HasSuffix predicate on the "error_reason" field.
func ErrorReasonHasSuffix(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldErrorReason, v))
}
// ErrorReasonIsNil applies the IsNil predicate on the "error_reason" field.
func ErrorReasonIsNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldErrorReason))
}
// ErrorReasonNotNil applies the NotNil predicate on the "error_reason" field.
func ErrorReasonNotNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldErrorReason))
}
// ErrorReasonEqualFold applies the EqualFold predicate on the "error_reason" field.
func ErrorReasonEqualFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldErrorReason, v))
}
// ErrorReasonContainsFold applies the ContainsFold predicate on the "error_reason" field.
func ErrorReasonContainsFold(v string) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldErrorReason, v))
}
// LockedUntilEQ applies the EQ predicate on the "locked_until" field.
func LockedUntilEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldLockedUntil, v))
}
// LockedUntilNEQ applies the NEQ predicate on the "locked_until" field.
func LockedUntilNEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldLockedUntil, v))
}
// LockedUntilIn applies the In predicate on the "locked_until" field.
func LockedUntilIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldLockedUntil, vs...))
}
// LockedUntilNotIn applies the NotIn predicate on the "locked_until" field.
func LockedUntilNotIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldLockedUntil, vs...))
}
// LockedUntilGT applies the GT predicate on the "locked_until" field.
func LockedUntilGT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldLockedUntil, v))
}
// LockedUntilGTE applies the GTE predicate on the "locked_until" field.
func LockedUntilGTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldLockedUntil, v))
}
// LockedUntilLT applies the LT predicate on the "locked_until" field.
func LockedUntilLT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldLockedUntil, v))
}
// LockedUntilLTE applies the LTE predicate on the "locked_until" field.
func LockedUntilLTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldLockedUntil, v))
}
// LockedUntilIsNil applies the IsNil predicate on the "locked_until" field.
func LockedUntilIsNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldLockedUntil))
}
// LockedUntilNotNil applies the NotNil predicate on the "locked_until" field.
func LockedUntilNotNil() predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldLockedUntil))
}
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
func ExpiresAtEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldEQ(FieldExpiresAt, v))
}
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
func ExpiresAtNEQ(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldExpiresAt, v))
}
// ExpiresAtIn applies the In predicate on the "expires_at" field.
func ExpiresAtIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldIn(FieldExpiresAt, vs...))
}
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
func ExpiresAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldExpiresAt, vs...))
}
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
func ExpiresAtGT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGT(FieldExpiresAt, v))
}
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
func ExpiresAtGTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldGTE(FieldExpiresAt, v))
}
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
func ExpiresAtLT(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLT(FieldExpiresAt, v))
}
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
func ExpiresAtLTE(v time.Time) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.FieldLTE(FieldExpiresAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.IdempotencyRecord) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.IdempotencyRecord) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.IdempotencyRecord) predicate.IdempotencyRecord {
return predicate.IdempotencyRecord(sql.NotPredicates(p))
}

File diff suppressed because it is too large Load Diff

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/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// IdempotencyRecordDelete is the builder for deleting a IdempotencyRecord entity.
type IdempotencyRecordDelete struct {
config
hooks []Hook
mutation *IdempotencyRecordMutation
}
// Where appends a list predicates to the IdempotencyRecordDelete builder.
func (_d *IdempotencyRecordDelete) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *IdempotencyRecordDelete) 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 *IdempotencyRecordDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *IdempotencyRecordDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(idempotencyrecord.Table, sqlgraph.NewFieldSpec(idempotencyrecord.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
}
// IdempotencyRecordDeleteOne is the builder for deleting a single IdempotencyRecord entity.
type IdempotencyRecordDeleteOne struct {
_d *IdempotencyRecordDelete
}
// Where appends a list predicates to the IdempotencyRecordDelete builder.
func (_d *IdempotencyRecordDeleteOne) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *IdempotencyRecordDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{idempotencyrecord.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *IdempotencyRecordDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

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/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// IdempotencyRecordQuery is the builder for querying IdempotencyRecord entities.
type IdempotencyRecordQuery struct {
config
ctx *QueryContext
order []idempotencyrecord.OrderOption
inters []Interceptor
predicates []predicate.IdempotencyRecord
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 IdempotencyRecordQuery builder.
func (_q *IdempotencyRecordQuery) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *IdempotencyRecordQuery) Limit(limit int) *IdempotencyRecordQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *IdempotencyRecordQuery) Offset(offset int) *IdempotencyRecordQuery {
_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 *IdempotencyRecordQuery) Unique(unique bool) *IdempotencyRecordQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *IdempotencyRecordQuery) Order(o ...idempotencyrecord.OrderOption) *IdempotencyRecordQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first IdempotencyRecord entity from the query.
// Returns a *NotFoundError when no IdempotencyRecord was found.
func (_q *IdempotencyRecordQuery) First(ctx context.Context) (*IdempotencyRecord, 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{idempotencyrecord.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) FirstX(ctx context.Context) *IdempotencyRecord {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first IdempotencyRecord ID from the query.
// Returns a *NotFoundError when no IdempotencyRecord ID was found.
func (_q *IdempotencyRecordQuery) 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{idempotencyrecord.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single IdempotencyRecord entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one IdempotencyRecord entity is found.
// Returns a *NotFoundError when no IdempotencyRecord entities are found.
func (_q *IdempotencyRecordQuery) Only(ctx context.Context) (*IdempotencyRecord, 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{idempotencyrecord.Label}
default:
return nil, &NotSingularError{idempotencyrecord.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) OnlyX(ctx context.Context) *IdempotencyRecord {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only IdempotencyRecord ID in the query.
// Returns a *NotSingularError when more than one IdempotencyRecord ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *IdempotencyRecordQuery) 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{idempotencyrecord.Label}
default:
err = &NotSingularError{idempotencyrecord.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) 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 IdempotencyRecords.
func (_q *IdempotencyRecordQuery) All(ctx context.Context) ([]*IdempotencyRecord, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*IdempotencyRecord, *IdempotencyRecordQuery]()
return withInterceptors[[]*IdempotencyRecord](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) AllX(ctx context.Context) []*IdempotencyRecord {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of IdempotencyRecord IDs.
func (_q *IdempotencyRecordQuery) 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(idempotencyrecord.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) 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 *IdempotencyRecordQuery) 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[*IdempotencyRecordQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *IdempotencyRecordQuery) 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 *IdempotencyRecordQuery) 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 *IdempotencyRecordQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the IdempotencyRecordQuery 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 *IdempotencyRecordQuery) Clone() *IdempotencyRecordQuery {
if _q == nil {
return nil
}
return &IdempotencyRecordQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]idempotencyrecord.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.IdempotencyRecord{}, _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 {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.IdempotencyRecord.Query().
// GroupBy(idempotencyrecord.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *IdempotencyRecordQuery) GroupBy(field string, fields ...string) *IdempotencyRecordGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &IdempotencyRecordGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = idempotencyrecord.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 {
// CreatedAt time.Time `json:"created_at,omitempty"`
// }
//
// client.IdempotencyRecord.Query().
// Select(idempotencyrecord.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *IdempotencyRecordQuery) Select(fields ...string) *IdempotencyRecordSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &IdempotencyRecordSelect{IdempotencyRecordQuery: _q}
sbuild.label = idempotencyrecord.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a IdempotencyRecordSelect configured with the given aggregations.
func (_q *IdempotencyRecordQuery) Aggregate(fns ...AggregateFunc) *IdempotencyRecordSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *IdempotencyRecordQuery) 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 !idempotencyrecord.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 *IdempotencyRecordQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*IdempotencyRecord, error) {
var (
nodes = []*IdempotencyRecord{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*IdempotencyRecord).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &IdempotencyRecord{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 *IdempotencyRecordQuery) 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 *IdempotencyRecordQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.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, idempotencyrecord.FieldID)
for i := range fields {
if fields[i] != idempotencyrecord.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 *IdempotencyRecordQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(idempotencyrecord.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = idempotencyrecord.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 *IdempotencyRecordQuery) ForUpdate(opts ...sql.LockOption) *IdempotencyRecordQuery {
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 *IdempotencyRecordQuery) ForShare(opts ...sql.LockOption) *IdempotencyRecordQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// IdempotencyRecordGroupBy is the group-by builder for IdempotencyRecord entities.
type IdempotencyRecordGroupBy struct {
selector
build *IdempotencyRecordQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *IdempotencyRecordGroupBy) Aggregate(fns ...AggregateFunc) *IdempotencyRecordGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *IdempotencyRecordGroupBy) 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[*IdempotencyRecordQuery, *IdempotencyRecordGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *IdempotencyRecordGroupBy) sqlScan(ctx context.Context, root *IdempotencyRecordQuery, 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)
}
// IdempotencyRecordSelect is the builder for selecting fields of IdempotencyRecord entities.
type IdempotencyRecordSelect struct {
*IdempotencyRecordQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *IdempotencyRecordSelect) Aggregate(fns ...AggregateFunc) *IdempotencyRecordSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *IdempotencyRecordSelect) 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[*IdempotencyRecordQuery, *IdempotencyRecordSelect](ctx, _s.IdempotencyRecordQuery, _s, _s.inters, v)
}
func (_s *IdempotencyRecordSelect) sqlScan(ctx context.Context, root *IdempotencyRecordQuery, 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)
}

View File

@@ -0,0 +1,676 @@
// 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/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// IdempotencyRecordUpdate is the builder for updating IdempotencyRecord entities.
type IdempotencyRecordUpdate struct {
config
hooks []Hook
mutation *IdempotencyRecordMutation
}
// Where appends a list predicates to the IdempotencyRecordUpdate builder.
func (_u *IdempotencyRecordUpdate) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *IdempotencyRecordUpdate) SetUpdatedAt(v time.Time) *IdempotencyRecordUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetScope sets the "scope" field.
func (_u *IdempotencyRecordUpdate) SetScope(v string) *IdempotencyRecordUpdate {
_u.mutation.SetScope(v)
return _u
}
// SetNillableScope sets the "scope" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableScope(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetScope(*v)
}
return _u
}
// SetIdempotencyKeyHash sets the "idempotency_key_hash" field.
func (_u *IdempotencyRecordUpdate) SetIdempotencyKeyHash(v string) *IdempotencyRecordUpdate {
_u.mutation.SetIdempotencyKeyHash(v)
return _u
}
// SetNillableIdempotencyKeyHash sets the "idempotency_key_hash" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableIdempotencyKeyHash(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetIdempotencyKeyHash(*v)
}
return _u
}
// SetRequestFingerprint sets the "request_fingerprint" field.
func (_u *IdempotencyRecordUpdate) SetRequestFingerprint(v string) *IdempotencyRecordUpdate {
_u.mutation.SetRequestFingerprint(v)
return _u
}
// SetNillableRequestFingerprint sets the "request_fingerprint" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableRequestFingerprint(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetRequestFingerprint(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *IdempotencyRecordUpdate) SetStatus(v string) *IdempotencyRecordUpdate {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableStatus(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetStatus(*v)
}
return _u
}
// SetResponseStatus sets the "response_status" field.
func (_u *IdempotencyRecordUpdate) SetResponseStatus(v int) *IdempotencyRecordUpdate {
_u.mutation.ResetResponseStatus()
_u.mutation.SetResponseStatus(v)
return _u
}
// SetNillableResponseStatus sets the "response_status" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableResponseStatus(v *int) *IdempotencyRecordUpdate {
if v != nil {
_u.SetResponseStatus(*v)
}
return _u
}
// AddResponseStatus adds value to the "response_status" field.
func (_u *IdempotencyRecordUpdate) AddResponseStatus(v int) *IdempotencyRecordUpdate {
_u.mutation.AddResponseStatus(v)
return _u
}
// ClearResponseStatus clears the value of the "response_status" field.
func (_u *IdempotencyRecordUpdate) ClearResponseStatus() *IdempotencyRecordUpdate {
_u.mutation.ClearResponseStatus()
return _u
}
// SetResponseBody sets the "response_body" field.
func (_u *IdempotencyRecordUpdate) SetResponseBody(v string) *IdempotencyRecordUpdate {
_u.mutation.SetResponseBody(v)
return _u
}
// SetNillableResponseBody sets the "response_body" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableResponseBody(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetResponseBody(*v)
}
return _u
}
// ClearResponseBody clears the value of the "response_body" field.
func (_u *IdempotencyRecordUpdate) ClearResponseBody() *IdempotencyRecordUpdate {
_u.mutation.ClearResponseBody()
return _u
}
// SetErrorReason sets the "error_reason" field.
func (_u *IdempotencyRecordUpdate) SetErrorReason(v string) *IdempotencyRecordUpdate {
_u.mutation.SetErrorReason(v)
return _u
}
// SetNillableErrorReason sets the "error_reason" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableErrorReason(v *string) *IdempotencyRecordUpdate {
if v != nil {
_u.SetErrorReason(*v)
}
return _u
}
// ClearErrorReason clears the value of the "error_reason" field.
func (_u *IdempotencyRecordUpdate) ClearErrorReason() *IdempotencyRecordUpdate {
_u.mutation.ClearErrorReason()
return _u
}
// SetLockedUntil sets the "locked_until" field.
func (_u *IdempotencyRecordUpdate) SetLockedUntil(v time.Time) *IdempotencyRecordUpdate {
_u.mutation.SetLockedUntil(v)
return _u
}
// SetNillableLockedUntil sets the "locked_until" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableLockedUntil(v *time.Time) *IdempotencyRecordUpdate {
if v != nil {
_u.SetLockedUntil(*v)
}
return _u
}
// ClearLockedUntil clears the value of the "locked_until" field.
func (_u *IdempotencyRecordUpdate) ClearLockedUntil() *IdempotencyRecordUpdate {
_u.mutation.ClearLockedUntil()
return _u
}
// SetExpiresAt sets the "expires_at" field.
func (_u *IdempotencyRecordUpdate) SetExpiresAt(v time.Time) *IdempotencyRecordUpdate {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *IdempotencyRecordUpdate) SetNillableExpiresAt(v *time.Time) *IdempotencyRecordUpdate {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// Mutation returns the IdempotencyRecordMutation object of the builder.
func (_u *IdempotencyRecordUpdate) Mutation() *IdempotencyRecordMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *IdempotencyRecordUpdate) Save(ctx context.Context) (int, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *IdempotencyRecordUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *IdempotencyRecordUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *IdempotencyRecordUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *IdempotencyRecordUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := idempotencyrecord.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *IdempotencyRecordUpdate) check() error {
if v, ok := _u.mutation.Scope(); ok {
if err := idempotencyrecord.ScopeValidator(v); err != nil {
return &ValidationError{Name: "scope", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.scope": %w`, err)}
}
}
if v, ok := _u.mutation.IdempotencyKeyHash(); ok {
if err := idempotencyrecord.IdempotencyKeyHashValidator(v); err != nil {
return &ValidationError{Name: "idempotency_key_hash", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.idempotency_key_hash": %w`, err)}
}
}
if v, ok := _u.mutation.RequestFingerprint(); ok {
if err := idempotencyrecord.RequestFingerprintValidator(v); err != nil {
return &ValidationError{Name: "request_fingerprint", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.request_fingerprint": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := idempotencyrecord.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.status": %w`, err)}
}
}
if v, ok := _u.mutation.ErrorReason(); ok {
if err := idempotencyrecord.ErrorReasonValidator(v); err != nil {
return &ValidationError{Name: "error_reason", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.error_reason": %w`, err)}
}
}
return nil
}
func (_u *IdempotencyRecordUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.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.UpdatedAt(); ok {
_spec.SetField(idempotencyrecord.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.Scope(); ok {
_spec.SetField(idempotencyrecord.FieldScope, field.TypeString, value)
}
if value, ok := _u.mutation.IdempotencyKeyHash(); ok {
_spec.SetField(idempotencyrecord.FieldIdempotencyKeyHash, field.TypeString, value)
}
if value, ok := _u.mutation.RequestFingerprint(); ok {
_spec.SetField(idempotencyrecord.FieldRequestFingerprint, field.TypeString, value)
}
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(idempotencyrecord.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.ResponseStatus(); ok {
_spec.SetField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedResponseStatus(); ok {
_spec.AddField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
}
if _u.mutation.ResponseStatusCleared() {
_spec.ClearField(idempotencyrecord.FieldResponseStatus, field.TypeInt)
}
if value, ok := _u.mutation.ResponseBody(); ok {
_spec.SetField(idempotencyrecord.FieldResponseBody, field.TypeString, value)
}
if _u.mutation.ResponseBodyCleared() {
_spec.ClearField(idempotencyrecord.FieldResponseBody, field.TypeString)
}
if value, ok := _u.mutation.ErrorReason(); ok {
_spec.SetField(idempotencyrecord.FieldErrorReason, field.TypeString, value)
}
if _u.mutation.ErrorReasonCleared() {
_spec.ClearField(idempotencyrecord.FieldErrorReason, field.TypeString)
}
if value, ok := _u.mutation.LockedUntil(); ok {
_spec.SetField(idempotencyrecord.FieldLockedUntil, field.TypeTime, value)
}
if _u.mutation.LockedUntilCleared() {
_spec.ClearField(idempotencyrecord.FieldLockedUntil, field.TypeTime)
}
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(idempotencyrecord.FieldExpiresAt, field.TypeTime, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{idempotencyrecord.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// IdempotencyRecordUpdateOne is the builder for updating a single IdempotencyRecord entity.
type IdempotencyRecordUpdateOne struct {
config
fields []string
hooks []Hook
mutation *IdempotencyRecordMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *IdempotencyRecordUpdateOne) SetUpdatedAt(v time.Time) *IdempotencyRecordUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetScope sets the "scope" field.
func (_u *IdempotencyRecordUpdateOne) SetScope(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetScope(v)
return _u
}
// SetNillableScope sets the "scope" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableScope(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetScope(*v)
}
return _u
}
// SetIdempotencyKeyHash sets the "idempotency_key_hash" field.
func (_u *IdempotencyRecordUpdateOne) SetIdempotencyKeyHash(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetIdempotencyKeyHash(v)
return _u
}
// SetNillableIdempotencyKeyHash sets the "idempotency_key_hash" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableIdempotencyKeyHash(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetIdempotencyKeyHash(*v)
}
return _u
}
// SetRequestFingerprint sets the "request_fingerprint" field.
func (_u *IdempotencyRecordUpdateOne) SetRequestFingerprint(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetRequestFingerprint(v)
return _u
}
// SetNillableRequestFingerprint sets the "request_fingerprint" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableRequestFingerprint(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetRequestFingerprint(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *IdempotencyRecordUpdateOne) SetStatus(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableStatus(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetStatus(*v)
}
return _u
}
// SetResponseStatus sets the "response_status" field.
func (_u *IdempotencyRecordUpdateOne) SetResponseStatus(v int) *IdempotencyRecordUpdateOne {
_u.mutation.ResetResponseStatus()
_u.mutation.SetResponseStatus(v)
return _u
}
// SetNillableResponseStatus sets the "response_status" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableResponseStatus(v *int) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetResponseStatus(*v)
}
return _u
}
// AddResponseStatus adds value to the "response_status" field.
func (_u *IdempotencyRecordUpdateOne) AddResponseStatus(v int) *IdempotencyRecordUpdateOne {
_u.mutation.AddResponseStatus(v)
return _u
}
// ClearResponseStatus clears the value of the "response_status" field.
func (_u *IdempotencyRecordUpdateOne) ClearResponseStatus() *IdempotencyRecordUpdateOne {
_u.mutation.ClearResponseStatus()
return _u
}
// SetResponseBody sets the "response_body" field.
func (_u *IdempotencyRecordUpdateOne) SetResponseBody(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetResponseBody(v)
return _u
}
// SetNillableResponseBody sets the "response_body" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableResponseBody(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetResponseBody(*v)
}
return _u
}
// ClearResponseBody clears the value of the "response_body" field.
func (_u *IdempotencyRecordUpdateOne) ClearResponseBody() *IdempotencyRecordUpdateOne {
_u.mutation.ClearResponseBody()
return _u
}
// SetErrorReason sets the "error_reason" field.
func (_u *IdempotencyRecordUpdateOne) SetErrorReason(v string) *IdempotencyRecordUpdateOne {
_u.mutation.SetErrorReason(v)
return _u
}
// SetNillableErrorReason sets the "error_reason" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableErrorReason(v *string) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetErrorReason(*v)
}
return _u
}
// ClearErrorReason clears the value of the "error_reason" field.
func (_u *IdempotencyRecordUpdateOne) ClearErrorReason() *IdempotencyRecordUpdateOne {
_u.mutation.ClearErrorReason()
return _u
}
// SetLockedUntil sets the "locked_until" field.
func (_u *IdempotencyRecordUpdateOne) SetLockedUntil(v time.Time) *IdempotencyRecordUpdateOne {
_u.mutation.SetLockedUntil(v)
return _u
}
// SetNillableLockedUntil sets the "locked_until" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableLockedUntil(v *time.Time) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetLockedUntil(*v)
}
return _u
}
// ClearLockedUntil clears the value of the "locked_until" field.
func (_u *IdempotencyRecordUpdateOne) ClearLockedUntil() *IdempotencyRecordUpdateOne {
_u.mutation.ClearLockedUntil()
return _u
}
// SetExpiresAt sets the "expires_at" field.
func (_u *IdempotencyRecordUpdateOne) SetExpiresAt(v time.Time) *IdempotencyRecordUpdateOne {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *IdempotencyRecordUpdateOne) SetNillableExpiresAt(v *time.Time) *IdempotencyRecordUpdateOne {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// Mutation returns the IdempotencyRecordMutation object of the builder.
func (_u *IdempotencyRecordUpdateOne) Mutation() *IdempotencyRecordMutation {
return _u.mutation
}
// Where appends a list predicates to the IdempotencyRecordUpdate builder.
func (_u *IdempotencyRecordUpdateOne) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordUpdateOne {
_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 *IdempotencyRecordUpdateOne) Select(field string, fields ...string) *IdempotencyRecordUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated IdempotencyRecord entity.
func (_u *IdempotencyRecordUpdateOne) Save(ctx context.Context) (*IdempotencyRecord, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *IdempotencyRecordUpdateOne) SaveX(ctx context.Context) *IdempotencyRecord {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *IdempotencyRecordUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *IdempotencyRecordUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *IdempotencyRecordUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := idempotencyrecord.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *IdempotencyRecordUpdateOne) check() error {
if v, ok := _u.mutation.Scope(); ok {
if err := idempotencyrecord.ScopeValidator(v); err != nil {
return &ValidationError{Name: "scope", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.scope": %w`, err)}
}
}
if v, ok := _u.mutation.IdempotencyKeyHash(); ok {
if err := idempotencyrecord.IdempotencyKeyHashValidator(v); err != nil {
return &ValidationError{Name: "idempotency_key_hash", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.idempotency_key_hash": %w`, err)}
}
}
if v, ok := _u.mutation.RequestFingerprint(); ok {
if err := idempotencyrecord.RequestFingerprintValidator(v); err != nil {
return &ValidationError{Name: "request_fingerprint", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.request_fingerprint": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := idempotencyrecord.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.status": %w`, err)}
}
}
if v, ok := _u.mutation.ErrorReason(); ok {
if err := idempotencyrecord.ErrorReasonValidator(v); err != nil {
return &ValidationError{Name: "error_reason", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.error_reason": %w`, err)}
}
}
return nil
}
func (_u *IdempotencyRecordUpdateOne) sqlSave(ctx context.Context) (_node *IdempotencyRecord, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "IdempotencyRecord.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, idempotencyrecord.FieldID)
for _, f := range fields {
if !idempotencyrecord.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != idempotencyrecord.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.UpdatedAt(); ok {
_spec.SetField(idempotencyrecord.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.Scope(); ok {
_spec.SetField(idempotencyrecord.FieldScope, field.TypeString, value)
}
if value, ok := _u.mutation.IdempotencyKeyHash(); ok {
_spec.SetField(idempotencyrecord.FieldIdempotencyKeyHash, field.TypeString, value)
}
if value, ok := _u.mutation.RequestFingerprint(); ok {
_spec.SetField(idempotencyrecord.FieldRequestFingerprint, field.TypeString, value)
}
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(idempotencyrecord.FieldStatus, field.TypeString, value)
}
if value, ok := _u.mutation.ResponseStatus(); ok {
_spec.SetField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedResponseStatus(); ok {
_spec.AddField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
}
if _u.mutation.ResponseStatusCleared() {
_spec.ClearField(idempotencyrecord.FieldResponseStatus, field.TypeInt)
}
if value, ok := _u.mutation.ResponseBody(); ok {
_spec.SetField(idempotencyrecord.FieldResponseBody, field.TypeString, value)
}
if _u.mutation.ResponseBodyCleared() {
_spec.ClearField(idempotencyrecord.FieldResponseBody, field.TypeString)
}
if value, ok := _u.mutation.ErrorReason(); ok {
_spec.SetField(idempotencyrecord.FieldErrorReason, field.TypeString, value)
}
if _u.mutation.ErrorReasonCleared() {
_spec.ClearField(idempotencyrecord.FieldErrorReason, field.TypeString)
}
if value, ok := _u.mutation.LockedUntil(); ok {
_spec.SetField(idempotencyrecord.FieldLockedUntil, field.TypeTime, value)
}
if _u.mutation.LockedUntilCleared() {
_spec.ClearField(idempotencyrecord.FieldLockedUntil, field.TypeTime)
}
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(idempotencyrecord.FieldExpiresAt, field.TypeTime, value)
}
_node = &IdempotencyRecord{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{idempotencyrecord.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@@ -15,12 +15,19 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -275,6 +282,114 @@ func (f TraverseGroup) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.GroupQuery", q)
}
// The IdempotencyRecordFunc type is an adapter to allow the use of ordinary function as a Querier.
type IdempotencyRecordFunc func(context.Context, *ent.IdempotencyRecordQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f IdempotencyRecordFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.IdempotencyRecordQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q)
}
// The TraverseIdempotencyRecord type is an adapter to allow the use of ordinary function as Traverser.
type TraverseIdempotencyRecord func(context.Context, *ent.IdempotencyRecordQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseIdempotencyRecord) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseIdempotencyRecord) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.IdempotencyRecordQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q)
}
// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentAuditLogFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentAuditLogQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q)
}
// The TraversePaymentAuditLog type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentAuditLog func(context.Context, *ent.PaymentAuditLogQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentAuditLog) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentAuditLog) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentAuditLogQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q)
}
// The PaymentOrderFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentOrderFunc func(context.Context, *ent.PaymentOrderQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentOrderFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentOrderQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q)
}
// The TraversePaymentOrder type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentOrder func(context.Context, *ent.PaymentOrderQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentOrder) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentOrder) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentOrderQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q)
}
// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentProviderInstanceFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", q)
}
// The TraversePaymentProviderInstance type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentProviderInstance func(context.Context, *ent.PaymentProviderInstanceQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentProviderInstance) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentProviderInstance) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", q)
}
// The PromoCodeFunc type is an adapter to allow the use of ordinary function as a Querier.
type PromoCodeFunc func(context.Context, *ent.PromoCodeQuery) (ent.Value, error)
@@ -383,6 +498,33 @@ func (f TraverseRedeemCode) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.RedeemCodeQuery", q)
}
// The SecuritySecretFunc type is an adapter to allow the use of ordinary function as a Querier.
type SecuritySecretFunc func(context.Context, *ent.SecuritySecretQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f SecuritySecretFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.SecuritySecretQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SecuritySecretQuery", q)
}
// The TraverseSecuritySecret type is an adapter to allow the use of ordinary function as Traverser.
type TraverseSecuritySecret func(context.Context, *ent.SecuritySecretQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseSecuritySecret) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseSecuritySecret) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.SecuritySecretQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.SecuritySecretQuery", q)
}
// The SettingFunc type is an adapter to allow the use of ordinary function as a Querier.
type SettingFunc func(context.Context, *ent.SettingQuery) (ent.Value, error)
@@ -410,6 +552,60 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q)
}
// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary function as a Querier.
type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f SubscriptionPlanFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.SubscriptionPlanQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q)
}
// The TraverseSubscriptionPlan type is an adapter to allow the use of ordinary function as Traverser.
type TraverseSubscriptionPlan func(context.Context, *ent.SubscriptionPlanQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseSubscriptionPlan) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseSubscriptionPlan) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.SubscriptionPlanQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q)
}
// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary function as a Querier.
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f TLSFingerprintProfileFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q)
}
// The TraverseTLSFingerprintProfile type is an adapter to allow the use of ordinary function as Traverser.
type TraverseTLSFingerprintProfile func(context.Context, *ent.TLSFingerprintProfileQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseTLSFingerprintProfile) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseTLSFingerprintProfile) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q)
}
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary function as a Querier.
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskQuery) (ent.Value, error)
@@ -616,6 +812,14 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.ErrorPassthroughRuleQuery, predicate.ErrorPassthroughRule, errorpassthroughrule.OrderOption]{typ: ent.TypeErrorPassthroughRule, tq: q}, nil
case *ent.GroupQuery:
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
case *ent.IdempotencyRecordQuery:
return &query[*ent.IdempotencyRecordQuery, predicate.IdempotencyRecord, idempotencyrecord.OrderOption]{typ: ent.TypeIdempotencyRecord, tq: q}, nil
case *ent.PaymentAuditLogQuery:
return &query[*ent.PaymentAuditLogQuery, predicate.PaymentAuditLog, paymentauditlog.OrderOption]{typ: ent.TypePaymentAuditLog, tq: q}, nil
case *ent.PaymentOrderQuery:
return &query[*ent.PaymentOrderQuery, predicate.PaymentOrder, paymentorder.OrderOption]{typ: ent.TypePaymentOrder, tq: q}, nil
case *ent.PaymentProviderInstanceQuery:
return &query[*ent.PaymentProviderInstanceQuery, predicate.PaymentProviderInstance, paymentproviderinstance.OrderOption]{typ: ent.TypePaymentProviderInstance, tq: q}, nil
case *ent.PromoCodeQuery:
return &query[*ent.PromoCodeQuery, predicate.PromoCode, promocode.OrderOption]{typ: ent.TypePromoCode, tq: q}, nil
case *ent.PromoCodeUsageQuery:
@@ -624,8 +828,14 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.ProxyQuery, predicate.Proxy, proxy.OrderOption]{typ: ent.TypeProxy, tq: q}, nil
case *ent.RedeemCodeQuery:
return &query[*ent.RedeemCodeQuery, predicate.RedeemCode, redeemcode.OrderOption]{typ: ent.TypeRedeemCode, tq: q}, nil
case *ent.SecuritySecretQuery:
return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil
case *ent.SettingQuery:
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
case *ent.SubscriptionPlanQuery:
return &query[*ent.SubscriptionPlanQuery, predicate.SubscriptionPlan, subscriptionplan.OrderOption]{typ: ent.TypeSubscriptionPlan, tq: q}, nil
case *ent.TLSFingerprintProfileQuery:
return &query[*ent.TLSFingerprintProfileQuery, predicate.TLSFingerprintProfile, tlsfingerprintprofile.OrderOption]{typ: ent.TypeTLSFingerprintProfile, tq: q}, nil
case *ent.UsageCleanupTaskQuery:
return &query[*ent.UsageCleanupTaskQuery, predicate.UsageCleanupTask, usagecleanuptask.OrderOption]{typ: ent.TypeUsageCleanupTask, tq: q}, nil
case *ent.UsageLogQuery:

View File

@@ -18,11 +18,21 @@ var (
{Name: "key", Type: field.TypeString, Unique: true, Size: 128},
{Name: "name", Type: field.TypeString, Size: 100},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
{Name: "last_used_at", Type: field.TypeTime, Nullable: true},
{Name: "ip_whitelist", Type: field.TypeJSON, Nullable: true},
{Name: "ip_blacklist", Type: field.TypeJSON, Nullable: true},
{Name: "quota", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "quota_used", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "expires_at", Type: field.TypeTime, Nullable: true},
{Name: "rate_limit_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "rate_limit_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "rate_limit_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "window_5h_start", Type: field.TypeTime, Nullable: true},
{Name: "window_1d_start", Type: field.TypeTime, Nullable: true},
{Name: "window_7d_start", Type: field.TypeTime, Nullable: true},
{Name: "group_id", Type: field.TypeInt64, Nullable: true},
{Name: "user_id", Type: field.TypeInt64},
}
@@ -34,13 +44,13 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "api_keys_groups_api_keys",
Columns: []*schema.Column{APIKeysColumns[12]},
Columns: []*schema.Column{APIKeysColumns[22]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "api_keys_users_api_keys",
Columns: []*schema.Column{APIKeysColumns[13]},
Columns: []*schema.Column{APIKeysColumns[23]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
@@ -49,12 +59,12 @@ var (
{
Name: "apikey_user_id",
Unique: false,
Columns: []*schema.Column{APIKeysColumns[13]},
Columns: []*schema.Column{APIKeysColumns[23]},
},
{
Name: "apikey_group_id",
Unique: false,
Columns: []*schema.Column{APIKeysColumns[12]},
Columns: []*schema.Column{APIKeysColumns[22]},
},
{
Name: "apikey_status",
@@ -66,15 +76,20 @@ var (
Unique: false,
Columns: []*schema.Column{APIKeysColumns[3]},
},
{
Name: "apikey_last_used_at",
Unique: false,
Columns: []*schema.Column{APIKeysColumns[7]},
},
{
Name: "apikey_quota_quota_used",
Unique: false,
Columns: []*schema.Column{APIKeysColumns[9], APIKeysColumns[10]},
Columns: []*schema.Column{APIKeysColumns[10], APIKeysColumns[11]},
},
{
Name: "apikey_expires_at",
Unique: false,
Columns: []*schema.Column{APIKeysColumns[11]},
Columns: []*schema.Column{APIKeysColumns[12]},
},
},
}
@@ -91,6 +106,7 @@ var (
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "extra", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "concurrency", Type: field.TypeInt, Default: 3},
{Name: "load_factor", Type: field.TypeInt, Nullable: true},
{Name: "priority", Type: field.TypeInt, Default: 50},
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
@@ -102,6 +118,8 @@ var (
{Name: "rate_limited_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "rate_limit_reset_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "overload_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "temp_unschedulable_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "temp_unschedulable_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "session_window_start", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_end", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_status", Type: field.TypeString, Nullable: true, Size: 20},
@@ -115,7 +133,7 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "accounts_proxies_proxy",
Columns: []*schema.Column{AccountsColumns[25]},
Columns: []*schema.Column{AccountsColumns[28]},
RefColumns: []*schema.Column{ProxiesColumns[0]},
OnDelete: schema.SetNull,
},
@@ -134,42 +152,52 @@ var (
{
Name: "account_status",
Unique: false,
Columns: []*schema.Column{AccountsColumns[13]},
Columns: []*schema.Column{AccountsColumns[14]},
},
{
Name: "account_proxy_id",
Unique: false,
Columns: []*schema.Column{AccountsColumns[25]},
Columns: []*schema.Column{AccountsColumns[28]},
},
{
Name: "account_priority",
Unique: false,
Columns: []*schema.Column{AccountsColumns[11]},
Columns: []*schema.Column{AccountsColumns[12]},
},
{
Name: "account_last_used_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[15]},
Columns: []*schema.Column{AccountsColumns[16]},
},
{
Name: "account_schedulable",
Unique: false,
Columns: []*schema.Column{AccountsColumns[18]},
Columns: []*schema.Column{AccountsColumns[19]},
},
{
Name: "account_rate_limited_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[19]},
Columns: []*schema.Column{AccountsColumns[20]},
},
{
Name: "account_rate_limit_reset_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[20]},
Columns: []*schema.Column{AccountsColumns[21]},
},
{
Name: "account_overload_until",
Unique: false,
Columns: []*schema.Column{AccountsColumns[21]},
Columns: []*schema.Column{AccountsColumns[22]},
},
{
Name: "account_platform_priority",
Unique: false,
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[12]},
},
{
Name: "account_priority_status",
Unique: false,
Columns: []*schema.Column{AccountsColumns[12], AccountsColumns[14]},
},
{
Name: "account_deleted_at",
@@ -223,6 +251,7 @@ var (
{Name: "title", Type: field.TypeString, Size: 200},
{Name: "content", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
{Name: "status", Type: field.TypeString, Size: 20, Default: "draft"},
{Name: "notify_mode", Type: field.TypeString, Size: 20, Default: "silent"},
{Name: "targeting", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "starts_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "ends_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
@@ -245,17 +274,17 @@ var (
{
Name: "announcement_created_at",
Unique: false,
Columns: []*schema.Column{AnnouncementsColumns[9]},
Columns: []*schema.Column{AnnouncementsColumns[10]},
},
{
Name: "announcement_starts_at",
Unique: false,
Columns: []*schema.Column{AnnouncementsColumns[5]},
Columns: []*schema.Column{AnnouncementsColumns[6]},
},
{
Name: "announcement_ends_at",
Unique: false,
Columns: []*schema.Column{AnnouncementsColumns[6]},
Columns: []*schema.Column{AnnouncementsColumns[7]},
},
},
}
@@ -374,6 +403,11 @@ var (
{Name: "mcp_xml_inject", Type: field.TypeBool, Default: true},
{Name: "supported_model_scopes", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "sort_order", Type: field.TypeInt, Default: 0},
{Name: "allow_messages_dispatch", Type: field.TypeBool, Default: false},
{Name: "require_oauth_only", Type: field.TypeBool, Default: false},
{Name: "require_privacy_set", Type: field.TypeBool, Default: false},
{Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""},
{Name: "messages_dispatch_model_config", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
}
// GroupsTable holds the schema information for the "groups" table.
GroupsTable = &schema.Table{
@@ -413,6 +447,197 @@ var (
},
},
}
// IdempotencyRecordsColumns holds the columns for the "idempotency_records" table.
IdempotencyRecordsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "scope", Type: field.TypeString, Size: 128},
{Name: "idempotency_key_hash", Type: field.TypeString, Size: 64},
{Name: "request_fingerprint", Type: field.TypeString, Size: 64},
{Name: "status", Type: field.TypeString, Size: 32},
{Name: "response_status", Type: field.TypeInt, Nullable: true},
{Name: "response_body", Type: field.TypeString, Nullable: true},
{Name: "error_reason", Type: field.TypeString, Nullable: true, Size: 128},
{Name: "locked_until", Type: field.TypeTime, Nullable: true},
{Name: "expires_at", Type: field.TypeTime},
}
// IdempotencyRecordsTable holds the schema information for the "idempotency_records" table.
IdempotencyRecordsTable = &schema.Table{
Name: "idempotency_records",
Columns: IdempotencyRecordsColumns,
PrimaryKey: []*schema.Column{IdempotencyRecordsColumns[0]},
Indexes: []*schema.Index{
{
Name: "idempotencyrecord_scope_idempotency_key_hash",
Unique: true,
Columns: []*schema.Column{IdempotencyRecordsColumns[3], IdempotencyRecordsColumns[4]},
},
{
Name: "idempotencyrecord_expires_at",
Unique: false,
Columns: []*schema.Column{IdempotencyRecordsColumns[11]},
},
{
Name: "idempotencyrecord_status_locked_until",
Unique: false,
Columns: []*schema.Column{IdempotencyRecordsColumns[6], IdempotencyRecordsColumns[10]},
},
},
}
// PaymentAuditLogsColumns holds the columns for the "payment_audit_logs" table.
PaymentAuditLogsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "order_id", Type: field.TypeString, Size: 64},
{Name: "action", Type: field.TypeString, Size: 50},
{Name: "detail", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "operator", Type: field.TypeString, Size: 100, Default: "system"},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// PaymentAuditLogsTable holds the schema information for the "payment_audit_logs" table.
PaymentAuditLogsTable = &schema.Table{
Name: "payment_audit_logs",
Columns: PaymentAuditLogsColumns,
PrimaryKey: []*schema.Column{PaymentAuditLogsColumns[0]},
Indexes: []*schema.Index{
{
Name: "paymentauditlog_order_id",
Unique: false,
Columns: []*schema.Column{PaymentAuditLogsColumns[1]},
},
},
}
// PaymentOrdersColumns holds the columns for the "payment_orders" table.
PaymentOrdersColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "user_email", Type: field.TypeString, Size: 255},
{Name: "user_name", Type: field.TypeString, Size: 100},
{Name: "user_notes", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "pay_amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "fee_rate", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "recharge_code", Type: field.TypeString, Size: 64},
{Name: "out_trade_no", Type: field.TypeString, Size: 64, Default: ""},
{Name: "payment_type", Type: field.TypeString, Size: 30},
{Name: "payment_trade_no", Type: field.TypeString, Size: 128},
{Name: "pay_url", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "qr_code", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "qr_code_img", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "order_type", Type: field.TypeString, Size: 20, Default: "balance"},
{Name: "plan_id", Type: field.TypeInt64, Nullable: true},
{Name: "subscription_group_id", Type: field.TypeInt64, Nullable: true},
{Name: "subscription_days", Type: field.TypeInt, Nullable: true},
{Name: "provider_instance_id", Type: field.TypeString, Nullable: true, Size: 64},
{Name: "status", Type: field.TypeString, Size: 30, Default: "PENDING"},
{Name: "refund_amount", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "refund_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "force_refund", Type: field.TypeBool, Default: false},
{Name: "refund_requested_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "refund_request_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_requested_by", Type: field.TypeString, Nullable: true, Size: 20},
{Name: "expires_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "paid_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "completed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "failed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "failed_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "client_ip", Type: field.TypeString, Size: 50},
{Name: "src_host", Type: field.TypeString, Size: 255},
{Name: "src_url", 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: "user_id", Type: field.TypeInt64},
}
// PaymentOrdersTable holds the schema information for the "payment_orders" table.
PaymentOrdersTable = &schema.Table{
Name: "payment_orders",
Columns: PaymentOrdersColumns,
PrimaryKey: []*schema.Column{PaymentOrdersColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "payment_orders_users_payment_orders",
Columns: []*schema.Column{PaymentOrdersColumns[37]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "paymentorder_out_trade_no",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[8]},
},
{
Name: "paymentorder_user_id",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[37]},
},
{
Name: "paymentorder_status",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[19]},
},
{
Name: "paymentorder_expires_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[27]},
},
{
Name: "paymentorder_created_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[35]},
},
{
Name: "paymentorder_paid_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[28]},
},
{
Name: "paymentorder_payment_type_paid_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[9], PaymentOrdersColumns[28]},
},
{
Name: "paymentorder_order_type",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[14]},
},
},
}
// PaymentProviderInstancesColumns holds the columns for the "payment_provider_instances" table.
PaymentProviderInstancesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "provider_key", Type: field.TypeString, Size: 30},
{Name: "name", Type: field.TypeString, Size: 100, Default: ""},
{Name: "config", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
{Name: "supported_types", Type: field.TypeString, Size: 200, Default: ""},
{Name: "enabled", Type: field.TypeBool, Default: true},
{Name: "payment_mode", Type: field.TypeString, Size: 20, Default: ""},
{Name: "sort_order", Type: field.TypeInt, Default: 0},
{Name: "limits", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_enabled", Type: field.TypeBool, Default: false},
{Name: "allow_user_refund", Type: field.TypeBool, Default: false},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// PaymentProviderInstancesTable holds the schema information for the "payment_provider_instances" table.
PaymentProviderInstancesTable = &schema.Table{
Name: "payment_provider_instances",
Columns: PaymentProviderInstancesColumns,
PrimaryKey: []*schema.Column{PaymentProviderInstancesColumns[0]},
Indexes: []*schema.Index{
{
Name: "paymentproviderinstance_provider_key",
Unique: false,
Columns: []*schema.Column{PaymentProviderInstancesColumns[1]},
},
{
Name: "paymentproviderinstance_enabled",
Unique: false,
Columns: []*schema.Column{PaymentProviderInstancesColumns[5]},
},
},
}
// PromoCodesColumns holds the columns for the "promo_codes" table.
PromoCodesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -572,6 +797,20 @@ var (
},
},
}
// SecuritySecretsColumns holds the columns for the "security_secrets" table.
SecuritySecretsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "key", Type: field.TypeString, Unique: true, Size: 100},
{Name: "value", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
}
// SecuritySecretsTable holds the schema information for the "security_secrets" table.
SecuritySecretsTable = &schema.Table{
Name: "security_secrets",
Columns: SecuritySecretsColumns,
PrimaryKey: []*schema.Column{SecuritySecretsColumns[0]},
}
// SettingsColumns holds the columns for the "settings" table.
SettingsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -585,6 +824,65 @@ var (
Columns: SettingsColumns,
PrimaryKey: []*schema.Column{SettingsColumns[0]},
}
// SubscriptionPlansColumns holds the columns for the "subscription_plans" table.
SubscriptionPlansColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "group_id", Type: field.TypeInt64},
{Name: "name", Type: field.TypeString, Size: 100},
{Name: "description", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "price", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "original_price", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "validity_days", Type: field.TypeInt, Default: 30},
{Name: "validity_unit", Type: field.TypeString, Size: 10, Default: "day"},
{Name: "features", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "product_name", Type: field.TypeString, Size: 100, Default: ""},
{Name: "for_sale", Type: field.TypeBool, Default: true},
{Name: "sort_order", Type: field.TypeInt, Default: 0},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// SubscriptionPlansTable holds the schema information for the "subscription_plans" table.
SubscriptionPlansTable = &schema.Table{
Name: "subscription_plans",
Columns: SubscriptionPlansColumns,
PrimaryKey: []*schema.Column{SubscriptionPlansColumns[0]},
Indexes: []*schema.Index{
{
Name: "subscriptionplan_group_id",
Unique: false,
Columns: []*schema.Column{SubscriptionPlansColumns[1]},
},
{
Name: "subscriptionplan_for_sale",
Unique: false,
Columns: []*schema.Column{SubscriptionPlansColumns[10]},
},
},
}
// TLSFingerprintProfilesColumns holds the columns for the "tls_fingerprint_profiles" table.
TLSFingerprintProfilesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "name", Type: field.TypeString, Unique: true, Size: 100},
{Name: "description", Type: field.TypeString, Nullable: true, Size: 2147483647},
{Name: "enable_grease", Type: field.TypeBool, Default: false},
{Name: "cipher_suites", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "curves", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "point_formats", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "signature_algorithms", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "alpn_protocols", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "supported_versions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "key_share_groups", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "psk_modes", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "extensions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
}
// TLSFingerprintProfilesTable holds the schema information for the "tls_fingerprint_profiles" table.
TLSFingerprintProfilesTable = &schema.Table{
Name: "tls_fingerprint_profiles",
Columns: TLSFingerprintProfilesColumns,
PrimaryKey: []*schema.Column{TLSFingerprintProfilesColumns[0]},
}
// UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table.
UsageCleanupTasksColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -628,6 +926,12 @@ var (
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "request_id", Type: field.TypeString, Size: 64},
{Name: "model", Type: field.TypeString, Size: 100},
{Name: "requested_model", Type: field.TypeString, Nullable: true, Size: 100},
{Name: "upstream_model", Type: field.TypeString, Nullable: true, Size: 100},
{Name: "channel_id", Type: field.TypeInt64, Nullable: true},
{Name: "model_mapping_chain", Type: field.TypeString, Nullable: true, Size: 500},
{Name: "billing_tier", Type: field.TypeString, Nullable: true, Size: 50},
{Name: "billing_mode", Type: field.TypeString, Nullable: true, Size: 20},
{Name: "input_tokens", Type: field.TypeInt, Default: 0},
{Name: "output_tokens", Type: field.TypeInt, Default: 0},
{Name: "cache_creation_tokens", Type: field.TypeInt, Default: 0},
@@ -666,31 +970,31 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "usage_logs_api_keys_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[27]},
Columns: []*schema.Column{UsageLogsColumns[33]},
RefColumns: []*schema.Column{APIKeysColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_accounts_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[28]},
Columns: []*schema.Column{UsageLogsColumns[34]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_groups_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[29]},
Columns: []*schema.Column{UsageLogsColumns[35]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "usage_logs_users_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[30]},
Columns: []*schema.Column{UsageLogsColumns[36]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_user_subscriptions_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[31]},
Columns: []*schema.Column{UsageLogsColumns[37]},
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
OnDelete: schema.SetNull,
},
@@ -699,38 +1003,43 @@ var (
{
Name: "usagelog_user_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[30]},
Columns: []*schema.Column{UsageLogsColumns[36]},
},
{
Name: "usagelog_api_key_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[27]},
Columns: []*schema.Column{UsageLogsColumns[33]},
},
{
Name: "usagelog_account_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[28]},
Columns: []*schema.Column{UsageLogsColumns[34]},
},
{
Name: "usagelog_group_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[29]},
Columns: []*schema.Column{UsageLogsColumns[35]},
},
{
Name: "usagelog_subscription_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[31]},
Columns: []*schema.Column{UsageLogsColumns[37]},
},
{
Name: "usagelog_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[26]},
Columns: []*schema.Column{UsageLogsColumns[32]},
},
{
Name: "usagelog_model",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[2]},
},
{
Name: "usagelog_requested_model",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[3]},
},
{
Name: "usagelog_request_id",
Unique: false,
@@ -739,12 +1048,17 @@ var (
{
Name: "usagelog_user_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[30], UsageLogsColumns[26]},
Columns: []*schema.Column{UsageLogsColumns[36], UsageLogsColumns[32]},
},
{
Name: "usagelog_api_key_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[27], UsageLogsColumns[26]},
Columns: []*schema.Column{UsageLogsColumns[33], UsageLogsColumns[32]},
},
{
Name: "usagelog_group_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[35], UsageLogsColumns[32]},
},
},
}
@@ -765,6 +1079,11 @@ var (
{Name: "totp_secret_encrypted", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "totp_enabled", Type: field.TypeBool, Default: false},
{Name: "totp_enabled_at", Type: field.TypeTime, Nullable: true},
{Name: "balance_notify_enabled", Type: field.TypeBool, Default: true},
{Name: "balance_notify_threshold_type", Type: field.TypeString, Default: "fixed"},
{Name: "balance_notify_threshold", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "balance_notify_extra_emails", Type: field.TypeString, Default: "[]", SchemaType: map[string]string{"postgres": "text"}},
{Name: "total_recharged", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
}
// UsersTable holds the schema information for the "users" table.
UsersTable = &schema.Table{
@@ -970,6 +1289,11 @@ var (
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[5]},
},
{
Name: "usersubscription_user_id_status_expires_at",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[16], UserSubscriptionsColumns[6], UserSubscriptionsColumns[5]},
},
{
Name: "usersubscription_assigned_by",
Unique: false,
@@ -996,11 +1320,18 @@ var (
AnnouncementReadsTable,
ErrorPassthroughRulesTable,
GroupsTable,
IdempotencyRecordsTable,
PaymentAuditLogsTable,
PaymentOrdersTable,
PaymentProviderInstancesTable,
PromoCodesTable,
PromoCodeUsagesTable,
ProxiesTable,
RedeemCodesTable,
SecuritySecretsTable,
SettingsTable,
SubscriptionPlansTable,
TLSFingerprintProfilesTable,
UsageCleanupTasksTable,
UsageLogsTable,
UsersTable,
@@ -1040,6 +1371,19 @@ func init() {
GroupsTable.Annotation = &entsql.Annotation{
Table: "groups",
}
IdempotencyRecordsTable.Annotation = &entsql.Annotation{
Table: "idempotency_records",
}
PaymentAuditLogsTable.Annotation = &entsql.Annotation{
Table: "payment_audit_logs",
}
PaymentOrdersTable.ForeignKeys[0].RefTable = UsersTable
PaymentOrdersTable.Annotation = &entsql.Annotation{
Table: "payment_orders",
}
PaymentProviderInstancesTable.Annotation = &entsql.Annotation{
Table: "payment_provider_instances",
}
PromoCodesTable.Annotation = &entsql.Annotation{
Table: "promo_codes",
}
@@ -1056,9 +1400,18 @@ func init() {
RedeemCodesTable.Annotation = &entsql.Annotation{
Table: "redeem_codes",
}
SecuritySecretsTable.Annotation = &entsql.Annotation{
Table: "security_secrets",
}
SettingsTable.Annotation = &entsql.Annotation{
Table: "settings",
}
SubscriptionPlansTable.Annotation = &entsql.Annotation{
Table: "subscription_plans",
}
TLSFingerprintProfilesTable.Annotation = &entsql.Annotation{
Table: "tls_fingerprint_profiles",
}
UsageCleanupTasksTable.Annotation = &entsql.Annotation{
Table: "usage_cleanup_tasks",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
// 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/paymentauditlog"
)
// PaymentAuditLog is the model entity for the PaymentAuditLog schema.
type PaymentAuditLog struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// OrderID holds the value of the "order_id" field.
OrderID string `json:"order_id,omitempty"`
// Action holds the value of the "action" field.
Action string `json:"action,omitempty"`
// Detail holds the value of the "detail" field.
Detail string `json:"detail,omitempty"`
// Operator holds the value of the "operator" field.
Operator string `json:"operator,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 (*PaymentAuditLog) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentauditlog.FieldID:
values[i] = new(sql.NullInt64)
case paymentauditlog.FieldOrderID, paymentauditlog.FieldAction, paymentauditlog.FieldDetail, paymentauditlog.FieldOperator:
values[i] = new(sql.NullString)
case paymentauditlog.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 PaymentAuditLog fields.
func (_m *PaymentAuditLog) 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 paymentauditlog.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 paymentauditlog.FieldOrderID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field order_id", values[i])
} else if value.Valid {
_m.OrderID = value.String
}
case paymentauditlog.FieldAction:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field action", values[i])
} else if value.Valid {
_m.Action = value.String
}
case paymentauditlog.FieldDetail:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field detail", values[i])
} else if value.Valid {
_m.Detail = value.String
}
case paymentauditlog.FieldOperator:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field operator", values[i])
} else if value.Valid {
_m.Operator = value.String
}
case paymentauditlog.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 PaymentAuditLog.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentAuditLog) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this PaymentAuditLog.
// Note that you need to call PaymentAuditLog.Unwrap() before calling this method if this PaymentAuditLog
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentAuditLog) Update() *PaymentAuditLogUpdateOne {
return NewPaymentAuditLogClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentAuditLog 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 *PaymentAuditLog) Unwrap() *PaymentAuditLog {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentAuditLog is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentAuditLog) String() string {
var builder strings.Builder
builder.WriteString("PaymentAuditLog(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("order_id=")
builder.WriteString(_m.OrderID)
builder.WriteString(", ")
builder.WriteString("action=")
builder.WriteString(_m.Action)
builder.WriteString(", ")
builder.WriteString("detail=")
builder.WriteString(_m.Detail)
builder.WriteString(", ")
builder.WriteString("operator=")
builder.WriteString(_m.Operator)
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// PaymentAuditLogs is a parsable slice of PaymentAuditLog.
type PaymentAuditLogs []*PaymentAuditLog

View File

@@ -0,0 +1,96 @@
// Code generated by ent, DO NOT EDIT.
package paymentauditlog
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the paymentauditlog type in the database.
Label = "payment_audit_log"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldOrderID holds the string denoting the order_id field in the database.
FieldOrderID = "order_id"
// FieldAction holds the string denoting the action field in the database.
FieldAction = "action"
// FieldDetail holds the string denoting the detail field in the database.
FieldDetail = "detail"
// FieldOperator holds the string denoting the operator field in the database.
FieldOperator = "operator"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// Table holds the table name of the paymentauditlog in the database.
Table = "payment_audit_logs"
)
// Columns holds all SQL columns for paymentauditlog fields.
var Columns = []string{
FieldID,
FieldOrderID,
FieldAction,
FieldDetail,
FieldOperator,
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 (
// OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save.
OrderIDValidator func(string) error
// ActionValidator is a validator for the "action" field. It is called by the builders before save.
ActionValidator func(string) error
// DefaultDetail holds the default value on creation for the "detail" field.
DefaultDetail string
// DefaultOperator holds the default value on creation for the "operator" field.
DefaultOperator string
// OperatorValidator is a validator for the "operator" field. It is called by the builders before save.
OperatorValidator 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 PaymentAuditLog 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()
}
// ByOrderID orders the results by the order_id field.
func ByOrderID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOrderID, opts...).ToFunc()
}
// ByAction orders the results by the action field.
func ByAction(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAction, opts...).ToFunc()
}
// ByDetail orders the results by the detail field.
func ByDetail(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDetail, opts...).ToFunc()
}
// ByOperator orders the results by the operator field.
func ByOperator(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOperator, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}

View File

@@ -0,0 +1,395 @@
// Code generated by ent, DO NOT EDIT.
package paymentauditlog
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.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldID, id))
}
// OrderID applies equality check predicate on the "order_id" field. It's identical to OrderIDEQ.
func OrderID(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v))
}
// Action applies equality check predicate on the "action" field. It's identical to ActionEQ.
func Action(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v))
}
// Detail applies equality check predicate on the "detail" field. It's identical to DetailEQ.
func Detail(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v))
}
// Operator applies equality check predicate on the "operator" field. It's identical to OperatorEQ.
func Operator(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v))
}
// OrderIDEQ applies the EQ predicate on the "order_id" field.
func OrderIDEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v))
}
// OrderIDNEQ applies the NEQ predicate on the "order_id" field.
func OrderIDNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOrderID, v))
}
// OrderIDIn applies the In predicate on the "order_id" field.
func OrderIDIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldOrderID, vs...))
}
// OrderIDNotIn applies the NotIn predicate on the "order_id" field.
func OrderIDNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOrderID, vs...))
}
// OrderIDGT applies the GT predicate on the "order_id" field.
func OrderIDGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldOrderID, v))
}
// OrderIDGTE applies the GTE predicate on the "order_id" field.
func OrderIDGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldOrderID, v))
}
// OrderIDLT applies the LT predicate on the "order_id" field.
func OrderIDLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldOrderID, v))
}
// OrderIDLTE applies the LTE predicate on the "order_id" field.
func OrderIDLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldOrderID, v))
}
// OrderIDContains applies the Contains predicate on the "order_id" field.
func OrderIDContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldOrderID, v))
}
// OrderIDHasPrefix applies the HasPrefix predicate on the "order_id" field.
func OrderIDHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOrderID, v))
}
// OrderIDHasSuffix applies the HasSuffix predicate on the "order_id" field.
func OrderIDHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOrderID, v))
}
// OrderIDEqualFold applies the EqualFold predicate on the "order_id" field.
func OrderIDEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOrderID, v))
}
// OrderIDContainsFold applies the ContainsFold predicate on the "order_id" field.
func OrderIDContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOrderID, v))
}
// ActionEQ applies the EQ predicate on the "action" field.
func ActionEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v))
}
// ActionNEQ applies the NEQ predicate on the "action" field.
func ActionNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldAction, v))
}
// ActionIn applies the In predicate on the "action" field.
func ActionIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldAction, vs...))
}
// ActionNotIn applies the NotIn predicate on the "action" field.
func ActionNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldAction, vs...))
}
// ActionGT applies the GT predicate on the "action" field.
func ActionGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldAction, v))
}
// ActionGTE applies the GTE predicate on the "action" field.
func ActionGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldAction, v))
}
// ActionLT applies the LT predicate on the "action" field.
func ActionLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldAction, v))
}
// ActionLTE applies the LTE predicate on the "action" field.
func ActionLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldAction, v))
}
// ActionContains applies the Contains predicate on the "action" field.
func ActionContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldAction, v))
}
// ActionHasPrefix applies the HasPrefix predicate on the "action" field.
func ActionHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldAction, v))
}
// ActionHasSuffix applies the HasSuffix predicate on the "action" field.
func ActionHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldAction, v))
}
// ActionEqualFold applies the EqualFold predicate on the "action" field.
func ActionEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldAction, v))
}
// ActionContainsFold applies the ContainsFold predicate on the "action" field.
func ActionContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldAction, v))
}
// DetailEQ applies the EQ predicate on the "detail" field.
func DetailEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v))
}
// DetailNEQ applies the NEQ predicate on the "detail" field.
func DetailNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldDetail, v))
}
// DetailIn applies the In predicate on the "detail" field.
func DetailIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldDetail, vs...))
}
// DetailNotIn applies the NotIn predicate on the "detail" field.
func DetailNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldDetail, vs...))
}
// DetailGT applies the GT predicate on the "detail" field.
func DetailGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldDetail, v))
}
// DetailGTE applies the GTE predicate on the "detail" field.
func DetailGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldDetail, v))
}
// DetailLT applies the LT predicate on the "detail" field.
func DetailLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldDetail, v))
}
// DetailLTE applies the LTE predicate on the "detail" field.
func DetailLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldDetail, v))
}
// DetailContains applies the Contains predicate on the "detail" field.
func DetailContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldDetail, v))
}
// DetailHasPrefix applies the HasPrefix predicate on the "detail" field.
func DetailHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldDetail, v))
}
// DetailHasSuffix applies the HasSuffix predicate on the "detail" field.
func DetailHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldDetail, v))
}
// DetailEqualFold applies the EqualFold predicate on the "detail" field.
func DetailEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldDetail, v))
}
// DetailContainsFold applies the ContainsFold predicate on the "detail" field.
func DetailContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldDetail, v))
}
// OperatorEQ applies the EQ predicate on the "operator" field.
func OperatorEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v))
}
// OperatorNEQ applies the NEQ predicate on the "operator" field.
func OperatorNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOperator, v))
}
// OperatorIn applies the In predicate on the "operator" field.
func OperatorIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldOperator, vs...))
}
// OperatorNotIn applies the NotIn predicate on the "operator" field.
func OperatorNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOperator, vs...))
}
// OperatorGT applies the GT predicate on the "operator" field.
func OperatorGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldOperator, v))
}
// OperatorGTE applies the GTE predicate on the "operator" field.
func OperatorGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldOperator, v))
}
// OperatorLT applies the LT predicate on the "operator" field.
func OperatorLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldOperator, v))
}
// OperatorLTE applies the LTE predicate on the "operator" field.
func OperatorLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldOperator, v))
}
// OperatorContains applies the Contains predicate on the "operator" field.
func OperatorContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldOperator, v))
}
// OperatorHasPrefix applies the HasPrefix predicate on the "operator" field.
func OperatorHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOperator, v))
}
// OperatorHasSuffix applies the HasSuffix predicate on the "operator" field.
func OperatorHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOperator, v))
}
// OperatorEqualFold applies the EqualFold predicate on the "operator" field.
func OperatorEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOperator, v))
}
// OperatorContainsFold applies the ContainsFold predicate on the "operator" field.
func OperatorContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOperator, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldCreatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,696 @@
// 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/paymentauditlog"
)
// PaymentAuditLogCreate is the builder for creating a PaymentAuditLog entity.
type PaymentAuditLogCreate struct {
config
mutation *PaymentAuditLogMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetOrderID sets the "order_id" field.
func (_c *PaymentAuditLogCreate) SetOrderID(v string) *PaymentAuditLogCreate {
_c.mutation.SetOrderID(v)
return _c
}
// SetAction sets the "action" field.
func (_c *PaymentAuditLogCreate) SetAction(v string) *PaymentAuditLogCreate {
_c.mutation.SetAction(v)
return _c
}
// SetDetail sets the "detail" field.
func (_c *PaymentAuditLogCreate) SetDetail(v string) *PaymentAuditLogCreate {
_c.mutation.SetDetail(v)
return _c
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableDetail(v *string) *PaymentAuditLogCreate {
if v != nil {
_c.SetDetail(*v)
}
return _c
}
// SetOperator sets the "operator" field.
func (_c *PaymentAuditLogCreate) SetOperator(v string) *PaymentAuditLogCreate {
_c.mutation.SetOperator(v)
return _c
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableOperator(v *string) *PaymentAuditLogCreate {
if v != nil {
_c.SetOperator(*v)
}
return _c
}
// SetCreatedAt sets the "created_at" field.
func (_c *PaymentAuditLogCreate) SetCreatedAt(v time.Time) *PaymentAuditLogCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableCreatedAt(v *time.Time) *PaymentAuditLogCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_c *PaymentAuditLogCreate) Mutation() *PaymentAuditLogMutation {
return _c.mutation
}
// Save creates the PaymentAuditLog in the database.
func (_c *PaymentAuditLogCreate) Save(ctx context.Context) (*PaymentAuditLog, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *PaymentAuditLogCreate) SaveX(ctx context.Context) *PaymentAuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PaymentAuditLogCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PaymentAuditLogCreate) 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 *PaymentAuditLogCreate) defaults() {
if _, ok := _c.mutation.Detail(); !ok {
v := paymentauditlog.DefaultDetail
_c.mutation.SetDetail(v)
}
if _, ok := _c.mutation.Operator(); !ok {
v := paymentauditlog.DefaultOperator
_c.mutation.SetOperator(v)
}
if _, ok := _c.mutation.CreatedAt(); !ok {
v := paymentauditlog.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *PaymentAuditLogCreate) check() error {
if _, ok := _c.mutation.OrderID(); !ok {
return &ValidationError{Name: "order_id", err: errors.New(`ent: missing required field "PaymentAuditLog.order_id"`)}
}
if v, ok := _c.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if _, ok := _c.mutation.Action(); !ok {
return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "PaymentAuditLog.action"`)}
}
if v, ok := _c.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if _, ok := _c.mutation.Detail(); !ok {
return &ValidationError{Name: "detail", err: errors.New(`ent: missing required field "PaymentAuditLog.detail"`)}
}
if _, ok := _c.mutation.Operator(); !ok {
return &ValidationError{Name: "operator", err: errors.New(`ent: missing required field "PaymentAuditLog.operator"`)}
}
if v, ok := _c.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentAuditLog.created_at"`)}
}
return nil
}
func (_c *PaymentAuditLogCreate) sqlSave(ctx context.Context) (*PaymentAuditLog, 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 *PaymentAuditLogCreate) createSpec() (*PaymentAuditLog, *sqlgraph.CreateSpec) {
var (
_node = &PaymentAuditLog{config: _c.config}
_spec = sqlgraph.NewCreateSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
_node.OrderID = value
}
if value, ok := _c.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
_node.Action = value
}
if value, ok := _c.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
_node.Detail = value
}
if value, ok := _c.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
_node.Operator = value
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(paymentauditlog.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.PaymentAuditLog.Create().
// SetOrderID(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.PaymentAuditLogUpsert) {
// SetOrderID(v+v).
// }).
// Exec(ctx)
func (_c *PaymentAuditLogCreate) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertOne {
_c.conflict = opts
return &PaymentAuditLogUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *PaymentAuditLogCreate) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &PaymentAuditLogUpsertOne{
create: _c,
}
}
type (
// PaymentAuditLogUpsertOne is the builder for "upsert"-ing
// one PaymentAuditLog node.
PaymentAuditLogUpsertOne struct {
create *PaymentAuditLogCreate
}
// PaymentAuditLogUpsert is the "OnConflict" setter.
PaymentAuditLogUpsert struct {
*sql.UpdateSet
}
)
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsert) SetOrderID(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldOrderID, v)
return u
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateOrderID() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldOrderID)
return u
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsert) SetAction(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldAction, v)
return u
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateAction() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldAction)
return u
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsert) SetDetail(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldDetail, v)
return u
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateDetail() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldDetail)
return u
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsert) SetOperator(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldOperator, v)
return u
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateOperator() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldOperator)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *PaymentAuditLogUpsertOne) UpdateNewValues() *PaymentAuditLogUpsertOne {
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(paymentauditlog.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *PaymentAuditLogUpsertOne) Ignore() *PaymentAuditLogUpsertOne {
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 *PaymentAuditLogUpsertOne) DoNothing() *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreate.OnConflict
// documentation for more info.
func (u *PaymentAuditLogUpsertOne) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&PaymentAuditLogUpsert{UpdateSet: update})
}))
return u
}
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsertOne) SetOrderID(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOrderID(v)
})
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateOrderID() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOrderID()
})
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsertOne) SetAction(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetAction(v)
})
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateAction() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateAction()
})
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsertOne) SetDetail(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetDetail(v)
})
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateDetail() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateDetail()
})
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsertOne) SetOperator(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOperator(v)
})
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateOperator() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOperator()
})
}
// Exec executes the query.
func (u *PaymentAuditLogUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for PaymentAuditLogCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *PaymentAuditLogUpsertOne) 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 *PaymentAuditLogUpsertOne) 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 *PaymentAuditLogUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// PaymentAuditLogCreateBulk is the builder for creating many PaymentAuditLog entities in bulk.
type PaymentAuditLogCreateBulk struct {
config
err error
builders []*PaymentAuditLogCreate
conflict []sql.ConflictOption
}
// Save creates the PaymentAuditLog entities in the database.
func (_c *PaymentAuditLogCreateBulk) Save(ctx context.Context) ([]*PaymentAuditLog, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*PaymentAuditLog, 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.(*PaymentAuditLogMutation)
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 *PaymentAuditLogCreateBulk) SaveX(ctx context.Context) []*PaymentAuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PaymentAuditLogCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PaymentAuditLogCreateBulk) 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.PaymentAuditLog.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.PaymentAuditLogUpsert) {
// SetOrderID(v+v).
// }).
// Exec(ctx)
func (_c *PaymentAuditLogCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertBulk {
_c.conflict = opts
return &PaymentAuditLogUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *PaymentAuditLogCreateBulk) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &PaymentAuditLogUpsertBulk{
create: _c,
}
}
// PaymentAuditLogUpsertBulk is the builder for "upsert"-ing
// a bulk of PaymentAuditLog nodes.
type PaymentAuditLogUpsertBulk struct {
create *PaymentAuditLogCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *PaymentAuditLogUpsertBulk) UpdateNewValues() *PaymentAuditLogUpsertBulk {
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(paymentauditlog.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *PaymentAuditLogUpsertBulk) Ignore() *PaymentAuditLogUpsertBulk {
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 *PaymentAuditLogUpsertBulk) DoNothing() *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreateBulk.OnConflict
// documentation for more info.
func (u *PaymentAuditLogUpsertBulk) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&PaymentAuditLogUpsert{UpdateSet: update})
}))
return u
}
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsertBulk) SetOrderID(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOrderID(v)
})
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateOrderID() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOrderID()
})
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsertBulk) SetAction(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetAction(v)
})
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateAction() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateAction()
})
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsertBulk) SetDetail(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetDetail(v)
})
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateDetail() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateDetail()
})
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsertBulk) SetOperator(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOperator(v)
})
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateOperator() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOperator()
})
}
// Exec executes the query.
func (u *PaymentAuditLogUpsertBulk) 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 PaymentAuditLogCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for PaymentAuditLogCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *PaymentAuditLogUpsertBulk) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}

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/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogDelete is the builder for deleting a PaymentAuditLog entity.
type PaymentAuditLogDelete struct {
config
hooks []Hook
mutation *PaymentAuditLogMutation
}
// Where appends a list predicates to the PaymentAuditLogDelete builder.
func (_d *PaymentAuditLogDelete) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentAuditLogDelete) 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 *PaymentAuditLogDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentAuditLogDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.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
}
// PaymentAuditLogDeleteOne is the builder for deleting a single PaymentAuditLog entity.
type PaymentAuditLogDeleteOne struct {
_d *PaymentAuditLogDelete
}
// Where appends a list predicates to the PaymentAuditLogDelete builder.
func (_d *PaymentAuditLogDeleteOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentAuditLogDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentauditlog.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentAuditLogDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

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/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogQuery is the builder for querying PaymentAuditLog entities.
type PaymentAuditLogQuery struct {
config
ctx *QueryContext
order []paymentauditlog.OrderOption
inters []Interceptor
predicates []predicate.PaymentAuditLog
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 PaymentAuditLogQuery builder.
func (_q *PaymentAuditLogQuery) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentAuditLogQuery) Limit(limit int) *PaymentAuditLogQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentAuditLogQuery) Offset(offset int) *PaymentAuditLogQuery {
_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 *PaymentAuditLogQuery) Unique(unique bool) *PaymentAuditLogQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentAuditLogQuery) Order(o ...paymentauditlog.OrderOption) *PaymentAuditLogQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first PaymentAuditLog entity from the query.
// Returns a *NotFoundError when no PaymentAuditLog was found.
func (_q *PaymentAuditLogQuery) First(ctx context.Context) (*PaymentAuditLog, 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{paymentauditlog.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) FirstX(ctx context.Context) *PaymentAuditLog {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentAuditLog ID from the query.
// Returns a *NotFoundError when no PaymentAuditLog ID was found.
func (_q *PaymentAuditLogQuery) 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{paymentauditlog.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentAuditLog entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentAuditLog entity is found.
// Returns a *NotFoundError when no PaymentAuditLog entities are found.
func (_q *PaymentAuditLogQuery) Only(ctx context.Context) (*PaymentAuditLog, 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{paymentauditlog.Label}
default:
return nil, &NotSingularError{paymentauditlog.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) OnlyX(ctx context.Context) *PaymentAuditLog {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentAuditLog ID in the query.
// Returns a *NotSingularError when more than one PaymentAuditLog ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentAuditLogQuery) 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{paymentauditlog.Label}
default:
err = &NotSingularError{paymentauditlog.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) 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 PaymentAuditLogs.
func (_q *PaymentAuditLogQuery) All(ctx context.Context) ([]*PaymentAuditLog, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentAuditLog, *PaymentAuditLogQuery]()
return withInterceptors[[]*PaymentAuditLog](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) AllX(ctx context.Context) []*PaymentAuditLog {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentAuditLog IDs.
func (_q *PaymentAuditLogQuery) 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(paymentauditlog.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) 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[*PaymentAuditLogQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentAuditLogQuery 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 *PaymentAuditLogQuery) Clone() *PaymentAuditLogQuery {
if _q == nil {
return nil
}
return &PaymentAuditLogQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentauditlog.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentAuditLog{}, _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 {
// OrderID string `json:"order_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentAuditLog.Query().
// GroupBy(paymentauditlog.FieldOrderID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentAuditLogQuery) GroupBy(field string, fields ...string) *PaymentAuditLogGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentAuditLogGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentauditlog.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 {
// OrderID string `json:"order_id,omitempty"`
// }
//
// client.PaymentAuditLog.Query().
// Select(paymentauditlog.FieldOrderID).
// Scan(ctx, &v)
func (_q *PaymentAuditLogQuery) Select(fields ...string) *PaymentAuditLogSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentAuditLogSelect{PaymentAuditLogQuery: _q}
sbuild.label = paymentauditlog.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentAuditLogSelect configured with the given aggregations.
func (_q *PaymentAuditLogQuery) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentAuditLogQuery) 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 !paymentauditlog.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 *PaymentAuditLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentAuditLog, error) {
var (
nodes = []*PaymentAuditLog{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentAuditLog).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentAuditLog{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 *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.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, paymentauditlog.FieldID)
for i := range fields {
if fields[i] != paymentauditlog.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 *PaymentAuditLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentauditlog.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentauditlog.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 *PaymentAuditLogQuery) ForUpdate(opts ...sql.LockOption) *PaymentAuditLogQuery {
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 *PaymentAuditLogQuery) ForShare(opts ...sql.LockOption) *PaymentAuditLogQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentAuditLogGroupBy is the group-by builder for PaymentAuditLog entities.
type PaymentAuditLogGroupBy struct {
selector
build *PaymentAuditLogQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentAuditLogGroupBy) Aggregate(fns ...AggregateFunc) *PaymentAuditLogGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentAuditLogGroupBy) 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[*PaymentAuditLogQuery, *PaymentAuditLogGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentAuditLogGroupBy) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, 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)
}
// PaymentAuditLogSelect is the builder for selecting fields of PaymentAuditLog entities.
type PaymentAuditLogSelect struct {
*PaymentAuditLogQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentAuditLogSelect) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentAuditLogSelect) 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[*PaymentAuditLogQuery, *PaymentAuditLogSelect](ctx, _s.PaymentAuditLogQuery, _s, _s.inters, v)
}
func (_s *PaymentAuditLogSelect) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, 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)
}

View File

@@ -0,0 +1,357 @@
// 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/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogUpdate is the builder for updating PaymentAuditLog entities.
type PaymentAuditLogUpdate struct {
config
hooks []Hook
mutation *PaymentAuditLogMutation
}
// Where appends a list predicates to the PaymentAuditLogUpdate builder.
func (_u *PaymentAuditLogUpdate) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetOrderID sets the "order_id" field.
func (_u *PaymentAuditLogUpdate) SetOrderID(v string) *PaymentAuditLogUpdate {
_u.mutation.SetOrderID(v)
return _u
}
// SetNillableOrderID sets the "order_id" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableOrderID(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetOrderID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *PaymentAuditLogUpdate) SetAction(v string) *PaymentAuditLogUpdate {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableAction(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetDetail sets the "detail" field.
func (_u *PaymentAuditLogUpdate) SetDetail(v string) *PaymentAuditLogUpdate {
_u.mutation.SetDetail(v)
return _u
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableDetail(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetDetail(*v)
}
return _u
}
// SetOperator sets the "operator" field.
func (_u *PaymentAuditLogUpdate) SetOperator(v string) *PaymentAuditLogUpdate {
_u.mutation.SetOperator(v)
return _u
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableOperator(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetOperator(*v)
}
return _u
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_u *PaymentAuditLogUpdate) Mutation() *PaymentAuditLogMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *PaymentAuditLogUpdate) 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 *PaymentAuditLogUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *PaymentAuditLogUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentAuditLogUpdate) 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 *PaymentAuditLogUpdate) check() error {
if v, ok := _u.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if v, ok := _u.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
return nil
}
func (_u *PaymentAuditLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.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.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
}
if value, ok := _u.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentauditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// PaymentAuditLogUpdateOne is the builder for updating a single PaymentAuditLog entity.
type PaymentAuditLogUpdateOne struct {
config
fields []string
hooks []Hook
mutation *PaymentAuditLogMutation
}
// SetOrderID sets the "order_id" field.
func (_u *PaymentAuditLogUpdateOne) SetOrderID(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetOrderID(v)
return _u
}
// SetNillableOrderID sets the "order_id" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableOrderID(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetOrderID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *PaymentAuditLogUpdateOne) SetAction(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableAction(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetDetail sets the "detail" field.
func (_u *PaymentAuditLogUpdateOne) SetDetail(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetDetail(v)
return _u
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableDetail(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetDetail(*v)
}
return _u
}
// SetOperator sets the "operator" field.
func (_u *PaymentAuditLogUpdateOne) SetOperator(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetOperator(v)
return _u
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableOperator(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetOperator(*v)
}
return _u
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_u *PaymentAuditLogUpdateOne) Mutation() *PaymentAuditLogMutation {
return _u.mutation
}
// Where appends a list predicates to the PaymentAuditLogUpdate builder.
func (_u *PaymentAuditLogUpdateOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdateOne {
_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 *PaymentAuditLogUpdateOne) Select(field string, fields ...string) *PaymentAuditLogUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated PaymentAuditLog entity.
func (_u *PaymentAuditLogUpdateOne) Save(ctx context.Context) (*PaymentAuditLog, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentAuditLogUpdateOne) SaveX(ctx context.Context) *PaymentAuditLog {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *PaymentAuditLogUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentAuditLogUpdateOne) 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 *PaymentAuditLogUpdateOne) check() error {
if v, ok := _u.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if v, ok := _u.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
return nil
}
func (_u *PaymentAuditLogUpdateOne) sqlSave(ctx context.Context) (_node *PaymentAuditLog, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentAuditLog.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, paymentauditlog.FieldID)
for _, f := range fields {
if !paymentauditlog.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != paymentauditlog.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.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
}
if value, ok := _u.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
}
_node = &PaymentAuditLog{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{paymentauditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

589
backend/ent/paymentorder.go Normal file
View File

@@ -0,0 +1,589 @@
// 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/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// PaymentOrder is the model entity for the PaymentOrder schema.
type PaymentOrder struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// UserEmail holds the value of the "user_email" field.
UserEmail string `json:"user_email,omitempty"`
// UserName holds the value of the "user_name" field.
UserName string `json:"user_name,omitempty"`
// UserNotes holds the value of the "user_notes" field.
UserNotes *string `json:"user_notes,omitempty"`
// Amount holds the value of the "amount" field.
Amount float64 `json:"amount,omitempty"`
// PayAmount holds the value of the "pay_amount" field.
PayAmount float64 `json:"pay_amount,omitempty"`
// FeeRate holds the value of the "fee_rate" field.
FeeRate float64 `json:"fee_rate,omitempty"`
// RechargeCode holds the value of the "recharge_code" field.
RechargeCode string `json:"recharge_code,omitempty"`
// OutTradeNo holds the value of the "out_trade_no" field.
OutTradeNo string `json:"out_trade_no,omitempty"`
// PaymentType holds the value of the "payment_type" field.
PaymentType string `json:"payment_type,omitempty"`
// PaymentTradeNo holds the value of the "payment_trade_no" field.
PaymentTradeNo string `json:"payment_trade_no,omitempty"`
// PayURL holds the value of the "pay_url" field.
PayURL *string `json:"pay_url,omitempty"`
// QrCode holds the value of the "qr_code" field.
QrCode *string `json:"qr_code,omitempty"`
// QrCodeImg holds the value of the "qr_code_img" field.
QrCodeImg *string `json:"qr_code_img,omitempty"`
// OrderType holds the value of the "order_type" field.
OrderType string `json:"order_type,omitempty"`
// PlanID holds the value of the "plan_id" field.
PlanID *int64 `json:"plan_id,omitempty"`
// SubscriptionGroupID holds the value of the "subscription_group_id" field.
SubscriptionGroupID *int64 `json:"subscription_group_id,omitempty"`
// SubscriptionDays holds the value of the "subscription_days" field.
SubscriptionDays *int `json:"subscription_days,omitempty"`
// ProviderInstanceID holds the value of the "provider_instance_id" field.
ProviderInstanceID *string `json:"provider_instance_id,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// RefundAmount holds the value of the "refund_amount" field.
RefundAmount float64 `json:"refund_amount,omitempty"`
// RefundReason holds the value of the "refund_reason" field.
RefundReason *string `json:"refund_reason,omitempty"`
// RefundAt holds the value of the "refund_at" field.
RefundAt *time.Time `json:"refund_at,omitempty"`
// ForceRefund holds the value of the "force_refund" field.
ForceRefund bool `json:"force_refund,omitempty"`
// RefundRequestedAt holds the value of the "refund_requested_at" field.
RefundRequestedAt *time.Time `json:"refund_requested_at,omitempty"`
// RefundRequestReason holds the value of the "refund_request_reason" field.
RefundRequestReason *string `json:"refund_request_reason,omitempty"`
// RefundRequestedBy holds the value of the "refund_requested_by" field.
RefundRequestedBy *string `json:"refund_requested_by,omitempty"`
// ExpiresAt holds the value of the "expires_at" field.
ExpiresAt time.Time `json:"expires_at,omitempty"`
// PaidAt holds the value of the "paid_at" field.
PaidAt *time.Time `json:"paid_at,omitempty"`
// CompletedAt holds the value of the "completed_at" field.
CompletedAt *time.Time `json:"completed_at,omitempty"`
// FailedAt holds the value of the "failed_at" field.
FailedAt *time.Time `json:"failed_at,omitempty"`
// FailedReason holds the value of the "failed_reason" field.
FailedReason *string `json:"failed_reason,omitempty"`
// ClientIP holds the value of the "client_ip" field.
ClientIP string `json:"client_ip,omitempty"`
// SrcHost holds the value of the "src_host" field.
SrcHost string `json:"src_host,omitempty"`
// SrcURL holds the value of the "src_url" field.
SrcURL *string `json:"src_url,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"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the PaymentOrderQuery when eager-loading is set.
Edges PaymentOrderEdges `json:"edges"`
selectValues sql.SelectValues
}
// PaymentOrderEdges holds the relations/edges for other nodes in the graph.
type PaymentOrderEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// UserOrErr returns the User value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e PaymentOrderEdges) UserOrErr() (*User, error) {
if e.User != nil {
return e.User, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: user.Label}
}
return nil, &NotLoadedError{edge: "user"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*PaymentOrder) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentorder.FieldForceRefund:
values[i] = new(sql.NullBool)
case paymentorder.FieldAmount, paymentorder.FieldPayAmount, paymentorder.FieldFeeRate, paymentorder.FieldRefundAmount:
values[i] = new(sql.NullFloat64)
case paymentorder.FieldID, paymentorder.FieldUserID, paymentorder.FieldPlanID, paymentorder.FieldSubscriptionGroupID, paymentorder.FieldSubscriptionDays:
values[i] = new(sql.NullInt64)
case paymentorder.FieldUserEmail, paymentorder.FieldUserName, paymentorder.FieldUserNotes, paymentorder.FieldRechargeCode, paymentorder.FieldOutTradeNo, paymentorder.FieldPaymentType, paymentorder.FieldPaymentTradeNo, paymentorder.FieldPayURL, paymentorder.FieldQrCode, paymentorder.FieldQrCodeImg, paymentorder.FieldOrderType, paymentorder.FieldProviderInstanceID, paymentorder.FieldStatus, paymentorder.FieldRefundReason, paymentorder.FieldRefundRequestReason, paymentorder.FieldRefundRequestedBy, paymentorder.FieldFailedReason, paymentorder.FieldClientIP, paymentorder.FieldSrcHost, paymentorder.FieldSrcURL:
values[i] = new(sql.NullString)
case paymentorder.FieldRefundAt, paymentorder.FieldRefundRequestedAt, paymentorder.FieldExpiresAt, paymentorder.FieldPaidAt, paymentorder.FieldCompletedAt, paymentorder.FieldFailedAt, paymentorder.FieldCreatedAt, paymentorder.FieldUpdatedAt:
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 PaymentOrder fields.
func (_m *PaymentOrder) 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 paymentorder.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 paymentorder.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 paymentorder.FieldUserEmail:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_email", values[i])
} else if value.Valid {
_m.UserEmail = value.String
}
case paymentorder.FieldUserName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_name", values[i])
} else if value.Valid {
_m.UserName = value.String
}
case paymentorder.FieldUserNotes:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_notes", values[i])
} else if value.Valid {
_m.UserNotes = new(string)
*_m.UserNotes = value.String
}
case paymentorder.FieldAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field amount", values[i])
} else if value.Valid {
_m.Amount = value.Float64
}
case paymentorder.FieldPayAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field pay_amount", values[i])
} else if value.Valid {
_m.PayAmount = value.Float64
}
case paymentorder.FieldFeeRate:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field fee_rate", values[i])
} else if value.Valid {
_m.FeeRate = value.Float64
}
case paymentorder.FieldRechargeCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field recharge_code", values[i])
} else if value.Valid {
_m.RechargeCode = value.String
}
case paymentorder.FieldOutTradeNo:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field out_trade_no", values[i])
} else if value.Valid {
_m.OutTradeNo = value.String
}
case paymentorder.FieldPaymentType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_type", values[i])
} else if value.Valid {
_m.PaymentType = value.String
}
case paymentorder.FieldPaymentTradeNo:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_trade_no", values[i])
} else if value.Valid {
_m.PaymentTradeNo = value.String
}
case paymentorder.FieldPayURL:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field pay_url", values[i])
} else if value.Valid {
_m.PayURL = new(string)
*_m.PayURL = value.String
}
case paymentorder.FieldQrCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field qr_code", values[i])
} else if value.Valid {
_m.QrCode = new(string)
*_m.QrCode = value.String
}
case paymentorder.FieldQrCodeImg:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field qr_code_img", values[i])
} else if value.Valid {
_m.QrCodeImg = new(string)
*_m.QrCodeImg = value.String
}
case paymentorder.FieldOrderType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field order_type", values[i])
} else if value.Valid {
_m.OrderType = value.String
}
case paymentorder.FieldPlanID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field plan_id", values[i])
} else if value.Valid {
_m.PlanID = new(int64)
*_m.PlanID = value.Int64
}
case paymentorder.FieldSubscriptionGroupID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subscription_group_id", values[i])
} else if value.Valid {
_m.SubscriptionGroupID = new(int64)
*_m.SubscriptionGroupID = value.Int64
}
case paymentorder.FieldSubscriptionDays:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subscription_days", values[i])
} else if value.Valid {
_m.SubscriptionDays = new(int)
*_m.SubscriptionDays = int(value.Int64)
}
case paymentorder.FieldProviderInstanceID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_instance_id", values[i])
} else if value.Valid {
_m.ProviderInstanceID = new(string)
*_m.ProviderInstanceID = value.String
}
case paymentorder.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 paymentorder.FieldRefundAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field refund_amount", values[i])
} else if value.Valid {
_m.RefundAmount = value.Float64
}
case paymentorder.FieldRefundReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_reason", values[i])
} else if value.Valid {
_m.RefundReason = new(string)
*_m.RefundReason = value.String
}
case paymentorder.FieldRefundAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field refund_at", values[i])
} else if value.Valid {
_m.RefundAt = new(time.Time)
*_m.RefundAt = value.Time
}
case paymentorder.FieldForceRefund:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field force_refund", values[i])
} else if value.Valid {
_m.ForceRefund = value.Bool
}
case paymentorder.FieldRefundRequestedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field refund_requested_at", values[i])
} else if value.Valid {
_m.RefundRequestedAt = new(time.Time)
*_m.RefundRequestedAt = value.Time
}
case paymentorder.FieldRefundRequestReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_request_reason", values[i])
} else if value.Valid {
_m.RefundRequestReason = new(string)
*_m.RefundRequestReason = value.String
}
case paymentorder.FieldRefundRequestedBy:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_requested_by", values[i])
} else if value.Valid {
_m.RefundRequestedBy = new(string)
*_m.RefundRequestedBy = value.String
}
case paymentorder.FieldExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
} else if value.Valid {
_m.ExpiresAt = value.Time
}
case paymentorder.FieldPaidAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field paid_at", values[i])
} else if value.Valid {
_m.PaidAt = new(time.Time)
*_m.PaidAt = value.Time
}
case paymentorder.FieldCompletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field completed_at", values[i])
} else if value.Valid {
_m.CompletedAt = new(time.Time)
*_m.CompletedAt = value.Time
}
case paymentorder.FieldFailedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field failed_at", values[i])
} else if value.Valid {
_m.FailedAt = new(time.Time)
*_m.FailedAt = value.Time
}
case paymentorder.FieldFailedReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field failed_reason", values[i])
} else if value.Valid {
_m.FailedReason = new(string)
*_m.FailedReason = value.String
}
case paymentorder.FieldClientIP:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field client_ip", values[i])
} else if value.Valid {
_m.ClientIP = value.String
}
case paymentorder.FieldSrcHost:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field src_host", values[i])
} else if value.Valid {
_m.SrcHost = value.String
}
case paymentorder.FieldSrcURL:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field src_url", values[i])
} else if value.Valid {
_m.SrcURL = new(string)
*_m.SrcURL = value.String
}
case paymentorder.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 paymentorder.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
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the PaymentOrder.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentOrder) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the PaymentOrder entity.
func (_m *PaymentOrder) QueryUser() *UserQuery {
return NewPaymentOrderClient(_m.config).QueryUser(_m)
}
// Update returns a builder for updating this PaymentOrder.
// Note that you need to call PaymentOrder.Unwrap() before calling this method if this PaymentOrder
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentOrder) Update() *PaymentOrderUpdateOne {
return NewPaymentOrderClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentOrder 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 *PaymentOrder) Unwrap() *PaymentOrder {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentOrder is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentOrder) String() string {
var builder strings.Builder
builder.WriteString("PaymentOrder(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
builder.WriteString("user_email=")
builder.WriteString(_m.UserEmail)
builder.WriteString(", ")
builder.WriteString("user_name=")
builder.WriteString(_m.UserName)
builder.WriteString(", ")
if v := _m.UserNotes; v != nil {
builder.WriteString("user_notes=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("amount=")
builder.WriteString(fmt.Sprintf("%v", _m.Amount))
builder.WriteString(", ")
builder.WriteString("pay_amount=")
builder.WriteString(fmt.Sprintf("%v", _m.PayAmount))
builder.WriteString(", ")
builder.WriteString("fee_rate=")
builder.WriteString(fmt.Sprintf("%v", _m.FeeRate))
builder.WriteString(", ")
builder.WriteString("recharge_code=")
builder.WriteString(_m.RechargeCode)
builder.WriteString(", ")
builder.WriteString("out_trade_no=")
builder.WriteString(_m.OutTradeNo)
builder.WriteString(", ")
builder.WriteString("payment_type=")
builder.WriteString(_m.PaymentType)
builder.WriteString(", ")
builder.WriteString("payment_trade_no=")
builder.WriteString(_m.PaymentTradeNo)
builder.WriteString(", ")
if v := _m.PayURL; v != nil {
builder.WriteString("pay_url=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.QrCode; v != nil {
builder.WriteString("qr_code=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.QrCodeImg; v != nil {
builder.WriteString("qr_code_img=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("order_type=")
builder.WriteString(_m.OrderType)
builder.WriteString(", ")
if v := _m.PlanID; v != nil {
builder.WriteString("plan_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SubscriptionGroupID; v != nil {
builder.WriteString("subscription_group_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SubscriptionDays; v != nil {
builder.WriteString("subscription_days=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.ProviderInstanceID; v != nil {
builder.WriteString("provider_instance_id=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
builder.WriteString("refund_amount=")
builder.WriteString(fmt.Sprintf("%v", _m.RefundAmount))
builder.WriteString(", ")
if v := _m.RefundReason; v != nil {
builder.WriteString("refund_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.RefundAt; v != nil {
builder.WriteString("refund_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("force_refund=")
builder.WriteString(fmt.Sprintf("%v", _m.ForceRefund))
builder.WriteString(", ")
if v := _m.RefundRequestedAt; v != nil {
builder.WriteString("refund_requested_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.RefundRequestReason; v != nil {
builder.WriteString("refund_request_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.RefundRequestedBy; v != nil {
builder.WriteString("refund_requested_by=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("expires_at=")
builder.WriteString(_m.ExpiresAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := _m.PaidAt; v != nil {
builder.WriteString("paid_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.CompletedAt; v != nil {
builder.WriteString("completed_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.FailedAt; v != nil {
builder.WriteString("failed_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.FailedReason; v != nil {
builder.WriteString("failed_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("client_ip=")
builder.WriteString(_m.ClientIP)
builder.WriteString(", ")
builder.WriteString("src_host=")
builder.WriteString(_m.SrcHost)
builder.WriteString(", ")
if v := _m.SrcURL; v != nil {
builder.WriteString("src_url=")
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.WriteByte(')')
return builder.String()
}
// PaymentOrders is a parsable slice of PaymentOrder.
type PaymentOrders []*PaymentOrder

View File

@@ -0,0 +1,406 @@
// Code generated by ent, DO NOT EDIT.
package paymentorder
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the paymentorder type in the database.
Label = "payment_order"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldUserEmail holds the string denoting the user_email field in the database.
FieldUserEmail = "user_email"
// FieldUserName holds the string denoting the user_name field in the database.
FieldUserName = "user_name"
// FieldUserNotes holds the string denoting the user_notes field in the database.
FieldUserNotes = "user_notes"
// FieldAmount holds the string denoting the amount field in the database.
FieldAmount = "amount"
// FieldPayAmount holds the string denoting the pay_amount field in the database.
FieldPayAmount = "pay_amount"
// FieldFeeRate holds the string denoting the fee_rate field in the database.
FieldFeeRate = "fee_rate"
// FieldRechargeCode holds the string denoting the recharge_code field in the database.
FieldRechargeCode = "recharge_code"
// FieldOutTradeNo holds the string denoting the out_trade_no field in the database.
FieldOutTradeNo = "out_trade_no"
// FieldPaymentType holds the string denoting the payment_type field in the database.
FieldPaymentType = "payment_type"
// FieldPaymentTradeNo holds the string denoting the payment_trade_no field in the database.
FieldPaymentTradeNo = "payment_trade_no"
// FieldPayURL holds the string denoting the pay_url field in the database.
FieldPayURL = "pay_url"
// FieldQrCode holds the string denoting the qr_code field in the database.
FieldQrCode = "qr_code"
// FieldQrCodeImg holds the string denoting the qr_code_img field in the database.
FieldQrCodeImg = "qr_code_img"
// FieldOrderType holds the string denoting the order_type field in the database.
FieldOrderType = "order_type"
// FieldPlanID holds the string denoting the plan_id field in the database.
FieldPlanID = "plan_id"
// FieldSubscriptionGroupID holds the string denoting the subscription_group_id field in the database.
FieldSubscriptionGroupID = "subscription_group_id"
// FieldSubscriptionDays holds the string denoting the subscription_days field in the database.
FieldSubscriptionDays = "subscription_days"
// FieldProviderInstanceID holds the string denoting the provider_instance_id field in the database.
FieldProviderInstanceID = "provider_instance_id"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldRefundAmount holds the string denoting the refund_amount field in the database.
FieldRefundAmount = "refund_amount"
// FieldRefundReason holds the string denoting the refund_reason field in the database.
FieldRefundReason = "refund_reason"
// FieldRefundAt holds the string denoting the refund_at field in the database.
FieldRefundAt = "refund_at"
// FieldForceRefund holds the string denoting the force_refund field in the database.
FieldForceRefund = "force_refund"
// FieldRefundRequestedAt holds the string denoting the refund_requested_at field in the database.
FieldRefundRequestedAt = "refund_requested_at"
// FieldRefundRequestReason holds the string denoting the refund_request_reason field in the database.
FieldRefundRequestReason = "refund_request_reason"
// FieldRefundRequestedBy holds the string denoting the refund_requested_by field in the database.
FieldRefundRequestedBy = "refund_requested_by"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// FieldPaidAt holds the string denoting the paid_at field in the database.
FieldPaidAt = "paid_at"
// FieldCompletedAt holds the string denoting the completed_at field in the database.
FieldCompletedAt = "completed_at"
// FieldFailedAt holds the string denoting the failed_at field in the database.
FieldFailedAt = "failed_at"
// FieldFailedReason holds the string denoting the failed_reason field in the database.
FieldFailedReason = "failed_reason"
// FieldClientIP holds the string denoting the client_ip field in the database.
FieldClientIP = "client_ip"
// FieldSrcHost holds the string denoting the src_host field in the database.
FieldSrcHost = "src_host"
// FieldSrcURL holds the string denoting the src_url field in the database.
FieldSrcURL = "src_url"
// 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"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// Table holds the table name of the paymentorder in the database.
Table = "payment_orders"
// UserTable is the table that holds the user relation/edge.
UserTable = "payment_orders"
// UserInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
UserInverseTable = "users"
// UserColumn is the table column denoting the user relation/edge.
UserColumn = "user_id"
)
// Columns holds all SQL columns for paymentorder fields.
var Columns = []string{
FieldID,
FieldUserID,
FieldUserEmail,
FieldUserName,
FieldUserNotes,
FieldAmount,
FieldPayAmount,
FieldFeeRate,
FieldRechargeCode,
FieldOutTradeNo,
FieldPaymentType,
FieldPaymentTradeNo,
FieldPayURL,
FieldQrCode,
FieldQrCodeImg,
FieldOrderType,
FieldPlanID,
FieldSubscriptionGroupID,
FieldSubscriptionDays,
FieldProviderInstanceID,
FieldStatus,
FieldRefundAmount,
FieldRefundReason,
FieldRefundAt,
FieldForceRefund,
FieldRefundRequestedAt,
FieldRefundRequestReason,
FieldRefundRequestedBy,
FieldExpiresAt,
FieldPaidAt,
FieldCompletedAt,
FieldFailedAt,
FieldFailedReason,
FieldClientIP,
FieldSrcHost,
FieldSrcURL,
FieldCreatedAt,
FieldUpdatedAt,
}
// 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 (
// UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save.
UserEmailValidator func(string) error
// UserNameValidator is a validator for the "user_name" field. It is called by the builders before save.
UserNameValidator func(string) error
// DefaultFeeRate holds the default value on creation for the "fee_rate" field.
DefaultFeeRate float64
// RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save.
RechargeCodeValidator func(string) error
// DefaultOutTradeNo holds the default value on creation for the "out_trade_no" field.
DefaultOutTradeNo string
// OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save.
OutTradeNoValidator func(string) error
// PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save.
PaymentTypeValidator func(string) error
// PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save.
PaymentTradeNoValidator func(string) error
// DefaultOrderType holds the default value on creation for the "order_type" field.
DefaultOrderType string
// OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save.
OrderTypeValidator func(string) error
// ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save.
ProviderInstanceIDValidator 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
// DefaultRefundAmount holds the default value on creation for the "refund_amount" field.
DefaultRefundAmount float64
// DefaultForceRefund holds the default value on creation for the "force_refund" field.
DefaultForceRefund bool
// RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save.
RefundRequestedByValidator func(string) error
// ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save.
ClientIPValidator func(string) error
// SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save.
SrcHostValidator 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 PaymentOrder 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()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByUserEmail orders the results by the user_email field.
func ByUserEmail(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserEmail, opts...).ToFunc()
}
// ByUserName orders the results by the user_name field.
func ByUserName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserName, opts...).ToFunc()
}
// ByUserNotes orders the results by the user_notes field.
func ByUserNotes(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserNotes, opts...).ToFunc()
}
// ByAmount orders the results by the amount field.
func ByAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAmount, opts...).ToFunc()
}
// ByPayAmount orders the results by the pay_amount field.
func ByPayAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPayAmount, opts...).ToFunc()
}
// ByFeeRate orders the results by the fee_rate field.
func ByFeeRate(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFeeRate, opts...).ToFunc()
}
// ByRechargeCode orders the results by the recharge_code field.
func ByRechargeCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRechargeCode, opts...).ToFunc()
}
// ByOutTradeNo orders the results by the out_trade_no field.
func ByOutTradeNo(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOutTradeNo, opts...).ToFunc()
}
// ByPaymentType orders the results by the payment_type field.
func ByPaymentType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentType, opts...).ToFunc()
}
// ByPaymentTradeNo orders the results by the payment_trade_no field.
func ByPaymentTradeNo(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentTradeNo, opts...).ToFunc()
}
// ByPayURL orders the results by the pay_url field.
func ByPayURL(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPayURL, opts...).ToFunc()
}
// ByQrCode orders the results by the qr_code field.
func ByQrCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQrCode, opts...).ToFunc()
}
// ByQrCodeImg orders the results by the qr_code_img field.
func ByQrCodeImg(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQrCodeImg, opts...).ToFunc()
}
// ByOrderType orders the results by the order_type field.
func ByOrderType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOrderType, opts...).ToFunc()
}
// ByPlanID orders the results by the plan_id field.
func ByPlanID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPlanID, opts...).ToFunc()
}
// BySubscriptionGroupID orders the results by the subscription_group_id field.
func BySubscriptionGroupID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubscriptionGroupID, opts...).ToFunc()
}
// BySubscriptionDays orders the results by the subscription_days field.
func BySubscriptionDays(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubscriptionDays, opts...).ToFunc()
}
// ByProviderInstanceID orders the results by the provider_instance_id field.
func ByProviderInstanceID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderInstanceID, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByRefundAmount orders the results by the refund_amount field.
func ByRefundAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundAmount, opts...).ToFunc()
}
// ByRefundReason orders the results by the refund_reason field.
func ByRefundReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundReason, opts...).ToFunc()
}
// ByRefundAt orders the results by the refund_at field.
func ByRefundAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundAt, opts...).ToFunc()
}
// ByForceRefund orders the results by the force_refund field.
func ByForceRefund(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldForceRefund, opts...).ToFunc()
}
// ByRefundRequestedAt orders the results by the refund_requested_at field.
func ByRefundRequestedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestedAt, opts...).ToFunc()
}
// ByRefundRequestReason orders the results by the refund_request_reason field.
func ByRefundRequestReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestReason, opts...).ToFunc()
}
// ByRefundRequestedBy orders the results by the refund_requested_by field.
func ByRefundRequestedBy(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestedBy, opts...).ToFunc()
}
// ByExpiresAt orders the results by the expires_at field.
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}
// ByPaidAt orders the results by the paid_at field.
func ByPaidAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaidAt, opts...).ToFunc()
}
// ByCompletedAt orders the results by the completed_at field.
func ByCompletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCompletedAt, opts...).ToFunc()
}
// ByFailedAt orders the results by the failed_at field.
func ByFailedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailedAt, opts...).ToFunc()
}
// ByFailedReason orders the results by the failed_reason field.
func ByFailedReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailedReason, opts...).ToFunc()
}
// ByClientIP orders the results by the client_ip field.
func ByClientIP(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldClientIP, opts...).ToFunc()
}
// BySrcHost orders the results by the src_host field.
func BySrcHost(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSrcHost, opts...).ToFunc()
}
// BySrcURL orders the results by the src_url field.
func BySrcURL(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSrcURL, 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()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentOrderDelete is the builder for deleting a PaymentOrder entity.
type PaymentOrderDelete struct {
config
hooks []Hook
mutation *PaymentOrderMutation
}
// Where appends a list predicates to the PaymentOrderDelete builder.
func (_d *PaymentOrderDelete) Where(ps ...predicate.PaymentOrder) *PaymentOrderDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentOrderDelete) 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 *PaymentOrderDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentOrderDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentorder.Table, sqlgraph.NewFieldSpec(paymentorder.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
}
// PaymentOrderDeleteOne is the builder for deleting a single PaymentOrder entity.
type PaymentOrderDeleteOne struct {
_d *PaymentOrderDelete
}
// Where appends a list predicates to the PaymentOrderDelete builder.
func (_d *PaymentOrderDeleteOne) Where(ps ...predicate.PaymentOrder) *PaymentOrderDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentOrderDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentorder.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentOrderDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,643 @@
// 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/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// PaymentOrderQuery is the builder for querying PaymentOrder entities.
type PaymentOrderQuery struct {
config
ctx *QueryContext
order []paymentorder.OrderOption
inters []Interceptor
predicates []predicate.PaymentOrder
withUser *UserQuery
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 PaymentOrderQuery builder.
func (_q *PaymentOrderQuery) Where(ps ...predicate.PaymentOrder) *PaymentOrderQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentOrderQuery) Limit(limit int) *PaymentOrderQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentOrderQuery) Offset(offset int) *PaymentOrderQuery {
_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 *PaymentOrderQuery) Unique(unique bool) *PaymentOrderQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentOrderQuery) Order(o ...paymentorder.OrderOption) *PaymentOrderQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryUser chains the current query on the "user" edge.
func (_q *PaymentOrderQuery) QueryUser() *UserQuery {
query := (&UserClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(paymentorder.Table, paymentorder.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, paymentorder.UserTable, paymentorder.UserColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first PaymentOrder entity from the query.
// Returns a *NotFoundError when no PaymentOrder was found.
func (_q *PaymentOrderQuery) First(ctx context.Context) (*PaymentOrder, 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{paymentorder.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentOrderQuery) FirstX(ctx context.Context) *PaymentOrder {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentOrder ID from the query.
// Returns a *NotFoundError when no PaymentOrder ID was found.
func (_q *PaymentOrderQuery) 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{paymentorder.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentOrderQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentOrder entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentOrder entity is found.
// Returns a *NotFoundError when no PaymentOrder entities are found.
func (_q *PaymentOrderQuery) Only(ctx context.Context) (*PaymentOrder, 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{paymentorder.Label}
default:
return nil, &NotSingularError{paymentorder.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentOrderQuery) OnlyX(ctx context.Context) *PaymentOrder {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentOrder ID in the query.
// Returns a *NotSingularError when more than one PaymentOrder ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentOrderQuery) 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{paymentorder.Label}
default:
err = &NotSingularError{paymentorder.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentOrderQuery) 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 PaymentOrders.
func (_q *PaymentOrderQuery) All(ctx context.Context) ([]*PaymentOrder, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentOrder, *PaymentOrderQuery]()
return withInterceptors[[]*PaymentOrder](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentOrderQuery) AllX(ctx context.Context) []*PaymentOrder {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentOrder IDs.
func (_q *PaymentOrderQuery) 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(paymentorder.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentOrderQuery) 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 *PaymentOrderQuery) 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[*PaymentOrderQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentOrderQuery) 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 *PaymentOrderQuery) 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 *PaymentOrderQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentOrderQuery 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 *PaymentOrderQuery) Clone() *PaymentOrderQuery {
if _q == nil {
return nil
}
return &PaymentOrderQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentorder.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentOrder{}, _q.predicates...),
withUser: _q.withUser.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithUser tells the query-builder to eager-load the nodes that are connected to
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *PaymentOrderQuery) WithUser(opts ...func(*UserQuery)) *PaymentOrderQuery {
query := (&UserClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withUser = query
return _q
}
// 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 {
// UserID int64 `json:"user_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentOrder.Query().
// GroupBy(paymentorder.FieldUserID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentOrderQuery) GroupBy(field string, fields ...string) *PaymentOrderGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentOrderGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentorder.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 {
// UserID int64 `json:"user_id,omitempty"`
// }
//
// client.PaymentOrder.Query().
// Select(paymentorder.FieldUserID).
// Scan(ctx, &v)
func (_q *PaymentOrderQuery) Select(fields ...string) *PaymentOrderSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentOrderSelect{PaymentOrderQuery: _q}
sbuild.label = paymentorder.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentOrderSelect configured with the given aggregations.
func (_q *PaymentOrderQuery) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentOrderQuery) 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 !paymentorder.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 *PaymentOrderQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentOrder, error) {
var (
nodes = []*PaymentOrder{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withUser != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentOrder).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentOrder{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
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
}
if query := _q.withUser; query != nil {
if err := _q.loadUser(ctx, query, nodes, nil,
func(n *PaymentOrder, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *PaymentOrderQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*PaymentOrder, init func(*PaymentOrder), assign func(*PaymentOrder, *User)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*PaymentOrder)
for i := range nodes {
fk := nodes[i].UserID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *PaymentOrderQuery) 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 *PaymentOrderQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.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, paymentorder.FieldID)
for i := range fields {
if fields[i] != paymentorder.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withUser != nil {
_spec.Node.AddColumnOnce(paymentorder.FieldUserID)
}
}
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 *PaymentOrderQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentorder.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentorder.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 *PaymentOrderQuery) ForUpdate(opts ...sql.LockOption) *PaymentOrderQuery {
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 *PaymentOrderQuery) ForShare(opts ...sql.LockOption) *PaymentOrderQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentOrderGroupBy is the group-by builder for PaymentOrder entities.
type PaymentOrderGroupBy struct {
selector
build *PaymentOrderQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentOrderGroupBy) Aggregate(fns ...AggregateFunc) *PaymentOrderGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentOrderGroupBy) 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[*PaymentOrderQuery, *PaymentOrderGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentOrderGroupBy) sqlScan(ctx context.Context, root *PaymentOrderQuery, 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)
}
// PaymentOrderSelect is the builder for selecting fields of PaymentOrder entities.
type PaymentOrderSelect struct {
*PaymentOrderQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentOrderSelect) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentOrderSelect) 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[*PaymentOrderQuery, *PaymentOrderSelect](ctx, _s.PaymentOrderQuery, _s, _s.inters, v)
}
func (_s *PaymentOrderSelect) sqlScan(ctx context.Context, root *PaymentOrderQuery, 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

View File

@@ -0,0 +1,229 @@
// 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/paymentproviderinstance"
)
// PaymentProviderInstance is the model entity for the PaymentProviderInstance schema.
type PaymentProviderInstance struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// ProviderKey holds the value of the "provider_key" field.
ProviderKey string `json:"provider_key,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Config holds the value of the "config" field.
Config string `json:"config,omitempty"`
// SupportedTypes holds the value of the "supported_types" field.
SupportedTypes string `json:"supported_types,omitempty"`
// Enabled holds the value of the "enabled" field.
Enabled bool `json:"enabled,omitempty"`
// PaymentMode holds the value of the "payment_mode" field.
PaymentMode string `json:"payment_mode,omitempty"`
// SortOrder holds the value of the "sort_order" field.
SortOrder int `json:"sort_order,omitempty"`
// Limits holds the value of the "limits" field.
Limits string `json:"limits,omitempty"`
// RefundEnabled holds the value of the "refund_enabled" field.
RefundEnabled bool `json:"refund_enabled,omitempty"`
// AllowUserRefund holds the value of the "allow_user_refund" field.
AllowUserRefund bool `json:"allow_user_refund,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"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*PaymentProviderInstance) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentproviderinstance.FieldEnabled, paymentproviderinstance.FieldRefundEnabled, paymentproviderinstance.FieldAllowUserRefund:
values[i] = new(sql.NullBool)
case paymentproviderinstance.FieldID, paymentproviderinstance.FieldSortOrder:
values[i] = new(sql.NullInt64)
case paymentproviderinstance.FieldProviderKey, paymentproviderinstance.FieldName, paymentproviderinstance.FieldConfig, paymentproviderinstance.FieldSupportedTypes, paymentproviderinstance.FieldPaymentMode, paymentproviderinstance.FieldLimits:
values[i] = new(sql.NullString)
case paymentproviderinstance.FieldCreatedAt, paymentproviderinstance.FieldUpdatedAt:
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 PaymentProviderInstance fields.
func (_m *PaymentProviderInstance) 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 paymentproviderinstance.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 paymentproviderinstance.FieldProviderKey:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_key", values[i])
} else if value.Valid {
_m.ProviderKey = value.String
}
case paymentproviderinstance.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
case paymentproviderinstance.FieldConfig:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field config", values[i])
} else if value.Valid {
_m.Config = value.String
}
case paymentproviderinstance.FieldSupportedTypes:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field supported_types", values[i])
} else if value.Valid {
_m.SupportedTypes = value.String
}
case paymentproviderinstance.FieldEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field enabled", values[i])
} else if value.Valid {
_m.Enabled = value.Bool
}
case paymentproviderinstance.FieldPaymentMode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_mode", values[i])
} else if value.Valid {
_m.PaymentMode = value.String
}
case paymentproviderinstance.FieldSortOrder:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field sort_order", values[i])
} else if value.Valid {
_m.SortOrder = int(value.Int64)
}
case paymentproviderinstance.FieldLimits:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field limits", values[i])
} else if value.Valid {
_m.Limits = value.String
}
case paymentproviderinstance.FieldRefundEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field refund_enabled", values[i])
} else if value.Valid {
_m.RefundEnabled = value.Bool
}
case paymentproviderinstance.FieldAllowUserRefund:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field allow_user_refund", values[i])
} else if value.Valid {
_m.AllowUserRefund = value.Bool
}
case paymentproviderinstance.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 paymentproviderinstance.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
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the PaymentProviderInstance.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentProviderInstance) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this PaymentProviderInstance.
// Note that you need to call PaymentProviderInstance.Unwrap() before calling this method if this PaymentProviderInstance
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentProviderInstance) Update() *PaymentProviderInstanceUpdateOne {
return NewPaymentProviderInstanceClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentProviderInstance 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 *PaymentProviderInstance) Unwrap() *PaymentProviderInstance {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentProviderInstance is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentProviderInstance) String() string {
var builder strings.Builder
builder.WriteString("PaymentProviderInstance(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("provider_key=")
builder.WriteString(_m.ProviderKey)
builder.WriteString(", ")
builder.WriteString("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("config=")
builder.WriteString(_m.Config)
builder.WriteString(", ")
builder.WriteString("supported_types=")
builder.WriteString(_m.SupportedTypes)
builder.WriteString(", ")
builder.WriteString("enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.Enabled))
builder.WriteString(", ")
builder.WriteString("payment_mode=")
builder.WriteString(_m.PaymentMode)
builder.WriteString(", ")
builder.WriteString("sort_order=")
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
builder.WriteString(", ")
builder.WriteString("limits=")
builder.WriteString(_m.Limits)
builder.WriteString(", ")
builder.WriteString("refund_enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.RefundEnabled))
builder.WriteString(", ")
builder.WriteString("allow_user_refund=")
builder.WriteString(fmt.Sprintf("%v", _m.AllowUserRefund))
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.WriteByte(')')
return builder.String()
}
// PaymentProviderInstances is a parsable slice of PaymentProviderInstance.
type PaymentProviderInstances []*PaymentProviderInstance

View File

@@ -0,0 +1,170 @@
// Code generated by ent, DO NOT EDIT.
package paymentproviderinstance
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the paymentproviderinstance type in the database.
Label = "payment_provider_instance"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldProviderKey holds the string denoting the provider_key field in the database.
FieldProviderKey = "provider_key"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldConfig holds the string denoting the config field in the database.
FieldConfig = "config"
// FieldSupportedTypes holds the string denoting the supported_types field in the database.
FieldSupportedTypes = "supported_types"
// FieldEnabled holds the string denoting the enabled field in the database.
FieldEnabled = "enabled"
// FieldPaymentMode holds the string denoting the payment_mode field in the database.
FieldPaymentMode = "payment_mode"
// FieldSortOrder holds the string denoting the sort_order field in the database.
FieldSortOrder = "sort_order"
// FieldLimits holds the string denoting the limits field in the database.
FieldLimits = "limits"
// FieldRefundEnabled holds the string denoting the refund_enabled field in the database.
FieldRefundEnabled = "refund_enabled"
// FieldAllowUserRefund holds the string denoting the allow_user_refund field in the database.
FieldAllowUserRefund = "allow_user_refund"
// 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"
// Table holds the table name of the paymentproviderinstance in the database.
Table = "payment_provider_instances"
)
// Columns holds all SQL columns for paymentproviderinstance fields.
var Columns = []string{
FieldID,
FieldProviderKey,
FieldName,
FieldConfig,
FieldSupportedTypes,
FieldEnabled,
FieldPaymentMode,
FieldSortOrder,
FieldLimits,
FieldRefundEnabled,
FieldAllowUserRefund,
FieldCreatedAt,
FieldUpdatedAt,
}
// 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 (
// ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
ProviderKeyValidator func(string) error
// DefaultName holds the default value on creation for the "name" field.
DefaultName string
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultSupportedTypes holds the default value on creation for the "supported_types" field.
DefaultSupportedTypes string
// SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save.
SupportedTypesValidator func(string) error
// DefaultEnabled holds the default value on creation for the "enabled" field.
DefaultEnabled bool
// DefaultPaymentMode holds the default value on creation for the "payment_mode" field.
DefaultPaymentMode string
// PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save.
PaymentModeValidator func(string) error
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
DefaultSortOrder int
// DefaultLimits holds the default value on creation for the "limits" field.
DefaultLimits string
// DefaultRefundEnabled holds the default value on creation for the "refund_enabled" field.
DefaultRefundEnabled bool
// DefaultAllowUserRefund holds the default value on creation for the "allow_user_refund" field.
DefaultAllowUserRefund bool
// 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 PaymentProviderInstance 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()
}
// ByProviderKey orders the results by the provider_key field.
func ByProviderKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderKey, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByConfig orders the results by the config field.
func ByConfig(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConfig, opts...).ToFunc()
}
// BySupportedTypes orders the results by the supported_types field.
func BySupportedTypes(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSupportedTypes, opts...).ToFunc()
}
// ByEnabled orders the results by the enabled field.
func ByEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEnabled, opts...).ToFunc()
}
// ByPaymentMode orders the results by the payment_mode field.
func ByPaymentMode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentMode, opts...).ToFunc()
}
// BySortOrder orders the results by the sort_order field.
func BySortOrder(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
}
// ByLimits orders the results by the limits field.
func ByLimits(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLimits, opts...).ToFunc()
}
// ByRefundEnabled orders the results by the refund_enabled field.
func ByRefundEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundEnabled, opts...).ToFunc()
}
// ByAllowUserRefund orders the results by the allow_user_refund field.
func ByAllowUserRefund(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAllowUserRefund, 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()
}

View File

@@ -0,0 +1,670 @@
// Code generated by ent, DO NOT EDIT.
package paymentproviderinstance
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.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldID, id))
}
// ProviderKey applies equality check predicate on the "provider_key" field. It's identical to ProviderKeyEQ.
func ProviderKey(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v))
}
// Config applies equality check predicate on the "config" field. It's identical to ConfigEQ.
func Config(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v))
}
// SupportedTypes applies equality check predicate on the "supported_types" field. It's identical to SupportedTypesEQ.
func SupportedTypes(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v))
}
// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ.
func Enabled(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v))
}
// PaymentMode applies equality check predicate on the "payment_mode" field. It's identical to PaymentModeEQ.
func PaymentMode(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v))
}
// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ.
func SortOrder(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v))
}
// Limits applies equality check predicate on the "limits" field. It's identical to LimitsEQ.
func Limits(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v))
}
// RefundEnabled applies equality check predicate on the "refund_enabled" field. It's identical to RefundEnabledEQ.
func RefundEnabled(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v))
}
// AllowUserRefund applies equality check predicate on the "allow_user_refund" field. It's identical to AllowUserRefundEQ.
func AllowUserRefund(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldAllowUserRefund, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v))
}
// ProviderKeyEQ applies the EQ predicate on the "provider_key" field.
func ProviderKeyEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v))
}
// ProviderKeyNEQ applies the NEQ predicate on the "provider_key" field.
func ProviderKeyNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldProviderKey, v))
}
// ProviderKeyIn applies the In predicate on the "provider_key" field.
func ProviderKeyIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldProviderKey, vs...))
}
// ProviderKeyNotIn applies the NotIn predicate on the "provider_key" field.
func ProviderKeyNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldProviderKey, vs...))
}
// ProviderKeyGT applies the GT predicate on the "provider_key" field.
func ProviderKeyGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldProviderKey, v))
}
// ProviderKeyGTE applies the GTE predicate on the "provider_key" field.
func ProviderKeyGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldProviderKey, v))
}
// ProviderKeyLT applies the LT predicate on the "provider_key" field.
func ProviderKeyLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldProviderKey, v))
}
// ProviderKeyLTE applies the LTE predicate on the "provider_key" field.
func ProviderKeyLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldProviderKey, v))
}
// ProviderKeyContains applies the Contains predicate on the "provider_key" field.
func ProviderKeyContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldProviderKey, v))
}
// ProviderKeyHasPrefix applies the HasPrefix predicate on the "provider_key" field.
func ProviderKeyHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldProviderKey, v))
}
// ProviderKeyHasSuffix applies the HasSuffix predicate on the "provider_key" field.
func ProviderKeyHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldProviderKey, v))
}
// ProviderKeyEqualFold applies the EqualFold predicate on the "provider_key" field.
func ProviderKeyEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldProviderKey, v))
}
// ProviderKeyContainsFold applies the ContainsFold predicate on the "provider_key" field.
func ProviderKeyContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldProviderKey, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldName, v))
}
// ConfigEQ applies the EQ predicate on the "config" field.
func ConfigEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v))
}
// ConfigNEQ applies the NEQ predicate on the "config" field.
func ConfigNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldConfig, v))
}
// ConfigIn applies the In predicate on the "config" field.
func ConfigIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldConfig, vs...))
}
// ConfigNotIn applies the NotIn predicate on the "config" field.
func ConfigNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldConfig, vs...))
}
// ConfigGT applies the GT predicate on the "config" field.
func ConfigGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldConfig, v))
}
// ConfigGTE applies the GTE predicate on the "config" field.
func ConfigGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldConfig, v))
}
// ConfigLT applies the LT predicate on the "config" field.
func ConfigLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldConfig, v))
}
// ConfigLTE applies the LTE predicate on the "config" field.
func ConfigLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldConfig, v))
}
// ConfigContains applies the Contains predicate on the "config" field.
func ConfigContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldConfig, v))
}
// ConfigHasPrefix applies the HasPrefix predicate on the "config" field.
func ConfigHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldConfig, v))
}
// ConfigHasSuffix applies the HasSuffix predicate on the "config" field.
func ConfigHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldConfig, v))
}
// ConfigEqualFold applies the EqualFold predicate on the "config" field.
func ConfigEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldConfig, v))
}
// ConfigContainsFold applies the ContainsFold predicate on the "config" field.
func ConfigContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldConfig, v))
}
// SupportedTypesEQ applies the EQ predicate on the "supported_types" field.
func SupportedTypesEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v))
}
// SupportedTypesNEQ applies the NEQ predicate on the "supported_types" field.
func SupportedTypesNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSupportedTypes, v))
}
// SupportedTypesIn applies the In predicate on the "supported_types" field.
func SupportedTypesIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldSupportedTypes, vs...))
}
// SupportedTypesNotIn applies the NotIn predicate on the "supported_types" field.
func SupportedTypesNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSupportedTypes, vs...))
}
// SupportedTypesGT applies the GT predicate on the "supported_types" field.
func SupportedTypesGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldSupportedTypes, v))
}
// SupportedTypesGTE applies the GTE predicate on the "supported_types" field.
func SupportedTypesGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSupportedTypes, v))
}
// SupportedTypesLT applies the LT predicate on the "supported_types" field.
func SupportedTypesLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldSupportedTypes, v))
}
// SupportedTypesLTE applies the LTE predicate on the "supported_types" field.
func SupportedTypesLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSupportedTypes, v))
}
// SupportedTypesContains applies the Contains predicate on the "supported_types" field.
func SupportedTypesContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldSupportedTypes, v))
}
// SupportedTypesHasPrefix applies the HasPrefix predicate on the "supported_types" field.
func SupportedTypesHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldSupportedTypes, v))
}
// SupportedTypesHasSuffix applies the HasSuffix predicate on the "supported_types" field.
func SupportedTypesHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldSupportedTypes, v))
}
// SupportedTypesEqualFold applies the EqualFold predicate on the "supported_types" field.
func SupportedTypesEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldSupportedTypes, v))
}
// SupportedTypesContainsFold applies the ContainsFold predicate on the "supported_types" field.
func SupportedTypesContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldSupportedTypes, v))
}
// EnabledEQ applies the EQ predicate on the "enabled" field.
func EnabledEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v))
}
// EnabledNEQ applies the NEQ predicate on the "enabled" field.
func EnabledNEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldEnabled, v))
}
// PaymentModeEQ applies the EQ predicate on the "payment_mode" field.
func PaymentModeEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v))
}
// PaymentModeNEQ applies the NEQ predicate on the "payment_mode" field.
func PaymentModeNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldPaymentMode, v))
}
// PaymentModeIn applies the In predicate on the "payment_mode" field.
func PaymentModeIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldPaymentMode, vs...))
}
// PaymentModeNotIn applies the NotIn predicate on the "payment_mode" field.
func PaymentModeNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldPaymentMode, vs...))
}
// PaymentModeGT applies the GT predicate on the "payment_mode" field.
func PaymentModeGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldPaymentMode, v))
}
// PaymentModeGTE applies the GTE predicate on the "payment_mode" field.
func PaymentModeGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldPaymentMode, v))
}
// PaymentModeLT applies the LT predicate on the "payment_mode" field.
func PaymentModeLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldPaymentMode, v))
}
// PaymentModeLTE applies the LTE predicate on the "payment_mode" field.
func PaymentModeLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldPaymentMode, v))
}
// PaymentModeContains applies the Contains predicate on the "payment_mode" field.
func PaymentModeContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldPaymentMode, v))
}
// PaymentModeHasPrefix applies the HasPrefix predicate on the "payment_mode" field.
func PaymentModeHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldPaymentMode, v))
}
// PaymentModeHasSuffix applies the HasSuffix predicate on the "payment_mode" field.
func PaymentModeHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldPaymentMode, v))
}
// PaymentModeEqualFold applies the EqualFold predicate on the "payment_mode" field.
func PaymentModeEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldPaymentMode, v))
}
// PaymentModeContainsFold applies the ContainsFold predicate on the "payment_mode" field.
func PaymentModeContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldPaymentMode, v))
}
// SortOrderEQ applies the EQ predicate on the "sort_order" field.
func SortOrderEQ(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v))
}
// SortOrderNEQ applies the NEQ predicate on the "sort_order" field.
func SortOrderNEQ(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSortOrder, v))
}
// SortOrderIn applies the In predicate on the "sort_order" field.
func SortOrderIn(vs ...int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldSortOrder, vs...))
}
// SortOrderNotIn applies the NotIn predicate on the "sort_order" field.
func SortOrderNotIn(vs ...int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSortOrder, vs...))
}
// SortOrderGT applies the GT predicate on the "sort_order" field.
func SortOrderGT(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldSortOrder, v))
}
// SortOrderGTE applies the GTE predicate on the "sort_order" field.
func SortOrderGTE(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSortOrder, v))
}
// SortOrderLT applies the LT predicate on the "sort_order" field.
func SortOrderLT(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldSortOrder, v))
}
// SortOrderLTE applies the LTE predicate on the "sort_order" field.
func SortOrderLTE(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSortOrder, v))
}
// LimitsEQ applies the EQ predicate on the "limits" field.
func LimitsEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v))
}
// LimitsNEQ applies the NEQ predicate on the "limits" field.
func LimitsNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldLimits, v))
}
// LimitsIn applies the In predicate on the "limits" field.
func LimitsIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldLimits, vs...))
}
// LimitsNotIn applies the NotIn predicate on the "limits" field.
func LimitsNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldLimits, vs...))
}
// LimitsGT applies the GT predicate on the "limits" field.
func LimitsGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldLimits, v))
}
// LimitsGTE applies the GTE predicate on the "limits" field.
func LimitsGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldLimits, v))
}
// LimitsLT applies the LT predicate on the "limits" field.
func LimitsLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldLimits, v))
}
// LimitsLTE applies the LTE predicate on the "limits" field.
func LimitsLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldLimits, v))
}
// LimitsContains applies the Contains predicate on the "limits" field.
func LimitsContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldLimits, v))
}
// LimitsHasPrefix applies the HasPrefix predicate on the "limits" field.
func LimitsHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldLimits, v))
}
// LimitsHasSuffix applies the HasSuffix predicate on the "limits" field.
func LimitsHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldLimits, v))
}
// LimitsEqualFold applies the EqualFold predicate on the "limits" field.
func LimitsEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldLimits, v))
}
// LimitsContainsFold applies the ContainsFold predicate on the "limits" field.
func LimitsContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldLimits, v))
}
// RefundEnabledEQ applies the EQ predicate on the "refund_enabled" field.
func RefundEnabledEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v))
}
// RefundEnabledNEQ applies the NEQ predicate on the "refund_enabled" field.
func RefundEnabledNEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldRefundEnabled, v))
}
// AllowUserRefundEQ applies the EQ predicate on the "allow_user_refund" field.
func AllowUserRefundEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldAllowUserRefund, v))
}
// AllowUserRefundNEQ applies the NEQ predicate on the "allow_user_refund" field.
func AllowUserRefundNEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldAllowUserRefund, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldUpdatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.NotPredicates(p))
}

File diff suppressed because it is too large Load Diff

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/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceDelete is the builder for deleting a PaymentProviderInstance entity.
type PaymentProviderInstanceDelete struct {
config
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// Where appends a list predicates to the PaymentProviderInstanceDelete builder.
func (_d *PaymentProviderInstanceDelete) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentProviderInstanceDelete) 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 *PaymentProviderInstanceDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentProviderInstanceDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentproviderinstance.Table, sqlgraph.NewFieldSpec(paymentproviderinstance.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
}
// PaymentProviderInstanceDeleteOne is the builder for deleting a single PaymentProviderInstance entity.
type PaymentProviderInstanceDeleteOne struct {
_d *PaymentProviderInstanceDelete
}
// Where appends a list predicates to the PaymentProviderInstanceDelete builder.
func (_d *PaymentProviderInstanceDeleteOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentProviderInstanceDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentproviderinstance.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentProviderInstanceDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

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/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceQuery is the builder for querying PaymentProviderInstance entities.
type PaymentProviderInstanceQuery struct {
config
ctx *QueryContext
order []paymentproviderinstance.OrderOption
inters []Interceptor
predicates []predicate.PaymentProviderInstance
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 PaymentProviderInstanceQuery builder.
func (_q *PaymentProviderInstanceQuery) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentProviderInstanceQuery) Limit(limit int) *PaymentProviderInstanceQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentProviderInstanceQuery) Offset(offset int) *PaymentProviderInstanceQuery {
_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 *PaymentProviderInstanceQuery) Unique(unique bool) *PaymentProviderInstanceQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentProviderInstanceQuery) Order(o ...paymentproviderinstance.OrderOption) *PaymentProviderInstanceQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first PaymentProviderInstance entity from the query.
// Returns a *NotFoundError when no PaymentProviderInstance was found.
func (_q *PaymentProviderInstanceQuery) First(ctx context.Context) (*PaymentProviderInstance, 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{paymentproviderinstance.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) FirstX(ctx context.Context) *PaymentProviderInstance {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentProviderInstance ID from the query.
// Returns a *NotFoundError when no PaymentProviderInstance ID was found.
func (_q *PaymentProviderInstanceQuery) 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{paymentproviderinstance.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentProviderInstance entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentProviderInstance entity is found.
// Returns a *NotFoundError when no PaymentProviderInstance entities are found.
func (_q *PaymentProviderInstanceQuery) Only(ctx context.Context) (*PaymentProviderInstance, 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{paymentproviderinstance.Label}
default:
return nil, &NotSingularError{paymentproviderinstance.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) OnlyX(ctx context.Context) *PaymentProviderInstance {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentProviderInstance ID in the query.
// Returns a *NotSingularError when more than one PaymentProviderInstance ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentProviderInstanceQuery) 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{paymentproviderinstance.Label}
default:
err = &NotSingularError{paymentproviderinstance.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) 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 PaymentProviderInstances.
func (_q *PaymentProviderInstanceQuery) All(ctx context.Context) ([]*PaymentProviderInstance, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentProviderInstance, *PaymentProviderInstanceQuery]()
return withInterceptors[[]*PaymentProviderInstance](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) AllX(ctx context.Context) []*PaymentProviderInstance {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentProviderInstance IDs.
func (_q *PaymentProviderInstanceQuery) 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(paymentproviderinstance.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) 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[*PaymentProviderInstanceQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentProviderInstanceQuery 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 *PaymentProviderInstanceQuery) Clone() *PaymentProviderInstanceQuery {
if _q == nil {
return nil
}
return &PaymentProviderInstanceQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentproviderinstance.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentProviderInstance{}, _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 {
// ProviderKey string `json:"provider_key,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentProviderInstance.Query().
// GroupBy(paymentproviderinstance.FieldProviderKey).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentProviderInstanceQuery) GroupBy(field string, fields ...string) *PaymentProviderInstanceGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentProviderInstanceGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentproviderinstance.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 {
// ProviderKey string `json:"provider_key,omitempty"`
// }
//
// client.PaymentProviderInstance.Query().
// Select(paymentproviderinstance.FieldProviderKey).
// Scan(ctx, &v)
func (_q *PaymentProviderInstanceQuery) Select(fields ...string) *PaymentProviderInstanceSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentProviderInstanceSelect{PaymentProviderInstanceQuery: _q}
sbuild.label = paymentproviderinstance.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentProviderInstanceSelect configured with the given aggregations.
func (_q *PaymentProviderInstanceQuery) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentProviderInstanceQuery) 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 !paymentproviderinstance.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 *PaymentProviderInstanceQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentProviderInstance, error) {
var (
nodes = []*PaymentProviderInstance{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentProviderInstance).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentProviderInstance{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 *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.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, paymentproviderinstance.FieldID)
for i := range fields {
if fields[i] != paymentproviderinstance.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 *PaymentProviderInstanceQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentproviderinstance.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentproviderinstance.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 *PaymentProviderInstanceQuery) ForUpdate(opts ...sql.LockOption) *PaymentProviderInstanceQuery {
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 *PaymentProviderInstanceQuery) ForShare(opts ...sql.LockOption) *PaymentProviderInstanceQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentProviderInstanceGroupBy is the group-by builder for PaymentProviderInstance entities.
type PaymentProviderInstanceGroupBy struct {
selector
build *PaymentProviderInstanceQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentProviderInstanceGroupBy) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentProviderInstanceGroupBy) 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[*PaymentProviderInstanceQuery, *PaymentProviderInstanceGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentProviderInstanceGroupBy) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, 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)
}
// PaymentProviderInstanceSelect is the builder for selecting fields of PaymentProviderInstance entities.
type PaymentProviderInstanceSelect struct {
*PaymentProviderInstanceQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentProviderInstanceSelect) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentProviderInstanceSelect) 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[*PaymentProviderInstanceQuery, *PaymentProviderInstanceSelect](ctx, _s.PaymentProviderInstanceQuery, _s, _s.inters, v)
}
func (_s *PaymentProviderInstanceSelect) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, 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)
}

View File

@@ -0,0 +1,628 @@
// 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/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceUpdate is the builder for updating PaymentProviderInstance entities.
type PaymentProviderInstanceUpdate struct {
config
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// Where appends a list predicates to the PaymentProviderInstanceUpdate builder.
func (_u *PaymentProviderInstanceUpdate) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *PaymentProviderInstanceUpdate) SetProviderKey(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetName sets the "name" field.
func (_u *PaymentProviderInstanceUpdate) SetName(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableName(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetConfig sets the "config" field.
func (_u *PaymentProviderInstanceUpdate) SetConfig(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetConfig(v)
return _u
}
// SetNillableConfig sets the "config" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableConfig(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetConfig(*v)
}
return _u
}
// SetSupportedTypes sets the "supported_types" field.
func (_u *PaymentProviderInstanceUpdate) SetSupportedTypes(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetSupportedTypes(v)
return _u
}
// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetSupportedTypes(*v)
}
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *PaymentProviderInstanceUpdate) SetEnabled(v bool) *PaymentProviderInstanceUpdate {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetPaymentMode sets the "payment_mode" field.
func (_u *PaymentProviderInstanceUpdate) SetPaymentMode(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetPaymentMode(v)
return _u
}
// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetPaymentMode(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *PaymentProviderInstanceUpdate) SetSortOrder(v int) *PaymentProviderInstanceUpdate {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *PaymentProviderInstanceUpdate) AddSortOrder(v int) *PaymentProviderInstanceUpdate {
_u.mutation.AddSortOrder(v)
return _u
}
// SetLimits sets the "limits" field.
func (_u *PaymentProviderInstanceUpdate) SetLimits(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetLimits(v)
return _u
}
// SetNillableLimits sets the "limits" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableLimits(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetLimits(*v)
}
return _u
}
// SetRefundEnabled sets the "refund_enabled" field.
func (_u *PaymentProviderInstanceUpdate) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdate {
_u.mutation.SetRefundEnabled(v)
return _u
}
// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetRefundEnabled(*v)
}
return _u
}
// SetAllowUserRefund sets the "allow_user_refund" field.
func (_u *PaymentProviderInstanceUpdate) SetAllowUserRefund(v bool) *PaymentProviderInstanceUpdate {
_u.mutation.SetAllowUserRefund(v)
return _u
}
// SetNillableAllowUserRefund sets the "allow_user_refund" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableAllowUserRefund(v *bool) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetAllowUserRefund(*v)
}
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *PaymentProviderInstanceUpdate) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the PaymentProviderInstanceMutation object of the builder.
func (_u *PaymentProviderInstanceUpdate) Mutation() *PaymentProviderInstanceMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *PaymentProviderInstanceUpdate) Save(ctx context.Context) (int, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *PaymentProviderInstanceUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *PaymentProviderInstanceUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := paymentproviderinstance.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentProviderInstanceUpdate) check() error {
if v, ok := _u.mutation.ProviderKey(); ok {
if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Name(); ok {
if err := paymentproviderinstance.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)}
}
}
if v, ok := _u.mutation.SupportedTypes(); ok {
if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil {
return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)}
}
}
if v, ok := _u.mutation.PaymentMode(); ok {
if err := paymentproviderinstance.PaymentModeValidator(v); err != nil {
return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)}
}
}
return nil
}
func (_u *PaymentProviderInstanceUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.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.ProviderKey(); ok {
_spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Config(); ok {
_spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value)
}
if value, ok := _u.mutation.SupportedTypes(); ok {
_spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PaymentMode(); ok {
_spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.Limits(); ok {
_spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value)
}
if value, ok := _u.mutation.RefundEnabled(); ok {
_spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.AllowUserRefund(); ok {
_spec.SetField(paymentproviderinstance.FieldAllowUserRefund, field.TypeBool, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentproviderinstance.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// PaymentProviderInstanceUpdateOne is the builder for updating a single PaymentProviderInstance entity.
type PaymentProviderInstanceUpdateOne struct {
config
fields []string
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// SetProviderKey sets the "provider_key" field.
func (_u *PaymentProviderInstanceUpdateOne) SetProviderKey(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetName sets the "name" field.
func (_u *PaymentProviderInstanceUpdateOne) SetName(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableName(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetConfig sets the "config" field.
func (_u *PaymentProviderInstanceUpdateOne) SetConfig(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetConfig(v)
return _u
}
// SetNillableConfig sets the "config" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableConfig(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetConfig(*v)
}
return _u
}
// SetSupportedTypes sets the "supported_types" field.
func (_u *PaymentProviderInstanceUpdateOne) SetSupportedTypes(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetSupportedTypes(v)
return _u
}
// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetSupportedTypes(*v)
}
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *PaymentProviderInstanceUpdateOne) SetEnabled(v bool) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetPaymentMode sets the "payment_mode" field.
func (_u *PaymentProviderInstanceUpdateOne) SetPaymentMode(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetPaymentMode(v)
return _u
}
// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetPaymentMode(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *PaymentProviderInstanceUpdateOne) SetSortOrder(v int) *PaymentProviderInstanceUpdateOne {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *PaymentProviderInstanceUpdateOne) AddSortOrder(v int) *PaymentProviderInstanceUpdateOne {
_u.mutation.AddSortOrder(v)
return _u
}
// SetLimits sets the "limits" field.
func (_u *PaymentProviderInstanceUpdateOne) SetLimits(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetLimits(v)
return _u
}
// SetNillableLimits sets the "limits" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableLimits(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetLimits(*v)
}
return _u
}
// SetRefundEnabled sets the "refund_enabled" field.
func (_u *PaymentProviderInstanceUpdateOne) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetRefundEnabled(v)
return _u
}
// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetRefundEnabled(*v)
}
return _u
}
// SetAllowUserRefund sets the "allow_user_refund" field.
func (_u *PaymentProviderInstanceUpdateOne) SetAllowUserRefund(v bool) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetAllowUserRefund(v)
return _u
}
// SetNillableAllowUserRefund sets the "allow_user_refund" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableAllowUserRefund(v *bool) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetAllowUserRefund(*v)
}
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *PaymentProviderInstanceUpdateOne) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the PaymentProviderInstanceMutation object of the builder.
func (_u *PaymentProviderInstanceUpdateOne) Mutation() *PaymentProviderInstanceMutation {
return _u.mutation
}
// Where appends a list predicates to the PaymentProviderInstanceUpdate builder.
func (_u *PaymentProviderInstanceUpdateOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdateOne {
_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 *PaymentProviderInstanceUpdateOne) Select(field string, fields ...string) *PaymentProviderInstanceUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated PaymentProviderInstance entity.
func (_u *PaymentProviderInstanceUpdateOne) Save(ctx context.Context) (*PaymentProviderInstance, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdateOne) SaveX(ctx context.Context) *PaymentProviderInstance {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *PaymentProviderInstanceUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *PaymentProviderInstanceUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := paymentproviderinstance.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentProviderInstanceUpdateOne) check() error {
if v, ok := _u.mutation.ProviderKey(); ok {
if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Name(); ok {
if err := paymentproviderinstance.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)}
}
}
if v, ok := _u.mutation.SupportedTypes(); ok {
if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil {
return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)}
}
}
if v, ok := _u.mutation.PaymentMode(); ok {
if err := paymentproviderinstance.PaymentModeValidator(v); err != nil {
return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)}
}
}
return nil
}
func (_u *PaymentProviderInstanceUpdateOne) sqlSave(ctx context.Context) (_node *PaymentProviderInstance, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentProviderInstance.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, paymentproviderinstance.FieldID)
for _, f := range fields {
if !paymentproviderinstance.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != paymentproviderinstance.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.ProviderKey(); ok {
_spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Config(); ok {
_spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value)
}
if value, ok := _u.mutation.SupportedTypes(); ok {
_spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PaymentMode(); ok {
_spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.Limits(); ok {
_spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value)
}
if value, ok := _u.mutation.RefundEnabled(); ok {
_spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.AllowUserRefund(); ok {
_spec.SetField(paymentproviderinstance.FieldAllowUserRefund, field.TypeBool, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value)
}
_node = &PaymentProviderInstance{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{paymentproviderinstance.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@@ -27,6 +27,18 @@ type ErrorPassthroughRule func(*sql.Selector)
// Group is the predicate function for group builders.
type Group func(*sql.Selector)
// IdempotencyRecord is the predicate function for idempotencyrecord builders.
type IdempotencyRecord func(*sql.Selector)
// PaymentAuditLog is the predicate function for paymentauditlog builders.
type PaymentAuditLog func(*sql.Selector)
// PaymentOrder is the predicate function for paymentorder builders.
type PaymentOrder func(*sql.Selector)
// PaymentProviderInstance is the predicate function for paymentproviderinstance builders.
type PaymentProviderInstance func(*sql.Selector)
// PromoCode is the predicate function for promocode builders.
type PromoCode func(*sql.Selector)
@@ -39,9 +51,18 @@ type Proxy func(*sql.Selector)
// RedeemCode is the predicate function for redeemcode builders.
type RedeemCode func(*sql.Selector)
// SecuritySecret is the predicate function for securitysecret builders.
type SecuritySecret func(*sql.Selector)
// Setting is the predicate function for setting builders.
type Setting func(*sql.Selector)
// SubscriptionPlan is the predicate function for subscriptionplan builders.
type SubscriptionPlan func(*sql.Selector)
// TLSFingerprintProfile is the predicate function for tlsfingerprintprofile builders.
type TLSFingerprintProfile func(*sql.Selector)
// UsageCleanupTask is the predicate function for usagecleanuptask builders.
type UsageCleanupTask func(*sql.Selector)

View File

@@ -12,12 +12,19 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/schema"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -25,6 +32,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/userattributedefinition"
"github.com/Wei-Shaw/sub2api/ent/userattributevalue"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// The init function reads all schema descriptors with runtime code
@@ -93,13 +101,37 @@ func init() {
// apikey.StatusValidator is a validator for the "status" field. It is called by the builders before save.
apikey.StatusValidator = apikeyDescStatus.Validators[0].(func(string) error)
// apikeyDescQuota is the schema descriptor for quota field.
apikeyDescQuota := apikeyFields[7].Descriptor()
apikeyDescQuota := apikeyFields[8].Descriptor()
// apikey.DefaultQuota holds the default value on creation for the quota field.
apikey.DefaultQuota = apikeyDescQuota.Default.(float64)
// apikeyDescQuotaUsed is the schema descriptor for quota_used field.
apikeyDescQuotaUsed := apikeyFields[8].Descriptor()
apikeyDescQuotaUsed := apikeyFields[9].Descriptor()
// apikey.DefaultQuotaUsed holds the default value on creation for the quota_used field.
apikey.DefaultQuotaUsed = apikeyDescQuotaUsed.Default.(float64)
// apikeyDescRateLimit5h is the schema descriptor for rate_limit_5h field.
apikeyDescRateLimit5h := apikeyFields[11].Descriptor()
// apikey.DefaultRateLimit5h holds the default value on creation for the rate_limit_5h field.
apikey.DefaultRateLimit5h = apikeyDescRateLimit5h.Default.(float64)
// apikeyDescRateLimit1d is the schema descriptor for rate_limit_1d field.
apikeyDescRateLimit1d := apikeyFields[12].Descriptor()
// apikey.DefaultRateLimit1d holds the default value on creation for the rate_limit_1d field.
apikey.DefaultRateLimit1d = apikeyDescRateLimit1d.Default.(float64)
// apikeyDescRateLimit7d is the schema descriptor for rate_limit_7d field.
apikeyDescRateLimit7d := apikeyFields[13].Descriptor()
// apikey.DefaultRateLimit7d holds the default value on creation for the rate_limit_7d field.
apikey.DefaultRateLimit7d = apikeyDescRateLimit7d.Default.(float64)
// apikeyDescUsage5h is the schema descriptor for usage_5h field.
apikeyDescUsage5h := apikeyFields[14].Descriptor()
// apikey.DefaultUsage5h holds the default value on creation for the usage_5h field.
apikey.DefaultUsage5h = apikeyDescUsage5h.Default.(float64)
// apikeyDescUsage1d is the schema descriptor for usage_1d field.
apikeyDescUsage1d := apikeyFields[15].Descriptor()
// apikey.DefaultUsage1d holds the default value on creation for the usage_1d field.
apikey.DefaultUsage1d = apikeyDescUsage1d.Default.(float64)
// apikeyDescUsage7d is the schema descriptor for usage_7d field.
apikeyDescUsage7d := apikeyFields[16].Descriptor()
// apikey.DefaultUsage7d holds the default value on creation for the usage_7d field.
apikey.DefaultUsage7d = apikeyDescUsage7d.Default.(float64)
accountMixin := schema.Account{}.Mixin()
accountMixinHooks1 := accountMixin[1].Hooks()
account.Hooks[0] = accountMixinHooks1[0]
@@ -186,29 +218,29 @@ func init() {
// account.DefaultConcurrency holds the default value on creation for the concurrency field.
account.DefaultConcurrency = accountDescConcurrency.Default.(int)
// accountDescPriority is the schema descriptor for priority field.
accountDescPriority := accountFields[8].Descriptor()
accountDescPriority := accountFields[9].Descriptor()
// account.DefaultPriority holds the default value on creation for the priority field.
account.DefaultPriority = accountDescPriority.Default.(int)
// accountDescRateMultiplier is the schema descriptor for rate_multiplier field.
accountDescRateMultiplier := accountFields[9].Descriptor()
accountDescRateMultiplier := accountFields[10].Descriptor()
// account.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
account.DefaultRateMultiplier = accountDescRateMultiplier.Default.(float64)
// accountDescStatus is the schema descriptor for status field.
accountDescStatus := accountFields[10].Descriptor()
accountDescStatus := accountFields[11].Descriptor()
// account.DefaultStatus holds the default value on creation for the status field.
account.DefaultStatus = accountDescStatus.Default.(string)
// account.StatusValidator is a validator for the "status" field. It is called by the builders before save.
account.StatusValidator = accountDescStatus.Validators[0].(func(string) error)
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
accountDescAutoPauseOnExpired := accountFields[14].Descriptor()
accountDescAutoPauseOnExpired := accountFields[15].Descriptor()
// account.DefaultAutoPauseOnExpired holds the default value on creation for the auto_pause_on_expired field.
account.DefaultAutoPauseOnExpired = accountDescAutoPauseOnExpired.Default.(bool)
// accountDescSchedulable is the schema descriptor for schedulable field.
accountDescSchedulable := accountFields[15].Descriptor()
accountDescSchedulable := accountFields[16].Descriptor()
// account.DefaultSchedulable holds the default value on creation for the schedulable field.
account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
accountDescSessionWindowStatus := accountFields[21].Descriptor()
accountDescSessionWindowStatus := accountFields[24].Descriptor()
// account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
account.SessionWindowStatusValidator = accountDescSessionWindowStatus.Validators[0].(func(string) error)
accountgroupFields := schema.AccountGroup{}.Fields()
@@ -251,12 +283,18 @@ func init() {
announcement.DefaultStatus = announcementDescStatus.Default.(string)
// announcement.StatusValidator is a validator for the "status" field. It is called by the builders before save.
announcement.StatusValidator = announcementDescStatus.Validators[0].(func(string) error)
// announcementDescNotifyMode is the schema descriptor for notify_mode field.
announcementDescNotifyMode := announcementFields[3].Descriptor()
// announcement.DefaultNotifyMode holds the default value on creation for the notify_mode field.
announcement.DefaultNotifyMode = announcementDescNotifyMode.Default.(string)
// announcement.NotifyModeValidator is a validator for the "notify_mode" field. It is called by the builders before save.
announcement.NotifyModeValidator = announcementDescNotifyMode.Validators[0].(func(string) error)
// announcementDescCreatedAt is the schema descriptor for created_at field.
announcementDescCreatedAt := announcementFields[8].Descriptor()
announcementDescCreatedAt := announcementFields[9].Descriptor()
// announcement.DefaultCreatedAt holds the default value on creation for the created_at field.
announcement.DefaultCreatedAt = announcementDescCreatedAt.Default.(func() time.Time)
// announcementDescUpdatedAt is the schema descriptor for updated_at field.
announcementDescUpdatedAt := announcementFields[9].Descriptor()
announcementDescUpdatedAt := announcementFields[10].Descriptor()
// announcement.DefaultUpdatedAt holds the default value on creation for the updated_at field.
announcement.DefaultUpdatedAt = announcementDescUpdatedAt.Default.(func() time.Time)
// announcement.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
@@ -417,6 +455,233 @@ func init() {
groupDescSortOrder := groupFields[21].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[22].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[23].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[24].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[25].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[26].Descriptor()
// group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field.
group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig)
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields()
_ = idempotencyrecordMixinFields0
idempotencyrecordFields := schema.IdempotencyRecord{}.Fields()
_ = idempotencyrecordFields
// idempotencyrecordDescCreatedAt is the schema descriptor for created_at field.
idempotencyrecordDescCreatedAt := idempotencyrecordMixinFields0[0].Descriptor()
// idempotencyrecord.DefaultCreatedAt holds the default value on creation for the created_at field.
idempotencyrecord.DefaultCreatedAt = idempotencyrecordDescCreatedAt.Default.(func() time.Time)
// idempotencyrecordDescUpdatedAt is the schema descriptor for updated_at field.
idempotencyrecordDescUpdatedAt := idempotencyrecordMixinFields0[1].Descriptor()
// idempotencyrecord.DefaultUpdatedAt holds the default value on creation for the updated_at field.
idempotencyrecord.DefaultUpdatedAt = idempotencyrecordDescUpdatedAt.Default.(func() time.Time)
// idempotencyrecord.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
idempotencyrecord.UpdateDefaultUpdatedAt = idempotencyrecordDescUpdatedAt.UpdateDefault.(func() time.Time)
// idempotencyrecordDescScope is the schema descriptor for scope field.
idempotencyrecordDescScope := idempotencyrecordFields[0].Descriptor()
// idempotencyrecord.ScopeValidator is a validator for the "scope" field. It is called by the builders before save.
idempotencyrecord.ScopeValidator = idempotencyrecordDescScope.Validators[0].(func(string) error)
// idempotencyrecordDescIdempotencyKeyHash is the schema descriptor for idempotency_key_hash field.
idempotencyrecordDescIdempotencyKeyHash := idempotencyrecordFields[1].Descriptor()
// idempotencyrecord.IdempotencyKeyHashValidator is a validator for the "idempotency_key_hash" field. It is called by the builders before save.
idempotencyrecord.IdempotencyKeyHashValidator = idempotencyrecordDescIdempotencyKeyHash.Validators[0].(func(string) error)
// idempotencyrecordDescRequestFingerprint is the schema descriptor for request_fingerprint field.
idempotencyrecordDescRequestFingerprint := idempotencyrecordFields[2].Descriptor()
// idempotencyrecord.RequestFingerprintValidator is a validator for the "request_fingerprint" field. It is called by the builders before save.
idempotencyrecord.RequestFingerprintValidator = idempotencyrecordDescRequestFingerprint.Validators[0].(func(string) error)
// idempotencyrecordDescStatus is the schema descriptor for status field.
idempotencyrecordDescStatus := idempotencyrecordFields[3].Descriptor()
// idempotencyrecord.StatusValidator is a validator for the "status" field. It is called by the builders before save.
idempotencyrecord.StatusValidator = idempotencyrecordDescStatus.Validators[0].(func(string) error)
// idempotencyrecordDescErrorReason is the schema descriptor for error_reason field.
idempotencyrecordDescErrorReason := idempotencyrecordFields[6].Descriptor()
// idempotencyrecord.ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save.
idempotencyrecord.ErrorReasonValidator = idempotencyrecordDescErrorReason.Validators[0].(func(string) error)
paymentauditlogFields := schema.PaymentAuditLog{}.Fields()
_ = paymentauditlogFields
// paymentauditlogDescOrderID is the schema descriptor for order_id field.
paymentauditlogDescOrderID := paymentauditlogFields[0].Descriptor()
// paymentauditlog.OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save.
paymentauditlog.OrderIDValidator = paymentauditlogDescOrderID.Validators[0].(func(string) error)
// paymentauditlogDescAction is the schema descriptor for action field.
paymentauditlogDescAction := paymentauditlogFields[1].Descriptor()
// paymentauditlog.ActionValidator is a validator for the "action" field. It is called by the builders before save.
paymentauditlog.ActionValidator = paymentauditlogDescAction.Validators[0].(func(string) error)
// paymentauditlogDescDetail is the schema descriptor for detail field.
paymentauditlogDescDetail := paymentauditlogFields[2].Descriptor()
// paymentauditlog.DefaultDetail holds the default value on creation for the detail field.
paymentauditlog.DefaultDetail = paymentauditlogDescDetail.Default.(string)
// paymentauditlogDescOperator is the schema descriptor for operator field.
paymentauditlogDescOperator := paymentauditlogFields[3].Descriptor()
// paymentauditlog.DefaultOperator holds the default value on creation for the operator field.
paymentauditlog.DefaultOperator = paymentauditlogDescOperator.Default.(string)
// paymentauditlog.OperatorValidator is a validator for the "operator" field. It is called by the builders before save.
paymentauditlog.OperatorValidator = paymentauditlogDescOperator.Validators[0].(func(string) error)
// paymentauditlogDescCreatedAt is the schema descriptor for created_at field.
paymentauditlogDescCreatedAt := paymentauditlogFields[4].Descriptor()
// paymentauditlog.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentauditlog.DefaultCreatedAt = paymentauditlogDescCreatedAt.Default.(func() time.Time)
paymentorderFields := schema.PaymentOrder{}.Fields()
_ = paymentorderFields
// paymentorderDescUserEmail is the schema descriptor for user_email field.
paymentorderDescUserEmail := paymentorderFields[1].Descriptor()
// paymentorder.UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save.
paymentorder.UserEmailValidator = paymentorderDescUserEmail.Validators[0].(func(string) error)
// paymentorderDescUserName is the schema descriptor for user_name field.
paymentorderDescUserName := paymentorderFields[2].Descriptor()
// paymentorder.UserNameValidator is a validator for the "user_name" field. It is called by the builders before save.
paymentorder.UserNameValidator = paymentorderDescUserName.Validators[0].(func(string) error)
// paymentorderDescFeeRate is the schema descriptor for fee_rate field.
paymentorderDescFeeRate := paymentorderFields[6].Descriptor()
// paymentorder.DefaultFeeRate holds the default value on creation for the fee_rate field.
paymentorder.DefaultFeeRate = paymentorderDescFeeRate.Default.(float64)
// paymentorderDescRechargeCode is the schema descriptor for recharge_code field.
paymentorderDescRechargeCode := paymentorderFields[7].Descriptor()
// paymentorder.RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save.
paymentorder.RechargeCodeValidator = paymentorderDescRechargeCode.Validators[0].(func(string) error)
// paymentorderDescOutTradeNo is the schema descriptor for out_trade_no field.
paymentorderDescOutTradeNo := paymentorderFields[8].Descriptor()
// paymentorder.DefaultOutTradeNo holds the default value on creation for the out_trade_no field.
paymentorder.DefaultOutTradeNo = paymentorderDescOutTradeNo.Default.(string)
// paymentorder.OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save.
paymentorder.OutTradeNoValidator = paymentorderDescOutTradeNo.Validators[0].(func(string) error)
// paymentorderDescPaymentType is the schema descriptor for payment_type field.
paymentorderDescPaymentType := paymentorderFields[9].Descriptor()
// paymentorder.PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save.
paymentorder.PaymentTypeValidator = paymentorderDescPaymentType.Validators[0].(func(string) error)
// paymentorderDescPaymentTradeNo is the schema descriptor for payment_trade_no field.
paymentorderDescPaymentTradeNo := paymentorderFields[10].Descriptor()
// paymentorder.PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save.
paymentorder.PaymentTradeNoValidator = paymentorderDescPaymentTradeNo.Validators[0].(func(string) error)
// paymentorderDescOrderType is the schema descriptor for order_type field.
paymentorderDescOrderType := paymentorderFields[14].Descriptor()
// paymentorder.DefaultOrderType holds the default value on creation for the order_type field.
paymentorder.DefaultOrderType = paymentorderDescOrderType.Default.(string)
// paymentorder.OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save.
paymentorder.OrderTypeValidator = paymentorderDescOrderType.Validators[0].(func(string) error)
// paymentorderDescProviderInstanceID is the schema descriptor for provider_instance_id field.
paymentorderDescProviderInstanceID := paymentorderFields[18].Descriptor()
// paymentorder.ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save.
paymentorder.ProviderInstanceIDValidator = paymentorderDescProviderInstanceID.Validators[0].(func(string) error)
// paymentorderDescStatus is the schema descriptor for status field.
paymentorderDescStatus := paymentorderFields[19].Descriptor()
// paymentorder.DefaultStatus holds the default value on creation for the status field.
paymentorder.DefaultStatus = paymentorderDescStatus.Default.(string)
// paymentorder.StatusValidator is a validator for the "status" field. It is called by the builders before save.
paymentorder.StatusValidator = paymentorderDescStatus.Validators[0].(func(string) error)
// paymentorderDescRefundAmount is the schema descriptor for refund_amount field.
paymentorderDescRefundAmount := paymentorderFields[20].Descriptor()
// paymentorder.DefaultRefundAmount holds the default value on creation for the refund_amount field.
paymentorder.DefaultRefundAmount = paymentorderDescRefundAmount.Default.(float64)
// paymentorderDescForceRefund is the schema descriptor for force_refund field.
paymentorderDescForceRefund := paymentorderFields[23].Descriptor()
// paymentorder.DefaultForceRefund holds the default value on creation for the force_refund field.
paymentorder.DefaultForceRefund = paymentorderDescForceRefund.Default.(bool)
// paymentorderDescRefundRequestedBy is the schema descriptor for refund_requested_by field.
paymentorderDescRefundRequestedBy := paymentorderFields[26].Descriptor()
// paymentorder.RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save.
paymentorder.RefundRequestedByValidator = paymentorderDescRefundRequestedBy.Validators[0].(func(string) error)
// paymentorderDescClientIP is the schema descriptor for client_ip field.
paymentorderDescClientIP := paymentorderFields[32].Descriptor()
// paymentorder.ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save.
paymentorder.ClientIPValidator = paymentorderDescClientIP.Validators[0].(func(string) error)
// paymentorderDescSrcHost is the schema descriptor for src_host field.
paymentorderDescSrcHost := paymentorderFields[33].Descriptor()
// paymentorder.SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save.
paymentorder.SrcHostValidator = paymentorderDescSrcHost.Validators[0].(func(string) error)
// paymentorderDescCreatedAt is the schema descriptor for created_at field.
paymentorderDescCreatedAt := paymentorderFields[35].Descriptor()
// paymentorder.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentorder.DefaultCreatedAt = paymentorderDescCreatedAt.Default.(func() time.Time)
// paymentorderDescUpdatedAt is the schema descriptor for updated_at field.
paymentorderDescUpdatedAt := paymentorderFields[36].Descriptor()
// paymentorder.DefaultUpdatedAt holds the default value on creation for the updated_at field.
paymentorder.DefaultUpdatedAt = paymentorderDescUpdatedAt.Default.(func() time.Time)
// paymentorder.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
paymentorder.UpdateDefaultUpdatedAt = paymentorderDescUpdatedAt.UpdateDefault.(func() time.Time)
paymentproviderinstanceFields := schema.PaymentProviderInstance{}.Fields()
_ = paymentproviderinstanceFields
// paymentproviderinstanceDescProviderKey is the schema descriptor for provider_key field.
paymentproviderinstanceDescProviderKey := paymentproviderinstanceFields[0].Descriptor()
// paymentproviderinstance.ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
paymentproviderinstance.ProviderKeyValidator = func() func(string) error {
validators := paymentproviderinstanceDescProviderKey.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(provider_key string) error {
for _, fn := range fns {
if err := fn(provider_key); err != nil {
return err
}
}
return nil
}
}()
// paymentproviderinstanceDescName is the schema descriptor for name field.
paymentproviderinstanceDescName := paymentproviderinstanceFields[1].Descriptor()
// paymentproviderinstance.DefaultName holds the default value on creation for the name field.
paymentproviderinstance.DefaultName = paymentproviderinstanceDescName.Default.(string)
// paymentproviderinstance.NameValidator is a validator for the "name" field. It is called by the builders before save.
paymentproviderinstance.NameValidator = paymentproviderinstanceDescName.Validators[0].(func(string) error)
// paymentproviderinstanceDescSupportedTypes is the schema descriptor for supported_types field.
paymentproviderinstanceDescSupportedTypes := paymentproviderinstanceFields[3].Descriptor()
// paymentproviderinstance.DefaultSupportedTypes holds the default value on creation for the supported_types field.
paymentproviderinstance.DefaultSupportedTypes = paymentproviderinstanceDescSupportedTypes.Default.(string)
// paymentproviderinstance.SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save.
paymentproviderinstance.SupportedTypesValidator = paymentproviderinstanceDescSupportedTypes.Validators[0].(func(string) error)
// paymentproviderinstanceDescEnabled is the schema descriptor for enabled field.
paymentproviderinstanceDescEnabled := paymentproviderinstanceFields[4].Descriptor()
// paymentproviderinstance.DefaultEnabled holds the default value on creation for the enabled field.
paymentproviderinstance.DefaultEnabled = paymentproviderinstanceDescEnabled.Default.(bool)
// paymentproviderinstanceDescPaymentMode is the schema descriptor for payment_mode field.
paymentproviderinstanceDescPaymentMode := paymentproviderinstanceFields[5].Descriptor()
// paymentproviderinstance.DefaultPaymentMode holds the default value on creation for the payment_mode field.
paymentproviderinstance.DefaultPaymentMode = paymentproviderinstanceDescPaymentMode.Default.(string)
// paymentproviderinstance.PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save.
paymentproviderinstance.PaymentModeValidator = paymentproviderinstanceDescPaymentMode.Validators[0].(func(string) error)
// paymentproviderinstanceDescSortOrder is the schema descriptor for sort_order field.
paymentproviderinstanceDescSortOrder := paymentproviderinstanceFields[6].Descriptor()
// paymentproviderinstance.DefaultSortOrder holds the default value on creation for the sort_order field.
paymentproviderinstance.DefaultSortOrder = paymentproviderinstanceDescSortOrder.Default.(int)
// paymentproviderinstanceDescLimits is the schema descriptor for limits field.
paymentproviderinstanceDescLimits := paymentproviderinstanceFields[7].Descriptor()
// paymentproviderinstance.DefaultLimits holds the default value on creation for the limits field.
paymentproviderinstance.DefaultLimits = paymentproviderinstanceDescLimits.Default.(string)
// paymentproviderinstanceDescRefundEnabled is the schema descriptor for refund_enabled field.
paymentproviderinstanceDescRefundEnabled := paymentproviderinstanceFields[8].Descriptor()
// paymentproviderinstance.DefaultRefundEnabled holds the default value on creation for the refund_enabled field.
paymentproviderinstance.DefaultRefundEnabled = paymentproviderinstanceDescRefundEnabled.Default.(bool)
// paymentproviderinstanceDescAllowUserRefund is the schema descriptor for allow_user_refund field.
paymentproviderinstanceDescAllowUserRefund := paymentproviderinstanceFields[9].Descriptor()
// paymentproviderinstance.DefaultAllowUserRefund holds the default value on creation for the allow_user_refund field.
paymentproviderinstance.DefaultAllowUserRefund = paymentproviderinstanceDescAllowUserRefund.Default.(bool)
// paymentproviderinstanceDescCreatedAt is the schema descriptor for created_at field.
paymentproviderinstanceDescCreatedAt := paymentproviderinstanceFields[10].Descriptor()
// paymentproviderinstance.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentproviderinstance.DefaultCreatedAt = paymentproviderinstanceDescCreatedAt.Default.(func() time.Time)
// paymentproviderinstanceDescUpdatedAt is the schema descriptor for updated_at field.
paymentproviderinstanceDescUpdatedAt := paymentproviderinstanceFields[11].Descriptor()
// paymentproviderinstance.DefaultUpdatedAt holds the default value on creation for the updated_at field.
paymentproviderinstance.DefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.Default.(func() time.Time)
// paymentproviderinstance.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
paymentproviderinstance.UpdateDefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.UpdateDefault.(func() time.Time)
promocodeFields := schema.PromoCode{}.Fields()
_ = promocodeFields
// promocodeDescCode is the schema descriptor for code field.
@@ -602,6 +867,43 @@ func init() {
redeemcodeDescValidityDays := redeemcodeFields[9].Descriptor()
// redeemcode.DefaultValidityDays holds the default value on creation for the validity_days field.
redeemcode.DefaultValidityDays = redeemcodeDescValidityDays.Default.(int)
securitysecretMixin := schema.SecuritySecret{}.Mixin()
securitysecretMixinFields0 := securitysecretMixin[0].Fields()
_ = securitysecretMixinFields0
securitysecretFields := schema.SecuritySecret{}.Fields()
_ = securitysecretFields
// securitysecretDescCreatedAt is the schema descriptor for created_at field.
securitysecretDescCreatedAt := securitysecretMixinFields0[0].Descriptor()
// securitysecret.DefaultCreatedAt holds the default value on creation for the created_at field.
securitysecret.DefaultCreatedAt = securitysecretDescCreatedAt.Default.(func() time.Time)
// securitysecretDescUpdatedAt is the schema descriptor for updated_at field.
securitysecretDescUpdatedAt := securitysecretMixinFields0[1].Descriptor()
// securitysecret.DefaultUpdatedAt holds the default value on creation for the updated_at field.
securitysecret.DefaultUpdatedAt = securitysecretDescUpdatedAt.Default.(func() time.Time)
// securitysecret.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
securitysecret.UpdateDefaultUpdatedAt = securitysecretDescUpdatedAt.UpdateDefault.(func() time.Time)
// securitysecretDescKey is the schema descriptor for key field.
securitysecretDescKey := securitysecretFields[0].Descriptor()
// securitysecret.KeyValidator is a validator for the "key" field. It is called by the builders before save.
securitysecret.KeyValidator = func() func(string) error {
validators := securitysecretDescKey.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(key string) error {
for _, fn := range fns {
if err := fn(key); err != nil {
return err
}
}
return nil
}
}()
// securitysecretDescValue is the schema descriptor for value field.
securitysecretDescValue := securitysecretFields[1].Descriptor()
// securitysecret.ValueValidator is a validator for the "value" field. It is called by the builders before save.
securitysecret.ValueValidator = securitysecretDescValue.Validators[0].(func(string) error)
settingFields := schema.Setting{}.Fields()
_ = settingFields
// settingDescKey is the schema descriptor for key field.
@@ -628,6 +930,105 @@ func init() {
setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time)
// setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time)
subscriptionplanFields := schema.SubscriptionPlan{}.Fields()
_ = subscriptionplanFields
// subscriptionplanDescName is the schema descriptor for name field.
subscriptionplanDescName := subscriptionplanFields[1].Descriptor()
// subscriptionplan.NameValidator is a validator for the "name" field. It is called by the builders before save.
subscriptionplan.NameValidator = func() func(string) error {
validators := subscriptionplanDescName.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(name string) error {
for _, fn := range fns {
if err := fn(name); err != nil {
return err
}
}
return nil
}
}()
// subscriptionplanDescDescription is the schema descriptor for description field.
subscriptionplanDescDescription := subscriptionplanFields[2].Descriptor()
// subscriptionplan.DefaultDescription holds the default value on creation for the description field.
subscriptionplan.DefaultDescription = subscriptionplanDescDescription.Default.(string)
// subscriptionplanDescValidityDays is the schema descriptor for validity_days field.
subscriptionplanDescValidityDays := subscriptionplanFields[5].Descriptor()
// subscriptionplan.DefaultValidityDays holds the default value on creation for the validity_days field.
subscriptionplan.DefaultValidityDays = subscriptionplanDescValidityDays.Default.(int)
// subscriptionplanDescValidityUnit is the schema descriptor for validity_unit field.
subscriptionplanDescValidityUnit := subscriptionplanFields[6].Descriptor()
// subscriptionplan.DefaultValidityUnit holds the default value on creation for the validity_unit field.
subscriptionplan.DefaultValidityUnit = subscriptionplanDescValidityUnit.Default.(string)
// subscriptionplan.ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save.
subscriptionplan.ValidityUnitValidator = subscriptionplanDescValidityUnit.Validators[0].(func(string) error)
// subscriptionplanDescFeatures is the schema descriptor for features field.
subscriptionplanDescFeatures := subscriptionplanFields[7].Descriptor()
// subscriptionplan.DefaultFeatures holds the default value on creation for the features field.
subscriptionplan.DefaultFeatures = subscriptionplanDescFeatures.Default.(string)
// subscriptionplanDescProductName is the schema descriptor for product_name field.
subscriptionplanDescProductName := subscriptionplanFields[8].Descriptor()
// subscriptionplan.DefaultProductName holds the default value on creation for the product_name field.
subscriptionplan.DefaultProductName = subscriptionplanDescProductName.Default.(string)
// subscriptionplan.ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save.
subscriptionplan.ProductNameValidator = subscriptionplanDescProductName.Validators[0].(func(string) error)
// subscriptionplanDescForSale is the schema descriptor for for_sale field.
subscriptionplanDescForSale := subscriptionplanFields[9].Descriptor()
// subscriptionplan.DefaultForSale holds the default value on creation for the for_sale field.
subscriptionplan.DefaultForSale = subscriptionplanDescForSale.Default.(bool)
// subscriptionplanDescSortOrder is the schema descriptor for sort_order field.
subscriptionplanDescSortOrder := subscriptionplanFields[10].Descriptor()
// subscriptionplan.DefaultSortOrder holds the default value on creation for the sort_order field.
subscriptionplan.DefaultSortOrder = subscriptionplanDescSortOrder.Default.(int)
// subscriptionplanDescCreatedAt is the schema descriptor for created_at field.
subscriptionplanDescCreatedAt := subscriptionplanFields[11].Descriptor()
// subscriptionplan.DefaultCreatedAt holds the default value on creation for the created_at field.
subscriptionplan.DefaultCreatedAt = subscriptionplanDescCreatedAt.Default.(func() time.Time)
// subscriptionplanDescUpdatedAt is the schema descriptor for updated_at field.
subscriptionplanDescUpdatedAt := subscriptionplanFields[12].Descriptor()
// subscriptionplan.DefaultUpdatedAt holds the default value on creation for the updated_at field.
subscriptionplan.DefaultUpdatedAt = subscriptionplanDescUpdatedAt.Default.(func() time.Time)
// subscriptionplan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
subscriptionplan.UpdateDefaultUpdatedAt = subscriptionplanDescUpdatedAt.UpdateDefault.(func() time.Time)
tlsfingerprintprofileMixin := schema.TLSFingerprintProfile{}.Mixin()
tlsfingerprintprofileMixinFields0 := tlsfingerprintprofileMixin[0].Fields()
_ = tlsfingerprintprofileMixinFields0
tlsfingerprintprofileFields := schema.TLSFingerprintProfile{}.Fields()
_ = tlsfingerprintprofileFields
// tlsfingerprintprofileDescCreatedAt is the schema descriptor for created_at field.
tlsfingerprintprofileDescCreatedAt := tlsfingerprintprofileMixinFields0[0].Descriptor()
// tlsfingerprintprofile.DefaultCreatedAt holds the default value on creation for the created_at field.
tlsfingerprintprofile.DefaultCreatedAt = tlsfingerprintprofileDescCreatedAt.Default.(func() time.Time)
// tlsfingerprintprofileDescUpdatedAt is the schema descriptor for updated_at field.
tlsfingerprintprofileDescUpdatedAt := tlsfingerprintprofileMixinFields0[1].Descriptor()
// tlsfingerprintprofile.DefaultUpdatedAt holds the default value on creation for the updated_at field.
tlsfingerprintprofile.DefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.Default.(func() time.Time)
// tlsfingerprintprofile.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
tlsfingerprintprofile.UpdateDefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.UpdateDefault.(func() time.Time)
// tlsfingerprintprofileDescName is the schema descriptor for name field.
tlsfingerprintprofileDescName := tlsfingerprintprofileFields[0].Descriptor()
// tlsfingerprintprofile.NameValidator is a validator for the "name" field. It is called by the builders before save.
tlsfingerprintprofile.NameValidator = func() func(string) error {
validators := tlsfingerprintprofileDescName.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(name string) error {
for _, fn := range fns {
if err := fn(name); err != nil {
return err
}
}
return nil
}
}()
// tlsfingerprintprofileDescEnableGrease is the schema descriptor for enable_grease field.
tlsfingerprintprofileDescEnableGrease := tlsfingerprintprofileFields[2].Descriptor()
// tlsfingerprintprofile.DefaultEnableGrease holds the default value on creation for the enable_grease field.
tlsfingerprintprofile.DefaultEnableGrease = tlsfingerprintprofileDescEnableGrease.Default.(bool)
usagecleanuptaskMixin := schema.UsageCleanupTask{}.Mixin()
usagecleanuptaskMixinFields0 := usagecleanuptaskMixin[0].Fields()
_ = usagecleanuptaskMixinFields0
@@ -703,88 +1104,108 @@ func init() {
return nil
}
}()
// usagelogDescRequestedModel is the schema descriptor for requested_model field.
usagelogDescRequestedModel := usagelogFields[5].Descriptor()
// usagelog.RequestedModelValidator is a validator for the "requested_model" field. It is called by the builders before save.
usagelog.RequestedModelValidator = usagelogDescRequestedModel.Validators[0].(func(string) error)
// usagelogDescUpstreamModel is the schema descriptor for upstream_model field.
usagelogDescUpstreamModel := usagelogFields[6].Descriptor()
// usagelog.UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
usagelog.UpstreamModelValidator = usagelogDescUpstreamModel.Validators[0].(func(string) error)
// usagelogDescModelMappingChain is the schema descriptor for model_mapping_chain field.
usagelogDescModelMappingChain := usagelogFields[8].Descriptor()
// usagelog.ModelMappingChainValidator is a validator for the "model_mapping_chain" field. It is called by the builders before save.
usagelog.ModelMappingChainValidator = usagelogDescModelMappingChain.Validators[0].(func(string) error)
// usagelogDescBillingTier is the schema descriptor for billing_tier field.
usagelogDescBillingTier := usagelogFields[9].Descriptor()
// usagelog.BillingTierValidator is a validator for the "billing_tier" field. It is called by the builders before save.
usagelog.BillingTierValidator = usagelogDescBillingTier.Validators[0].(func(string) error)
// usagelogDescBillingMode is the schema descriptor for billing_mode field.
usagelogDescBillingMode := usagelogFields[10].Descriptor()
// usagelog.BillingModeValidator is a validator for the "billing_mode" field. It is called by the builders before save.
usagelog.BillingModeValidator = usagelogDescBillingMode.Validators[0].(func(string) error)
// usagelogDescInputTokens is the schema descriptor for input_tokens field.
usagelogDescInputTokens := usagelogFields[7].Descriptor()
usagelogDescInputTokens := usagelogFields[13].Descriptor()
// usagelog.DefaultInputTokens holds the default value on creation for the input_tokens field.
usagelog.DefaultInputTokens = usagelogDescInputTokens.Default.(int)
// usagelogDescOutputTokens is the schema descriptor for output_tokens field.
usagelogDescOutputTokens := usagelogFields[8].Descriptor()
usagelogDescOutputTokens := usagelogFields[14].Descriptor()
// usagelog.DefaultOutputTokens holds the default value on creation for the output_tokens field.
usagelog.DefaultOutputTokens = usagelogDescOutputTokens.Default.(int)
// usagelogDescCacheCreationTokens is the schema descriptor for cache_creation_tokens field.
usagelogDescCacheCreationTokens := usagelogFields[9].Descriptor()
usagelogDescCacheCreationTokens := usagelogFields[15].Descriptor()
// usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field.
usagelog.DefaultCacheCreationTokens = usagelogDescCacheCreationTokens.Default.(int)
// usagelogDescCacheReadTokens is the schema descriptor for cache_read_tokens field.
usagelogDescCacheReadTokens := usagelogFields[10].Descriptor()
usagelogDescCacheReadTokens := usagelogFields[16].Descriptor()
// usagelog.DefaultCacheReadTokens holds the default value on creation for the cache_read_tokens field.
usagelog.DefaultCacheReadTokens = usagelogDescCacheReadTokens.Default.(int)
// usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field.
usagelogDescCacheCreation5mTokens := usagelogFields[11].Descriptor()
usagelogDescCacheCreation5mTokens := usagelogFields[17].Descriptor()
// usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field.
usagelog.DefaultCacheCreation5mTokens = usagelogDescCacheCreation5mTokens.Default.(int)
// usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field.
usagelogDescCacheCreation1hTokens := usagelogFields[12].Descriptor()
usagelogDescCacheCreation1hTokens := usagelogFields[18].Descriptor()
// usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field.
usagelog.DefaultCacheCreation1hTokens = usagelogDescCacheCreation1hTokens.Default.(int)
// usagelogDescInputCost is the schema descriptor for input_cost field.
usagelogDescInputCost := usagelogFields[13].Descriptor()
usagelogDescInputCost := usagelogFields[19].Descriptor()
// usagelog.DefaultInputCost holds the default value on creation for the input_cost field.
usagelog.DefaultInputCost = usagelogDescInputCost.Default.(float64)
// usagelogDescOutputCost is the schema descriptor for output_cost field.
usagelogDescOutputCost := usagelogFields[14].Descriptor()
usagelogDescOutputCost := usagelogFields[20].Descriptor()
// usagelog.DefaultOutputCost holds the default value on creation for the output_cost field.
usagelog.DefaultOutputCost = usagelogDescOutputCost.Default.(float64)
// usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field.
usagelogDescCacheCreationCost := usagelogFields[15].Descriptor()
usagelogDescCacheCreationCost := usagelogFields[21].Descriptor()
// usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field.
usagelog.DefaultCacheCreationCost = usagelogDescCacheCreationCost.Default.(float64)
// usagelogDescCacheReadCost is the schema descriptor for cache_read_cost field.
usagelogDescCacheReadCost := usagelogFields[16].Descriptor()
usagelogDescCacheReadCost := usagelogFields[22].Descriptor()
// usagelog.DefaultCacheReadCost holds the default value on creation for the cache_read_cost field.
usagelog.DefaultCacheReadCost = usagelogDescCacheReadCost.Default.(float64)
// usagelogDescTotalCost is the schema descriptor for total_cost field.
usagelogDescTotalCost := usagelogFields[17].Descriptor()
usagelogDescTotalCost := usagelogFields[23].Descriptor()
// usagelog.DefaultTotalCost holds the default value on creation for the total_cost field.
usagelog.DefaultTotalCost = usagelogDescTotalCost.Default.(float64)
// usagelogDescActualCost is the schema descriptor for actual_cost field.
usagelogDescActualCost := usagelogFields[18].Descriptor()
usagelogDescActualCost := usagelogFields[24].Descriptor()
// usagelog.DefaultActualCost holds the default value on creation for the actual_cost field.
usagelog.DefaultActualCost = usagelogDescActualCost.Default.(float64)
// usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field.
usagelogDescRateMultiplier := usagelogFields[19].Descriptor()
usagelogDescRateMultiplier := usagelogFields[25].Descriptor()
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64)
// usagelogDescBillingType is the schema descriptor for billing_type field.
usagelogDescBillingType := usagelogFields[21].Descriptor()
usagelogDescBillingType := usagelogFields[27].Descriptor()
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
usagelog.DefaultBillingType = usagelogDescBillingType.Default.(int8)
// usagelogDescStream is the schema descriptor for stream field.
usagelogDescStream := usagelogFields[22].Descriptor()
usagelogDescStream := usagelogFields[28].Descriptor()
// usagelog.DefaultStream holds the default value on creation for the stream field.
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
// usagelogDescUserAgent is the schema descriptor for user_agent field.
usagelogDescUserAgent := usagelogFields[25].Descriptor()
usagelogDescUserAgent := usagelogFields[31].Descriptor()
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
usagelog.UserAgentValidator = usagelogDescUserAgent.Validators[0].(func(string) error)
// usagelogDescIPAddress is the schema descriptor for ip_address field.
usagelogDescIPAddress := usagelogFields[26].Descriptor()
usagelogDescIPAddress := usagelogFields[32].Descriptor()
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
usagelog.IPAddressValidator = usagelogDescIPAddress.Validators[0].(func(string) error)
// usagelogDescImageCount is the schema descriptor for image_count field.
usagelogDescImageCount := usagelogFields[27].Descriptor()
usagelogDescImageCount := usagelogFields[33].Descriptor()
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
usagelog.DefaultImageCount = usagelogDescImageCount.Default.(int)
// usagelogDescImageSize is the schema descriptor for image_size field.
usagelogDescImageSize := usagelogFields[28].Descriptor()
usagelogDescImageSize := usagelogFields[34].Descriptor()
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
usagelogDescCacheTTLOverridden := usagelogFields[29].Descriptor()
usagelogDescCacheTTLOverridden := usagelogFields[35].Descriptor()
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
usagelog.DefaultCacheTTLOverridden = usagelogDescCacheTTLOverridden.Default.(bool)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt := usagelogFields[30].Descriptor()
usagelogDescCreatedAt := usagelogFields[36].Descriptor()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
userMixin := schema.User{}.Mixin()
@@ -876,6 +1297,22 @@ func init() {
userDescTotpEnabled := userFields[9].Descriptor()
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
user.DefaultTotpEnabled = userDescTotpEnabled.Default.(bool)
// userDescBalanceNotifyEnabled is the schema descriptor for balance_notify_enabled field.
userDescBalanceNotifyEnabled := userFields[11].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[12].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[14].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[15].Descriptor()
// user.DefaultTotalRecharged holds the default value on creation for the total_recharged field.
user.DefaultTotalRecharged = userDescTotalRecharged.Default.(float64)
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
_ = userallowedgroupFields
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.

View File

@@ -97,6 +97,8 @@ func (Account) Fields() []ent.Field {
field.Int("concurrency").
Default(3),
field.Int("load_factor").Optional().Nillable(),
// priority: 账户优先级,数值越小优先级越高
// 调度器会优先使用高优先级的账户
field.Int("priority").
@@ -164,6 +166,19 @@ func (Account) Fields() []ent.Field {
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
// temp_unschedulable_until: 临时不可调度状态解除时间
// 当命中临时不可调度规则时设置,在此时间前调度器应跳过该账号
field.Time("temp_unschedulable_until").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
// temp_unschedulable_reason: 临时不可调度原因,便于排障审计
field.String("temp_unschedulable_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// session_window_*: 会话窗口相关字段
// 用于管理某些需要会话时间窗口的 API如 Claude Pro
field.Time("session_window_start").
@@ -213,6 +228,9 @@ func (Account) Indexes() []ent.Index {
index.Fields("rate_limited_at"), // 筛选速率限制账户
index.Fields("rate_limit_reset_at"), // 筛选速率限制解除时间
index.Fields("overload_until"), // 筛选过载账户
index.Fields("deleted_at"), // 软删除查询优化
// 调度热路径复合索引(线上由 SQL 迁移创建部分索引schema 仅用于模型可读性对齐)
index.Fields("platform", "priority"),
index.Fields("priority", "status"),
index.Fields("deleted_at"), // 软删除查询优化
}
}

View File

@@ -41,6 +41,10 @@ func (Announcement) Fields() []ent.Field {
MaxLen(20).
Default(domain.AnnouncementStatusDraft).
Comment("状态: draft, active, archived"),
field.String("notify_mode").
MaxLen(20).
Default(domain.AnnouncementNotifyModeSilent).
Comment("通知模式: silent(仅铃铛), popup(弹窗提醒)"),
field.JSON("targeting", domain.AnnouncementTargeting{}).
Optional().
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).

View File

@@ -47,6 +47,10 @@ func (APIKey) Fields() []ent.Field {
field.String("status").
MaxLen(20).
Default(domain.StatusActive),
field.Time("last_used_at").
Optional().
Nillable().
Comment("Last usage time of this API key"),
field.JSON("ip_whitelist", []string{}).
Optional().
Comment("Allowed IPs/CIDRs, e.g. [\"192.168.1.100\", \"10.0.0.0/8\"]"),
@@ -70,6 +74,47 @@ func (APIKey) Fields() []ent.Field {
Optional().
Nillable().
Comment("Expiration time for this API key (null = never expires)"),
// ========== Rate limit fields ==========
// Rate limit configuration (0 = unlimited)
field.Float("rate_limit_5h").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per 5 hours (0 = unlimited)"),
field.Float("rate_limit_1d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per day (0 = unlimited)"),
field.Float("rate_limit_7d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per 7 days (0 = unlimited)"),
// Rate limit usage tracking
field.Float("usage_5h").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 5h window"),
field.Float("usage_1d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 1d window"),
field.Float("usage_7d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 7d window"),
// Window start times
field.Time("window_5h_start").
Optional().
Nillable().
Comment("Start time of the current 5h rate limit window"),
field.Time("window_1d_start").
Optional().
Nillable().
Comment("Start time of the current 1d rate limit window"),
field.Time("window_7d_start").
Optional().
Nillable().
Comment("Start time of the current 7d rate limit window"),
}
}
@@ -95,6 +140,7 @@ func (APIKey) Indexes() []ent.Index {
index.Fields("group_id"),
index.Fields("status"),
index.Fields("deleted_at"),
index.Fields("last_used_at"),
// Index for quota queries
index.Fields("quota", "quota_used"),
index.Fields("expires_at"),

View File

@@ -126,6 +126,25 @@ func (Group) Fields() []ent.Field {
field.Int("sort_order").
Default(0).
Comment("分组显示排序,数值越小越靠前"),
// OpenAI Messages 调度配置 (added by migration 069)
field.Bool("allow_messages_dispatch").
Default(false).
Comment("是否允许 /v1/messages 调度到此 OpenAI 分组"),
field.Bool("require_oauth_only").
Default(false).
Comment("仅允许非 apikey 类型账号关联到此分组"),
field.Bool("require_privacy_set").
Default(false).
Comment("调度时仅允许 privacy 已成功设置的账号"),
field.String("default_mapped_model").
MaxLen(100).
Default("").
Comment("默认映射模型 ID当账号级映射找不到时使用此值"),
field.JSON("messages_dispatch_model_config", domain.OpenAIMessagesDispatchModelConfig{}).
Default(domain.OpenAIMessagesDispatchModelConfig{}).
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
Comment("OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型"),
}
}

View File

@@ -0,0 +1,50 @@
package schema
import (
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// IdempotencyRecord 幂等请求记录表。
type IdempotencyRecord struct {
ent.Schema
}
func (IdempotencyRecord) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "idempotency_records"},
}
}
func (IdempotencyRecord) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.TimeMixin{},
}
}
func (IdempotencyRecord) Fields() []ent.Field {
return []ent.Field{
field.String("scope").MaxLen(128),
field.String("idempotency_key_hash").MaxLen(64),
field.String("request_fingerprint").MaxLen(64),
field.String("status").MaxLen(32),
field.Int("response_status").Optional().Nillable(),
field.String("response_body").Optional().Nillable(),
field.String("error_reason").MaxLen(128).Optional().Nillable(),
field.Time("locked_until").Optional().Nillable(),
field.Time("expires_at"),
}
}
func (IdempotencyRecord) Indexes() []ent.Index {
return []ent.Index{
index.Fields("scope", "idempotency_key_hash").Unique(),
index.Fields("expires_at"),
index.Fields("status", "locked_until"),
}
}

View File

@@ -0,0 +1,54 @@
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"
)
// PaymentAuditLog holds the schema definition for the PaymentAuditLog entity.
//
// 删除策略:硬删除
// PaymentAuditLog 使用硬删除而非软删除,原因如下:
// - 审计日志本身即为不可变记录,通常只追加不修改
// - 如需清理历史日志,直接按时间范围批量删除即可
// - 保持表结构简洁,提升插入和查询性能
type PaymentAuditLog struct {
ent.Schema
}
func (PaymentAuditLog) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_audit_logs"},
}
}
func (PaymentAuditLog) Fields() []ent.Field {
return []ent.Field{
field.String("order_id").
MaxLen(64),
field.String("action").
MaxLen(50),
field.String("detail").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.String("operator").
MaxLen(100).
Default("system"),
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (PaymentAuditLog) Indexes() []ent.Index {
return []ent.Index{
index.Fields("order_id"),
}
}

View File

@@ -0,0 +1,190 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// PaymentOrder holds the schema definition for the PaymentOrder entity.
//
// 删除策略:硬删除
// PaymentOrder 使用硬删除而非软删除,原因如下:
// - 订单通过 status 字段追踪完整生命周期,无需依赖软删除
// - 订单审计通过 PaymentAuditLog 表记录,删除前可归档
// - 减少查询复杂度,避免软删除过滤开销
type PaymentOrder struct {
ent.Schema
}
func (PaymentOrder) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_orders"},
}
}
func (PaymentOrder) Fields() []ent.Field {
return []ent.Field{
// 用户信息(冗余存储,避免关联查询)
field.Int64("user_id"),
field.String("user_email").
MaxLen(255),
field.String("user_name").
MaxLen(100),
field.String("user_notes").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 金额信息
field.Float("amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("pay_amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("fee_rate").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(0),
field.String("recharge_code").
MaxLen(64),
// 支付信息
field.String("out_trade_no").
MaxLen(64).
Default(""),
field.String("payment_type").
MaxLen(30),
field.String("payment_trade_no").
MaxLen(128),
field.String("pay_url").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("qr_code").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("qr_code_img").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 订单类型 & 订阅关联
field.String("order_type").
MaxLen(20).
Default("balance"),
field.Int64("plan_id").
Optional().
Nillable(),
field.Int64("subscription_group_id").
Optional().
Nillable(),
field.Int("subscription_days").
Optional().
Nillable(),
field.String("provider_instance_id").
Optional().
Nillable().
MaxLen(64),
// 状态
field.String("status").
MaxLen(30).
Default("PENDING"),
// 退款信息
field.Float("refund_amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}).
Default(0),
field.String("refund_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.Time("refund_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Bool("force_refund").
Default(false),
field.Time("refund_requested_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.String("refund_request_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("refund_requested_by").
Optional().
Nillable().
MaxLen(20),
// 时间节点
field.Time("expires_at").
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("paid_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("completed_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("failed_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.String("failed_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 来源信息
field.String("client_ip").
MaxLen(50),
field.String("src_host").
MaxLen(255),
field.String("src_url").
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"}),
}
}
func (PaymentOrder) Edges() []ent.Edge {
return []ent.Edge{
edge.From("user", User.Type).
Ref("payment_orders").
Field("user_id").
Unique().
Required(),
}
}
func (PaymentOrder) Indexes() []ent.Index {
return []ent.Index{
index.Fields("out_trade_no"),
index.Fields("user_id"),
index.Fields("status"),
index.Fields("expires_at"),
index.Fields("created_at"),
index.Fields("paid_at"),
index.Fields("payment_type", "paid_at"),
index.Fields("order_type"),
}
}

View File

@@ -0,0 +1,74 @@
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"
)
// PaymentProviderInstance holds the schema definition for the PaymentProviderInstance entity.
//
// 删除策略:硬删除
// PaymentProviderInstance 使用硬删除而非软删除,原因如下:
// - 服务商实例为管理员配置的支付通道,删除即表示废弃
// - 通过 enabled 字段控制是否启用,删除仅用于彻底移除
// - config 字段存储加密后的密钥信息,删除时应彻底清除
type PaymentProviderInstance struct {
ent.Schema
}
func (PaymentProviderInstance) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_provider_instances"},
}
}
func (PaymentProviderInstance) Fields() []ent.Field {
return []ent.Field{
field.String("provider_key").
MaxLen(30).
NotEmpty(),
field.String("name").
MaxLen(100).
Default(""),
field.String("config").
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("supported_types").
MaxLen(200).
Default(""),
field.Bool("enabled").
Default(true),
field.String("payment_mode").
MaxLen(20).
Default(""),
field.Int("sort_order").
Default(0),
field.String("limits").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.Bool("refund_enabled").
Default(false),
field.Bool("allow_user_refund").
Default(false),
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"}),
}
}
func (PaymentProviderInstance) Indexes() []ent.Index {
return []ent.Index{
index.Fields("provider_key"),
index.Fields("enabled"),
}
}

View File

@@ -0,0 +1,42 @@
package schema
import (
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
)
// SecuritySecret 存储系统级安全密钥(如 JWT 签名密钥、TOTP 加密密钥)。
type SecuritySecret struct {
ent.Schema
}
func (SecuritySecret) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "security_secrets"},
}
}
func (SecuritySecret) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.TimeMixin{},
}
}
func (SecuritySecret) Fields() []ent.Field {
return []ent.Field{
field.String("key").
MaxLen(100).
NotEmpty().
Unique(),
field.String("value").
NotEmpty().
SchemaType(map[string]string{
dialect.Postgres: "text",
}),
}
}

View File

@@ -0,0 +1,77 @@
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"
)
// SubscriptionPlan holds the schema definition for the SubscriptionPlan entity.
//
// 删除策略:硬删除
// SubscriptionPlan 使用硬删除而非软删除,原因如下:
// - 套餐为管理员维护的商品配置,删除即表示下架移除
// - 通过 for_sale 字段控制是否在售,删除仅用于彻底移除
// - 已购买的订阅记录保存在 UserSubscription 中,不受套餐删除影响
type SubscriptionPlan struct {
ent.Schema
}
func (SubscriptionPlan) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "subscription_plans"},
}
}
func (SubscriptionPlan) Fields() []ent.Field {
return []ent.Field{
field.Int64("group_id"),
field.String("name").
MaxLen(100).
NotEmpty(),
field.String("description").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.Float("price").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("original_price").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}).
Optional().
Nillable(),
field.Int("validity_days").
Default(30),
field.String("validity_unit").
MaxLen(10).
Default("day"),
field.String("features").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.String("product_name").
MaxLen(100).
Default(""),
field.Bool("for_sale").
Default(true),
field.Int("sort_order").
Default(0),
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"}),
}
}
func (SubscriptionPlan) Indexes() []ent.Index {
return []ent.Index{
index.Fields("group_id"),
index.Fields("for_sale"),
}
}

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