Compare commits

...

2347 Commits

Author SHA1 Message Date
Wesley Liddick
a0b5e5bfa0 Merge pull request #1973 from Nobody-Zhang/main
fix(payment): 修复 Zpay 退款接口调用
2026-04-26 13:11:42 +08:00
Wesley Liddick
41d0657330 Merge pull request #1970 from deqiying/fix-1754-claude-openai-cache-usage
fix(anthropic): 修正缓存 token 的 Anthropic 用量语义
2026-04-26 13:08:18 +08:00
Nobody-Zhang
1a0cabbfd6 Fix Zpay refund endpoint handling 2026-04-26 04:57:34 +00:00
shaw
9b6dcc57bd feat(affiliate): 完善邀请返利系统
- 修复返利不到账的根因:tryClaimAffiliateRebateAudit 中 PostgreSQL 参数类型推断冲突
  - 补全 OAuth 注册路径(LinuxDo/OIDC/WeChat/Pending Flow)的邀请码绑定
  - 前端 OAuth 注册页面传递 aff_code 参数
  - 新增返利冻结期机制:可配置冻结时间,到期后自动解冻(懒解冻)
  - 新增返利有效期:绑定后 N 天内有效,过期不再产生返利
  - 新增单人返利上限:超出上限部分精确截断
  - 增强返利流程 slog 结构化日志,便于排查问题
  - 已邀请用户列表增加返利明细列
2026-04-26 12:42:35 +08:00
deqiying
b17704d6ef fix(anthropic): 修正缓存 token 的 Anthropic 用量语义 2026-04-26 01:14:59 +08:00
shaw
496469ac4e fix(gateway): skip body mimicry for real Claude Code clients to restore prompt caching
PR #1914 unconditionally applied the full mimicry pipeline to all OAuth
accounts, including real Claude Code CLI clients. This replaced the
client's long system prompt (~10K+ tokens with stable cache_control
breakpoints) with a short ~45 token [billing, CC prompt] pair, which
falls below Anthropic's 1024-token minimum cacheable prefix threshold.
The result: every request created a new cache but never hit an existing
one.

Fix: restore the Claude Code client detection gate so that real CC
clients bypass body-level mimicry (system rewrite, message cache
management, tool name obfuscation). Non-CC third-party clients
(opencode, etc.) continue to receive full mimicry.

Also harden the detection logic:
- Make UA regex case-insensitive (align with claude_code_validator.go)
- Validate metadata.user_id format via ParseMetadataUserID() instead of
  just checking non-empty, preventing third-party tools from spoofing
  a claude-cli/* UA with an arbitrary user_id string to bypass mimicry
2026-04-25 22:50:35 +08:00
shaw
c1b52615be fix(payment): allow Stripe payment pages to bypass router auth guard
Stripe payment routes (/payment/stripe, /payment/stripe-popup) are
reached via hard navigation (window.location.href), which caused
the router guard to block access before the page could load.
Set requiresAuth and requiresPayment to false, consistent with
/payment/result. Backend API still enforces authentication.
2026-04-25 21:38:40 +08:00
shaw
3af9940b85 style: fix gofmt and ineffassign lint errors
- gofmt: realign AffiliateDetail struct tags in affiliate_service.go
- ineffassign: remove dead seenCompleted assignment before return in account_test_service.go
2026-04-25 20:37:42 +08:00
Wesley Liddick
22b1277572 Merge pull request #1948 from hungryboy1025/fix/openai-account-test-responses-stream
fix(openai): tighten responses stream account tests
2026-04-25 20:31:07 +08:00
Wesley Liddick
aff98d5ae1 Merge pull request #1960 from gaoren002/fix/openai-stream-keepalive-downstream-idle
fix(openai): keep responses stream alive during pre-output failover
2026-04-25 20:24:25 +08:00
shaw
4e1bb2b445 feat(affiliate): add feature toggle and per-user custom invite settings
- 在系统设置「功能开关」中新增邀请返利总开关,默认关闭;
  关闭态:菜单隐藏、注册忽略 aff、新充值不返利,但已有 quota 仍可转余额
- 支持管理员为指定用户设置专属邀请码(覆盖随机码,全局唯一)
- 支持管理员为指定用户设置专属返利比例(覆盖全局比例,可单条/批量调整)
- 在系统设置邀请返利卡片内嵌入专属用户管理表格(搜索/编辑/批量/删除),
  删除采用项目通用 ConfirmDialog,会同时清除专属比例并把邀请码重置为系统随机码
- /affiliate 用户页新增「我的返利比例」卡片与动态使用说明,让用户直观看到
  分享后能拿到多少(同源 resolveRebateRatePercent 计算,与实际充值一致)
- 新增数据库迁移 132 添加 aff_rebate_rate_percent 与 aff_code_custom 列
- 新增 admin 路由组 /api/v1/admin/affiliates/users/* 共 5 个端点
- AffiliateService 改为只依赖 *SettingService,去除冗余的 SettingRepository
- 邀请码格式校验放宽到 [A-Z0-9_-]{4,32},兼容旧 12 位系统码与新自定义码
- 补充单元测试与集成测试覆盖新方法、冲突路径与边界值
2026-04-25 20:22:07 +08:00
gaoren002
dac6e52091 fix(openai): keep responses stream alive during pre-output failover 2026-04-25 12:11:27 +00:00
hungryboy1025
8987e0ba67 fix(openai): tighten responses stream account tests 2026-04-25 16:56:50 +08:00
github-actions[bot]
9d1751ec57 chore: sync VERSION to 0.1.118 [skip ci] 2026-04-25 08:06:21 +00:00
Wesley Liddick
5d1c12e60e Merge pull request #1943 from AyeSt0/fix/openai-responses-preoutput-failover
fix(openai): 修复 Responses 流式失败前置事件导致无法 failover
2026-04-25 15:43:00 +08:00
AyeSt0
5b63a9b02d fix(openai): fail over before responses stream output 2026-04-25 15:09:40 +08:00
Wesley Liddick
641e61073f Merge pull request #1940 from 4fuu/fix/bump-codex-cli-version-to-0.125.0
fix(openai): bump codex CLI version from 0.104.0 to 0.125.0
2026-04-25 14:57:51 +08:00
shaw
095f457c57 feat(openai): port /responses/compact account support flow (PR #1555)
vansour/sub2api#1555 的 OpenAI compact 能力建模手工移植到当前 main:账号
级 compact 状态/auto-force_on-force_off 模式、compact-only 模型映射、调度器
tier 分层(已支持 > 未知 > 已知不支持)、管理后台 compact 主动探测,以及对应
i18n/状态徽章。普通 /responses 流量行为不变,无数据库迁移。
2026-04-25 14:52:58 +08:00
4fuu
1e57e88e43 fix(openai): bump codex CLI version from 0.104.0 to 0.125.0
The hardcoded codex CLI version (0.104.0) causes upstream rejection
when using gpt-5.5 with compact, as the server treats the request
as an outdated client and returns 400/502.

Update codexCLIVersion, codexCLIUserAgent, and openAICodexProbeVersion
to 0.125.0 to match the current Codex CLI release.

Fixes #1933, #1887, #1865
Related: #1609, #1298, #849
2026-04-25 05:26:33 +00:00
Wesley Liddick
b95ffce244 Merge pull request #1772 from KnowSky404/fix/openai-test-state-reconciliation
[codex] reconcile OpenAI admin test rate-limit state
2026-04-25 10:02:21 +08:00
shaw
8f28a834f8 fix(payment): 同时启用易支付和 Stripe 时显示 Stripe 按钮
VISIBLE_METHOD_ALIASES 漏了 stripe,导致 getVisibleMethods 把后端返回
的 stripe 过滤掉。点 Stripe 按钮时省略 method 查询参数,让落地页渲染
完整的 Payment Element。
2026-04-25 09:46:27 +08:00
shaw
7424c73b05 chore: remove unused model IDs 2026-04-25 09:04:34 +08:00
Wesley Liddick
1afd81b019 Merge pull request #1920 from Wuxie233/fix/responses-web-search-tool-types
fix(apicompat): recognize web_search_20250305 / google_search in Responses→Anthropic tool conversion
2026-04-25 09:00:37 +08:00
shaw
732d6495ea chore(gateway): fix lint issues from cc-mimicry-parity merge
- staticcheck QF1001: apply De Morgan's law to the OAuth-mimic header
  passthrough guard (`!(a && b)` → `a != ... || !b`).
- unused: drop `isClaudeCodeRequest`, which became dead after PR #1914
  switched both `/v1/messages` and `/count_tokens` paths to unconditional
  `account.IsOAuth()` mimicry. The lowercase helper `isClaudeCodeClient`
  is kept (still referenced by `TestIsClaudeCodeClient`).
2026-04-25 08:58:57 +08:00
Wesley Liddick
6d20ab8082 Merge pull request #1914 from keh4l/feat/cc-mimicry-parity
fix(claude): align Claude Code OAuth mimicry with real CLI traffic
2026-04-25 08:54:04 +08:00
shaw
aa8ee33b0a refactor(affiliate): tighten DI and harden inviter code validation
- Drop SetAffiliateService setters and ProvideAuthService /
  ProvidePaymentService / ProvideUserHandler wrappers in favor of direct
  Wire constructor injection. AffiliateService has no back-edge to
  Auth/Payment/User, so the indirection was never required.
- Change RegisterWithVerification's variadic affiliateCode to a fixed
  parameter; adjust all call sites.
- Validate aff_code length and charset in BindInviterByCode before any
  DB lookup, eliminating timing-side-channel and useless DB roundtrips
  on malformed input.
- Make affiliate cache invalidation synchronous; surface Redis errors
  via the project logger instead of swallowing them in a detached
  goroutine.
- Add an integration test guarding cross-layer tx propagation in
  AccrueQuota and a unit test pinning the aff_code format rules.
2026-04-25 08:44:18 +08:00
Wuxie233
5f630fbb19 fix(apicompat): recognize web_search_20250305 / google_search in Responses to Anthropic tool conversion 2026-04-25 01:09:51 +08:00
keh4l
bdbd2916f5 fix(gateway): skip client header passthrough on OAuth mimicry path
Root cause of persistent third-party detection: sub2api's
buildUpstreamRequest transparently forwards client headers via
allowedHeaders whitelist (addHeaderRaw) before applying mimicry
overrides. When third-party clients (opencode, etc.) send their own
anthropic-beta / user-agent / x-stainless-* / x-claude-code-session-id
values, these get appended to the request alongside our injected
headers, creating an inconsistent header set that Anthropic detects.

Parrot's build_upstream_headers constructs exactly 9 headers from
scratch and never forwards anything from the client. This is why
'same opencode version, some users work some don't' — different
opencode configs/versions send different header combinations.

Fix: when tokenType=oauth and mimicClaudeCode=true, skip the
client header passthrough loop entirely. The subsequent
applyClaudeCodeMimicHeaders + ApplyFingerprint + beta merge
pipeline constructs all necessary headers from our controlled values.

Also: remove systemIncludesClaudeCodePrompt gate — OAuth accounts
now unconditionally rewrite system (even if client already sent a
Claude Code-style prompt), ensuring billing attribution block is
always present.
2026-04-25 00:43:38 +08:00
keh4l
6dc89765fd fix(gateway): always apply full mimicry for OAuth accounts regardless of client identity
Before: isClaudeCodeRequest() checked whether the client looks like a
real Claude Code CLI (UA, system prompt, X-App header, metadata format).
If it looked like Claude Code, all mimicry was skipped — the assumption
being that a real CLI needs no help.

Problem: third-party tools like opencode partially impersonate Claude
Code (sending claude-cli UA + claude-code beta + CC system prompt) but
miss critical details (billing attribution block, tool-name obfuscation,
cache breakpoints, full beta set). Some users' opencode instances pass
the isClaudeCodeRequest check, causing sub2api to skip mimicry entirely,
while Anthropic still detects the request as third-party.

This explains why 'same opencode version, some users work, some don't'
— it depends on which opencode features/config trigger the validator.

Fix: OAuth accounts now unconditionally run the full mimicry pipeline,
matching Parrot's behavior (Parrot never checks client identity).
This is safe because our mimicry is strictly more complete than any
third-party client's partial impersonation.

Changed:
  - /v1/messages path: remove isClaudeCode gate
  - /v1/messages/count_tokens path: same
2026-04-25 00:26:37 +08:00
keh4l
f3233db01f fix(gateway): apply D/E/F mimicry to native /v1/messages and count_tokens paths
The previous commit only wired stripMessageCacheControl,
addMessageCacheBreakpoints, and tool-name obfuscation into
applyClaudeCodeOAuthMimicryToBody (used by /chat/completions and
/responses). The native /v1/messages path and count_tokens path
have their own independent mimicry code blocks and were missed.

Now all three entry points share the same D/E/F pipeline:
  - /v1/messages (gateway_service.go forwardAnthropic)
  - /v1/messages/count_tokens (gateway_service.go countTokens)
  - OpenAI compat (applyClaudeCodeOAuthMimicryToBody)
2026-04-24 23:16:32 +08:00
keh4l
6e12578bc5 feat(gateway): port Parrot tool-name obfuscation + message cache breakpoints
Implements the remaining three parity items with Parrot cc_mimicry:

  D) Tool-name obfuscation
     - Dynamic mapping when tools.length > 5 (matches Parrot threshold).
       Fake names follow {prefix}{name[:3]}{i:02d} (e.g. 'manage_bas00').
       Go port of random.Random(hash(tuple(names))) uses fnv64a seed +
       math/rand; byte-exact reproduction is impossible (Python hash vs
       Go hash), but the two invariants that matter are preserved:
         * same input tool_names yield identical mapping (cache hit)
         * prefix pool is shuffled (names look distributed)
     - Static prefix map (sessions_ -> cc_sess_, session_ -> cc_ses_)
       applied as fallback, matching Parrot TOOL_NAME_REWRITES verbatim.
     - Server tools (web_search_20250305, computer_*, etc.) are NOT
       renamed; only type=='function' and type=='custom' tools are.
     - tool_choice.name is rewritten in sync (only when type=='tool').
     - Response side: bytes-level replace on every SSE chunk / JSON
       body at 6 injection points (standard stream/non-stream,
       passthrough stream/non-stream, chat_completions stream +
       non-stream, responses stream + non-stream). Reverse mapping
       applied longest-fake-name-first to prevent substring conflicts
       (parity with Parrot _restore_tool_names_in_chunk).
     - tool_choice is no longer unconditionally deleted in
       normalizeClaudeOAuthRequestBody — Parrot passes it through.

  E) tools[-1] cache_control breakpoint
     - Injected as {type:ephemeral, ttl:<DefaultCacheControlTTL>} when
       the last tool has no cache_control. Client-provided ttl is
       passed through unchanged (repo-wide policy).

  F) messages cache_control strategy
     - stripMessageCacheControl removes every client-provided
       messages[*].content[*].cache_control (multi-turn stability).
     - addMessageCacheBreakpoints then injects two stable breakpoints:
       (1) last message, and (2) second-to-last user turn when
       messages.length >= 4.
     - Combined with the system block breakpoint and tools[-1]
       breakpoint, this gives exactly the 4 breakpoints Anthropic
       allows per request.

Non-trivial implementation details to be aware of when rebasing:

  * Two new files, no upstream collision:
      gateway_tool_rewrite.go       (D + E algorithms)
      gateway_messages_cache.go     (F strip + breakpoints)
  * Two new feature calls bolted onto the tail of
    applyClaudeCodeOAuthMimicryToBody in gateway_service.go — rebase
    conflicts will be ~10 lines maximum.
  * Response-side injection points all wrap their existing write with
    reverseToolNamesIfPresent(c, ...), preserving original behavior
    when no mapping is stored (static prefix rollback still runs).
  * Non-stream chat/responses switched from c.JSON to
    json.Marshal + c.Data so bytes-level replace is possible.
  * Retry bodies (FilterThinkingBlocksForRetry,
    FilterSignatureSensitiveBlocksForRetry, RectifyThinkingBudget)
    only prune blocks — they preserve the already-obfuscated tool
    names, so no extra mapping re-application is needed.

Manual QA: end-to-end scenario verified with 6 tools (above threshold)
and tool_choice.type=='tool'. Obfuscation + restore roundtrip shown
in test logs; then removed the temp test file.

Tests (16 new):
  - buildDynamicToolMap stability + below-threshold guard
  - sanitizeToolName precedence (dynamic > static)
  - restoreToolNamesInBytes longest-first + static rollback
  - applyToolNameRewriteToBody skips server tools + syncs tool_choice
  - applyToolsLastCacheBreakpoint defaults to 5m + passes client ttl
  - stripMessageCacheControl + addMessageCacheBreakpoints in the
    1/4/string-content cases + second-to-last user turn selection
  - buildToolNameRewriteFromBody ReverseOrdered is desc-by-fake-length
  - fake name shape follows Parrot {prefix}{head3}{i:02d}
2026-04-24 23:16:32 +08:00
keh4l
a25faecadd feat(gateway): align body shape with real Claude Code CLI defaults
Three field-level alignments in normalizeClaudeOAuthRequestBody to
match real Claude Code CLI traffic byte-for-byte:

  1. temperature: previously deleted unconditionally; now passes
     through client value, defaults to 1 when absent (real CLI
     always sends temperature, default 1).

  2. max_tokens: defaults to 128000 when absent (real CLI default).

  3. context_management: when thinking.type is enabled/adaptive
     and the client did not provide context_management, inject
     {"edits":[{"type":"clear_thinking_20251015","keep":"all"}]}
     to mirror real CLI behavior.

tool_choice removal is unchanged (Claude Code OAuth credentials
do not allow client-supplied tool_choice).

Tests updated:
  - gateway_body_order_test.go: temperature/max_tokens are now
    expected in output; tool_choice still removed.
  - gateway_prompt_test.go: system array is now 2 blocks
    (billing + cc prompt), assertions adjusted.
  - gateway_anthropic_apikey_passthrough_test.go: same 2-block
    assertion.
2026-04-24 23:16:32 +08:00
keh4l
5862e2d8d9 feat(gateway): add billing attribution block with cc_version fingerprint
Real Claude Code CLI always sends a 2-block system array:

  [0] {"type":"text", "text":"x-anthropic-billing-header: cc_version=X.Y.Z.{fp}; cc_entrypoint=cli; cch=00000;"}
  [1] {"type":"text", "text":"You are Claude Code...", "cache_control":{...}}

Before this commit, sub2api's mimicry path only produced block [1].
The missing billing block is one of the primary third-party detection
signals Anthropic uses for Claude-Code-scoped OAuth tokens.

New file gateway_billing_block.go ports the fingerprint algorithm
(byte-for-byte from Parrot cc_mimicry.py:compute_fingerprint):
pick chars at positions [4,7,20] of the first user text, then
`sha256(SALT + chars + cc_version)[:3]`.

  - claude/constants.go: CLICurrentVersion = "2.1.92" (must match UA)
  - gateway_billing_block.go: computeClaudeCodeFingerprint +
    buildBillingAttributionBlockJSON + extractFirstUserText
  - gateway_service.go: rewriteSystemForNonClaudeCode now emits both
    blocks in order; cch=00000 is filled in later by
    signBillingHeaderCCH in buildUpstreamRequest.

Downstream compat note: syncBillingHeaderVersion's regex
`cc_version=\d+\.\d+\.\d+` only matches the semver triple,
leaving the `.{fp}` suffix intact when rewriting in buildUpstreamRequest.
2026-04-24 23:16:32 +08:00
keh4l
66d6454535 feat(claude): add ttl to cache_control with default 5m
Real Claude CLI traffic sends cache_control as
`{"type":"ephemeral","ttl":"1h"}`. Our previous payload only
sent `{"type":"ephemeral"}`, which is a bytewise mismatch with
the official CLI and one more third-party detection signal.

Policy: client-provided ttl is always passed through unchanged.
Proxy-generated cache_control blocks default to 5m (vs Parrot's 1h)
to avoid burning the 1h cache budget on automatic breakpoints while
still aligning with the `ttl` field being present.

  - claude/constants.go: DefaultCacheControlTTL = "5m"
  - apicompat/types.go: new AnthropicCacheControl type with TTL field;
    AnthropicTool gains optional CacheControl pointer so the mimicry
    path can attach a cache breakpoint to tools[-1] later.
  - service/gateway_service.go: anthropicCacheControlPayload gains TTL;
    marshalAnthropicSystemTextBlock and rewriteSystemForNonClaudeCode
    emit ttl=5m by default.
2026-04-24 23:16:32 +08:00
keh4l
165553cfb0 fix(gateway): use full beta list in buildUpstreamRequest mimicry path
The previous commit added FullClaudeCodeMimicryBetas() but the two
call sites in buildUpstreamRequest still hardcoded the old 3-token
subset. Anthropic now checks the complete set of beta tokens to
decide if a request qualifies as Claude Code. Wire them up:

  - /v1/messages mimic path: requiredBetas = FullClaudeCodeMimicryBetas()
  - /v1/messages/count_tokens mimic path: same + BetaTokenCounting

Haiku models keep the 2-token exemption (BetaOAuth + InterleaveThinking).
2026-04-24 23:16:32 +08:00
keh4l
b5467d610a fix(gateway): apply full Claude Code mimicry on /chat/completions and /responses
Before: the OpenAI-compat forwarders only called injectClaudeCodePrompt,
which prepends the Claude Code banner but leaves the rest of the body
in its original non-Claude-Code shape. The codebase already admits this
is insufficient (see the comment on rewriteSystemForNonClaudeCode in
gateway_service.go: "仅前置追加 Claude Code 提示词无法通过检测").

Effect: OAuth accounts served through /v1/chat/completions or /v1/responses
were detected as third-party apps and bled plan quota with:

    Third-party apps now draw from your extra usage, not your plan limits.

Fix:
  - apicompat.AnthropicRequest: add Metadata json.RawMessage so metadata
    survives the OpenAI->Anthropic->Marshal round trip; without it the
    downstream rewrite has no user_id to work with.
  - service: extract applyClaudeCodeOAuthMimicryToBody, a ParsedRequest-free
    variant of the /v1/messages mimicry pipeline
    (rewriteSystemForNonClaudeCode + normalizeClaudeOAuthRequestBody +
    metadata.user_id injection) so the OpenAI-compat forwarders can reuse it.
  - service: add buildOAuthMetadataUserIDFromBody + hashBodyForSessionSeed
    for the same reason (no ParsedRequest at the call site).
  - ForwardAsChatCompletions / ForwardAsResponses: replace the 3-line
    prompt-prepend with the full mimicry pipeline.
  - applyClaudeCodeMimicHeaders: set x-client-request-id per-request
    (real Claude CLI always does); missing/duplicated values are one more
    third-party fingerprint signal.

No change to the native /v1/messages path: it already called the full
pipeline, we only lift those helpers into a reusable function.

Tests:
  - go build ./... passes
  - go test ./internal/service/... ./internal/pkg/apicompat/... passes
  - lsp_diagnostics clean on all touched files
  - pre-existing failures in internal/config are unrelated (env-sensitive
    tests that also fail on upstream main)
2026-04-24 23:16:32 +08:00
keh4l
57ff97960d chore(claude): bump mimicked CLI to 2.1.92 and extend anthropic-beta list
Align Claude Code mimicry constants with the latest real CLI traffic
(see Parrot's src/transform/cc_mimicry.py). Anthropic now uses the full
set of anthropic-beta tokens to decide whether a request counts as
"official Claude Code"; requests missing tokens that real CLI ships
today are demoted to third-party usage:

  Third-party apps now draw from your extra usage, not your plan limits.

Changes:
  - claude/constants.go: add new beta tokens (prompt-caching-scope,
    effort, redact-thinking, context-management, extended-cache-ttl) and
    expose FullClaudeCodeMimicryBetas() for the OAuth mimicry path.
  - claude/constants.go: bump default User-Agent to claude-cli/2.1.92.
  - identity_service.go: bump defaultFingerprint User-Agent accordingly.

No behavioral change for clients that already send a newer UA (fingerprint
merge still prefers the incoming value).
2026-04-24 23:16:32 +08:00
Wesley Liddick
5b5db88550 Merge pull request #1897 from VpSanta33/codex/invite-affiliate-rebate
feat: 新增邀请返利功能,并支持后台配置返利比例
2026-04-24 22:36:53 +08:00
VpSanta33
f03de00cb9 feat: add affiliate invite rebate flow and admin rebate-rate setting 2026-04-24 22:22:26 +08:00
Wesley Liddick
76aae5aa74 Merge pull request #1911 from gaoren002/fix/codex-responses-payload-normalization-mainbase
fix(openai): normalize codex responses payloads
2026-04-24 21:37:32 +08:00
gaoren002
27ee141c1e fix(openai): preserve mcp tool call ids 2026-04-24 13:24:21 +00:00
gaoren002
e65574dea9 fix(openai): normalize codex responses payloads 2026-04-24 12:03:19 +00:00
Wesley Liddick
1ce9dc03f9 Merge pull request #1895 from gaoren002/fix/codex-spark-limitations
fix(openai): handle codex spark model limitations
2026-04-24 19:57:42 +08:00
Wesley Liddick
15ce914a62 Merge pull request #1910 from slovx2/fix/codex-tool-call-ids
fix(openai): 修复 Codex 工具调用 call_id 处理
2026-04-24 19:56:03 +08:00
song
959af1c8f6 fix(openai): preserve codex tool call ids 2026-04-24 19:31:49 +08:00
gaoren002
c4d496da18 fix(openai): handle codex spark model limitations 2026-04-24 07:42:31 +00:00
KnowSky404
f3ea878ba2 chore: trigger PR checks 2026-04-24 11:32:41 +08:00
KnowSky404
d80469ea35 test: fix OpenAI account test helper calls after rebase 2026-04-24 11:32:41 +08:00
KnowSky404
5fc30ea964 test: cover openai admin test state transitions 2026-04-24 11:32:41 +08:00
KnowSky404
f68909a68b fix: reconcile openai admin test rate-limit state 2026-04-24 11:32:41 +08:00
github-actions[bot]
d162604f32 chore: sync VERSION to 0.1.117 [skip ci] 2026-04-24 01:40:02 +00:00
shaw
a4e329c18b fix: openai默认模型新增gpt5.5 2026-04-24 09:08:31 +08:00
shaw
ca204ddd2f fix(openai): preserve image outputs when text content serialization fails
In reconstructResponseOutputFromSSE, text content Marshal/Unmarshal
failure previously caused an early return that silently discarded
already-extracted image_generation_call outputs. Now serialization
errors are tolerated so image results still reach the client.
2026-04-24 08:58:51 +08:00
Wesley Liddick
ff08f9d798 Merge pull request #1853 from gaoren002/fix/codex-image-generation-bridge
fix(openai): 完善 Codex 在 Responses 链路下的图片生成兼容性
2026-04-24 08:55:23 +08:00
Wesley Liddick
ac11473833 Merge pull request #1850 from touwaeriol/feat/channel-insights
feat(monitor): channel monitor with available channels & feature flags
2026-04-24 08:31:21 +08:00
erio
09fd83ab9b fix(monitor): clean up unused updatedAt/updatedLabel after label removal 2026-04-24 00:14:30 +08:00
erio
6699d33760 fix(monitor): remove redundant "updated at" label from MonitorHero 2026-04-24 00:08:57 +08:00
erio
f7c8377abf fix(monitor): remove UNAVAILABLE status, keep only OPERATIONAL/DEGRADED 2026-04-24 00:03:22 +08:00
erio
0dcc0e0504 feat(monitor): proportion-based overall status + reusable auto-refresh
- Change overall status logic: >50% failed = UNAVAILABLE, any failed
  or degraded = DEGRADED, all ok = OPERATIONAL
- Extract useAutoRefresh composable with localStorage persistence
- Create AutoRefreshButton dropdown component (reusable)
- Integrate auto-refresh into channel status page via MonitorHero
2026-04-24 00:03:22 +08:00
gaoren002
5f41899705 fix: bridge codex image generation over responses 2026-04-23 15:13:57 +00:00
erio
5e060b2222 Merge remote-tracking branch 'upstream/main' into feat/channel-insights
# Conflicts:
#	backend/cmd/server/wire_gen.go
2026-04-23 22:30:45 +08:00
erio
6f04c25e3d test(api): add channel monitor fields to admin settings contract test 2026-04-23 22:15:03 +08:00
erio
375cce29c6 chore: remove accidentally committed fork utility script 2026-04-23 21:56:28 +08:00
erio
67518a59ac revert: remove fork-only changes from release sync
Revert payment/wechat, sora/claude-max cleanup, fork-only migrations,
and cosmetic changes that were brought in by the release sync commit.
Keep only channel-monitor related improvements:
- PublicSettingsInjectionPayload named struct with drift test
- ChannelMonitorRunner graceful shutdown in wire
- image_output_price in SupportedModelChip
- Simplified buildSelfNavItems in AppSidebar
- Gateway WARN logs for 503 branches
2026-04-23 21:40:58 +08:00
erio
a3ea8ecac5 fix(wire): add ChannelMonitorRunner.Stop() to cleanup steps in wire_gen.go 2026-04-23 21:06:51 +08:00
erio
497872693f chore: remove test files deleted in release
HelpTooltip.spec.ts and PaymentProviderDialog.spec.ts were removed
in release/custom-0.1.115; commit the deletion.
2026-04-23 21:04:54 +08:00
erio
748a84d871 sync: bring over remaining release/custom-0.1.115 changes
- Extract PublicSettingsInjectionPayload named struct with drift test
- Add channel_monitor_default_interval_seconds to SSR injection
- Add image_output_price to SupportedModelChip
- Simplify AppSidebar buildSelfNavItems (admins see available channels)
- Add gateway WARN logs for 503 no-available-accounts branches
- Wire ChannelMonitorRunner into provideCleanup for graceful shutdown
- Add migrations 130/131 (CC template userid fix + mimicry field cleanup)
- Clean up fork-only features (sora, claude max simulation, client affinity)
- Remove ~320 obsolete i18n keys
- Add codexUsage utility, WechatServiceButton, BulkEditAccountModal
- Tidy go.sum
2026-04-23 20:55:18 +08:00
erio
d5dac84e12 test(payment): cover ErrOrderNotFound sentinel contract
Service layer (payment_fulfillment_order_not_found_test.go):
- TestHandlePaymentNotification_UnknownOrder_ReturnsSentinel: in-memory
  sqlite ent client, query for a non-existent out_trade_no → errors.Is
  must recognise ErrOrderNotFound (handler relies on this to ack 200).
- TestHandlePaymentNotification_NonSuccessStatus_Skips: non-success
  notification short-circuits before DB lookup → nil error.
- TestErrOrderNotFound_DistinctFromOtherErrors: generic errors must not
  match the sentinel (prevents silently swallowing DB failures).

Handler layer (payment_webhook_handler_test.go):
- TestUnknownOrderWebhookAcksWithSuccess: locks the two ingredients the
  handleNotify ack path depends on — fmt.Errorf %w wrapping preserves
  errors.Is recognition, and writeSuccessResponse(stripe) returns an
  empty 200 body that Stripe treats as acknowledged.
2026-04-23 19:22:43 +08:00
erio
75e1b40fb4 fix(payment): ack unknown-order webhooks with 2xx to stop provider retries
Introduce a sentinel ErrOrderNotFound in the payment service layer so the
webhook handler can distinguish "the out_trade_no does not exist in our DB"
from other fulfillment failures, and downgrade the former to a WARN log +
success response.

Background
- Providers (Stripe, Alipay, Wxpay, EasyPay, ...) retry webhooks whenever
  we answer non-2xx. When a webhook endpoint is misconfigured (e.g. a
  foreign environment points at us) or our orders table has been wiped,
  we return 500 forever and the provider retries for days, spamming logs.
- The old code also collapsed "order not found" and "DB query failed" into
  the same branch — a DB blip would be reported as "order not found" and
  swallowed.

Service layer (payment_fulfillment.go)
- Add `var ErrOrderNotFound = errors.New("payment order not found")`.
- In HandlePaymentNotification, distinguish the two error paths:
  * dbent.IsNotFound(err) → wrap with ErrOrderNotFound so callers can
    errors.Is(...) it.
  * anything else → wrap the original err with %w so it still bubbles up
    as 500 and the provider retries (DB hiccup should be retried).

Handler layer (payment_webhook_handler.go)
- Before returning 500, check errors.Is(err, service.ErrOrderNotFound):
  emit a WARN (with provider / outTradeNo / tradeNo for discoverability),
  then call writeSuccessResponse so the provider sees its expected 2xx
  body (Stripe empty body / Wxpay JSON / others "success").
- Other errors retain the existing 500 behavior.

Monitoring note: because this path now swallows unknown-order webhooks
silently from the provider's perspective, the WARN log line is the only
signal. Alert on "unknown order, acking to stop retries" if you want
visibility into misrouted webhooks or accidental data loss.
2026-04-23 18:33:28 +08:00
erio
5eedf782f4 fix(frontend): add available_channels_enabled to PublicSettings type and defaults
featureFlags.ts registry uses 'available_channels_enabled' as a
public-settings key, but the PublicSettings TS type (types/index.ts)
and the app store default (stores/app.ts) only had
channel_monitor_enabled. Adds the missing field so pnpm build passes.
2026-04-23 18:24:07 +08:00
erio
1949425ab9 fix(dto): drop obsolete public settings drift test
The drift test referenced service.PublicSettingsInjectionPayload, a
named type introduced by a5b05538 but dropped when we cherry-picked
that commit into feat/channel-insights (we kept the inline struct from
HEAD to avoid pulling fork-only helpers from setting_service.go). The
test therefore could not compile. The 2 new public-settings fields
(channel_monitor_enabled, available_channels_enabled) are still covered
by manual wiring in GetPublicSettingsForInjection.
2026-04-23 18:21:31 +08:00
github-actions[bot]
0a80ec80e3 chore: sync VERSION to 0.1.116 [skip ci] 2026-04-23 09:47:27 +00:00
shaw
a22a5b9e72 chore: fix docker pull version tag in TG notification
Use ${VERSION} (without v prefix) instead of ${TAG_NAME} to match
GoReleaser's actual Docker image tags.
2026-04-23 17:33:22 +08:00
shaw
3fe4fd4c35 chore: add model gpt-5.5 2026-04-23 17:28:01 +08:00
Wesley Liddick
827a4498e0 Merge pull request #1829 from ZHOUKAILIAN/feature/codex-oauth-proxy-message
fix: 明确 OpenAI OAuth 未配置代理时的错误提示
2026-04-23 16:55:04 +08:00
Wesley Liddick
8dbbd94299 Merge pull request #1836 from wucm667/fix/account-daily-weekly-quota-cache-invalidation
fix: 修复账户配额跨越时调度快照入队逻辑
2026-04-23 16:49:25 +08:00
Wesley Liddick
6b0cf4663d Merge pull request #1815 from james-6-23/feat_rpm
feat(rpm): RPM 限流模块优化
2026-04-23 16:43:43 +08:00
james-6-23
dc5d42addc feat(rpm): RPM 限流模块优化
P0:
- rpm_override 嵌入 Auth Cache Snapshot,消除每请求 DB 查询 (snapshot v6→v7)
- 429 RPM 响应返回 Retry-After 头(当前分钟剩余秒数)

P1:
- ClearAll 按钮直连 DELETE API,带 loading 防重复
- 新增 GET /admin/users/:id/rpm-status 管理员 RPM 用量查询端点

优化:
- checkRPM 从级联互斥改为并行取最严,user.rpm_limit 作为全局硬上限始终生效
- Override/Group 变更后自动失效 auth cache
- fail-open 语义不变,Redis 故障不阻塞业务
2026-04-23 16:34:37 +08:00
shaw
ef967d8f8a fix: 修复 golangci-lint 报告的 36 个问题 2026-04-23 16:30:43 +08:00
Wesley Liddick
27ffc7f373 Merge pull request #1828 from wx-11/main
使用codex的生图接口代替web2api
2026-04-23 15:52:01 +08:00
wx-11
9e5a6351fc 修复计费问题以及模型回显 2026-04-23 15:09:47 +08:00
wucm667
bcf4aedcde fix: 修复账户配额跨越时调度快照入队逻辑 2026-04-23 14:53:57 +08:00
wx-11
11cf23da7d 修改403逻辑: 先临时冷却,再根据连续次数决定是否判坏号 2026-04-23 12:58:13 +08:00
wx-11
eea6f38881 使用codex的生图接口代替web2api 2026-04-23 12:44:44 +08:00
zhoukailian
2489ea3699 fix: clarify OpenAI OAuth proxy errors 2026-04-23 12:23:04 +08:00
shaw
0b85a8da88 fix: add io.LimitReader bounds to prevent OOM in image handling
Limit image download and multipart upload reads to 20MB to prevent
unbounded memory allocation from abnormal upstream responses.
2026-04-23 10:27:42 +08:00
Wesley Liddick
327da8e260 Merge pull request #1813 from meteor041/meteor041/fix-openai-image-handling
fix: openai image request handling
2026-04-23 10:19:12 +08:00
meteor041
00778dca31 fix openai image request handling 2026-04-23 09:53:57 +08:00
Wesley Liddick
79aff2df31 Merge pull request #1810 from IanShaw027/fix/profile-auth-bindings-i18n
fix(payment,profile,admin): 修复支付二维码流程、绑定提示与后台配置说明
2026-04-23 09:48:41 +08:00
IanShaw027
f35e967516 fix payment qr fallback and admin guidance 2026-04-22 07:33:14 -07:00
github-actions[bot]
6449da6c8d chore: sync VERSION to 0.1.115 [skip ci] 2026-04-22 12:08:51 +00:00
shaw
755c7d5026 chore: revert README files to 78f691d2 version 2026-04-22 19:55:13 +08:00
Wesley Liddick
1da4bd72df Merge pull request #1802 from IanShaw027/fix/profile-auth-bindings-i18n
fix(profile): 修正邮箱重复显示问题并添加国际化语言支持
2026-04-22 19:49:43 +08:00
IanShaw027
5551349349 fix: clean up profile auth binding notes 2026-04-22 19:11:51 +08:00
shaw
c6d25f69d5 chore: 恢复PAYMENT系列文件 2026-04-22 18:48:40 +08:00
shaw
45065c23d5 fix(ci): run 108a migration before 109 in backfill integration test 2026-04-22 18:36:44 +08:00
Wesley Liddick
ddf80f5ea1 Merge pull request #1799 from IanShaw027/rebuild/auth-identity-foundation
fix(auth,payment,profile): 修复认证身份和支付系统的后续问题
2026-04-22 18:18:39 +08:00
Wesley Liddick
c048ca80a4 Merge branch 'main' into rebuild/auth-identity-foundation 2026-04-22 18:17:12 +08:00
IanShaw027
22385be515 Merge remote-tracking branch 'upstream/main' into rebuild/auth-identity-foundation
# Conflicts:
#	backend/internal/service/openai_images.go
2026-04-22 18:13:05 +08:00
shaw
4d0483f5b8 feat: 补充gpt生图模型测试功能 2026-04-22 18:12:03 +08:00
IanShaw027
6b19490393 fix(ci): align openai account tests and remove dead wxpay const 2026-04-22 18:09:46 +08:00
shaw
1e0d466002 feat: 补充gpt生图模型测试功能 2026-04-22 18:06:14 +08:00
IanShaw027
9de7a72cce fix(upgrade): close payment and oidc compatibility gaps 2026-04-22 18:01:51 +08:00
IanShaw027
66b3acc274 fix(lint): remove embedded response selectors in openai images 2026-04-22 17:51:45 +08:00
IanShaw
0bc3a521b5 Merge branch 'Wei-Shaw:main' into rebuild/auth-identity-foundation 2026-04-22 17:24:38 +08:00
IanShaw027
3419cb0112 fix(admin): preserve legacy oidc security write defaults 2026-04-22 17:22:24 +08:00
IanShaw027
a94d89efa7 fix(unit): restore secure oidc defaults and wechat alias reuse 2026-04-22 16:51:23 +08:00
IanShaw027
66680a3056 fix(test): update wechat bind start path assertion 2026-04-22 16:44:25 +08:00
IanShaw027
ad4600964e fix(ci): clean up lint and dead code 2026-04-22 16:38:36 +08:00
IanShaw027
82259d1380 fix(auth): preserve resolved token version on oauth login 2026-04-22 16:01:25 +08:00
IanShaw027
ca4e38aa01 fix(profile): stabilize binding compatibility and frontend checks 2026-04-22 14:57:47 +08:00
IanShaw027
1aab084ecb fix(payment): restore upgrade-safe payment flows 2026-04-22 14:57:16 +08:00
IanShaw027
36aed35957 fix(auth): harden oauth identity upgrade paths 2026-04-22 14:56:56 +08:00
Wesley Liddick
32107b4f95 Merge pull request #1795 from 0x90000/feat/openai-image-api-sync
feat(openai): 同步生图 API 支持并接入图片计费调度
2026-04-22 14:24:41 +08:00
IanShaw027
3d29f7c2fa fix(auth): invalidate access tokens on session revoke 2026-04-22 13:30:34 +08:00
IanShaw027
01a991f56f fix(test): restore identity repo integration imports 2026-04-22 13:22:33 +08:00
IanShaw027
6696e61c7b fix(frontend): preserve callback recovery state 2026-04-22 13:19:41 +08:00
IanShaw027
81c827ee51 fix(profile): stabilize identity binding management 2026-04-22 13:19:28 +08:00
IanShaw027
83cad63ce0 fix(auth): harden oauth callback adoption flows 2026-04-22 13:19:20 +08:00
IanShaw027
06136af805 fix(upgrade): preserve legacy auth and payment compatibility 2026-04-22 13:18:10 +08:00
lucas morgan
6ad333d6b2 fix(openai): 修复生图服务 lint 问题
- 移除不安全的类型断言用法
- 补齐响应体关闭与字符串拼接的 lint 问题
- 按 staticcheck 建议简化选择器与条件表达式
2026-04-22 12:54:39 +08:00
IanShaw027
29caf85104 fix(frontend): stabilize wechat payment resume recovery 2026-04-22 12:30:24 +08:00
IanShaw027
d6a04bb772 fix(payment): support source routing and compatible resume signing 2026-04-22 12:30:17 +08:00
lucas morgan
c548021921 feat(openai): 同步生图 API 支持并接入图片计费调度
- 同步 OpenAI 图片生成与编辑接口
- 接入图片请求解析、账号调度、转发与用量记录
- 接入图片计费与图片用量落库
- 限制 OAuth 生图仅支持无显式模型和尺寸的基础请求
2026-04-22 12:30:08 +08:00
IanShaw027
b2e0712190 fix(settings): preserve oauth config compatibility on upgrade 2026-04-22 12:30:07 +08:00
IanShaw027
767f2f2dfe fix(auth): harden pending oauth and backend mode flows 2026-04-22 12:30:00 +08:00
IanShaw027
1ffebbb568 fix(migrations): keep auth identity and payment upgrades safe 2026-04-22 12:29:52 +08:00
IanShaw027
be9df2bea7 fix(auth): scrub legacy pending oauth tokens on upgrade 2026-04-22 11:29:05 +08:00
IanShaw027
9d5e9bbc18 fix(payment): respect configured visible method source 2026-04-22 11:28:58 +08:00
IanShaw027
454873221c test(auth): strengthen pending oauth legacy token assertions 2026-04-22 11:18:09 +08:00
IanShaw027
18481a100b fix(migrations): defer online ddl follow-ups safely 2026-04-22 11:17:45 +08:00
IanShaw027
ca1f30a911 fix(auth): harden pending oauth session consumption 2026-04-22 11:17:38 +08:00
IanShaw027
84628108fc fix(auth): preserve backward-compatible oauth defaults 2026-04-22 11:17:32 +08:00
IanShaw027
dd314c41e3 fix(payment): restore public resume and result flows 2026-04-22 11:17:23 +08:00
IanShaw027
c229f33e9e fix(review): harden payment, oauth, and migration paths 2026-04-22 10:26:22 +08:00
Wesley Liddick
8eb3f9e789 Merge pull request #1785 from IanShaw027/rebuild/auth-identity-foundation
feat(auth,payment): 重构认证身份和支付系统及其他部分优化
2026-04-22 10:14:15 +08:00
IanShaw027
7fbd5177c2 fix(ci): make legacy migration cleanup resilient 2026-04-22 09:15:39 +08:00
IanShaw027
fdf72eb511 fix(ci): repair integration repository tests 2026-04-22 02:42:43 +08:00
IanShaw027
b13e34f831 fix(ci): align auth and payment verification tests 2026-04-22 02:32:53 +08:00
IanShaw027
6d51834a95 refactor(profile): simplify profile page flow 2026-04-22 01:48:09 +08:00
IanShaw027
863258d782 Always show register password hint 2026-04-21 10:15:57 -07:00
IanShaw027
287f2f56d6 Show embedded avatar preview after selection 2026-04-21 10:13:28 -07:00
IanShaw027
525a320424 Fix user profile writes on postgres conflicts 2026-04-21 10:13:28 -07:00
IanShaw027
0f4a8d7be8 feat(profile): redesign profile center layout 2026-04-22 00:54:38 +08:00
IanShaw027
d4c0a99114 feat(auth): support unbinding third-party identities 2026-04-22 00:54:38 +08:00
IanShaw027
89d09838d8 Return bad request for invalid announcements 2026-04-21 09:53:15 -07:00
IanShaw027
0d87f94cb7 Harden adoption decision reassignment 2026-04-21 09:53:15 -07:00
IanShaw027
9bf8ab7048 Fix postgres provider grant queries 2026-04-21 09:53:15 -07:00
IanShaw027
dcbddef611 Merge remote-tracking branch 'origin/rebuild/auth-identity-foundation' into rebuild/auth-identity-foundation 2026-04-22 00:37:40 +08:00
IanShaw027
906802abe3 Fix mobile payment launch detection 2026-04-22 00:36:55 +08:00
IanShaw027
da1d26001f Merge branch 'main' into rebuild/auth-identity-foundation 2026-04-22 00:35:34 +08:00
IanShaw027
a13ae5a0da Fix mobile payment launch detection 2026-04-21 09:22:40 -07:00
IanShaw027
e4cfcae652 fix: reassign oauth adoption decisions on repeat login 2026-04-21 23:39:21 +08:00
IanShaw027
11db3989ce Fix repeated OAuth adoption prompt for existing logins 2026-04-21 23:35:59 +08:00
IanShaw027
40f7e832b4 fix: restore wechat settings compatibility after rebase 2026-04-21 23:26:45 +08:00
IanShaw027
b22d00e541 feat: drive visible payment methods from enabled providers 2026-04-21 23:20:37 +08:00
IanShaw027
54dc176725 feat(settings): support per-channel WeChat OAuth and persist payment options 2026-04-21 07:51:41 -07:00
IanShaw027
d5819181ea feat(auth): reclaim stale identities and refresh profile UI 2026-04-21 07:49:40 -07:00
IanShaw027
c0371e9104 frontend: align gateway scheduling toggles 2026-04-21 22:38:47 +08:00
IanShaw027
65d3bd728b frontend: normalize payment error presentation 2026-04-21 22:26:54 +08:00
IanShaw027
20062b44dc frontend: normalize profile and admin i18n cleanup 2026-04-21 22:26:35 +08:00
IanShaw027
a6b919eb53 frontend: normalize auth oauth i18n and error toasts 2026-04-21 22:26:11 +08:00
erio
1f81b77911 feat(settings): link feature toggles to their config pages
Channel Monitor card now links to 渠道管理 > 渠道监控 and the Available
Channels card links to 渠道管理 > 渠道定价 so admins know where to go
after flipping the switch.
2026-04-21 21:59:23 +08:00
erio
6cd7c60549 fix(channels): supported models = mapping ∪ pricing with global LiteLLM fallback
Why: channels with model pricing entries but no model mapping (e.g. azcc with
3 priced claude models, no mapping) were rendering as 未配置模型 in the
'Available Channels' page. The algorithm only iterated ModelMapping and
silently dropped any platform without a mapping entry.

Changes:
- channel.go: SupportedModels now unions mapping + pricing entries.
  For exact mapping src → target, pricing is looked up by target (the actually
  billed name), not by src.
- channel_available.go: ListAvailable enriches each entry with nil pricing
  via PricingService.GetModelPricing (global LiteLLM fallback) so the popover
  always shows a price.
- channel_service.go: NewChannelService takes *PricingService as 4th param.
- channel_test.go: rewrote 4 tests that froze the old mapping-only semantics;
  added pricing-only / mapping-target / target-missing coverage.
2026-04-23 00:45:10 +08:00
erio
25a5035503 fix(available-channels): description as own column, fixed table layout
- 描述独立成列:渠道名与描述各占一列,均用 rowspan 纵向合并
- 渠道名单元格 text-center + align-middle,合并后视觉居中
- table-fixed:给 name/description/platform 显式宽度,groups 和
  supported_models 在剩余空间均分。支持模型列此前在 table-auto 下
  不会换行导致横向溢出遮挡(反馈截图),加 table-fixed 后天然 flex-wrap
- i18n 增加 availableChannels.columns.description(zh/en)
2026-04-22 19:47:03 +08:00
erio
9dae6c7aee feat(sidebar+groups): available-channels above channel-status; show rate for subscription groups
- Sidebar user-side order: /available-channels now sits directly above
  /monitor (渠道状态) for regular users, mirroring the admin section where
  it sits above /admin/channels.
- GroupBadge gains an alwaysShowRate prop. Subscription groups default to
  a "订阅"/days-remaining label; the new flag swaps that for the rate
  multiplier while keeping the subscription theme color, so the Available
  Channels page can surface rates on every group type.
2026-04-21 22:10:51 +08:00
erio
ff4ef1b574 feat(channels): themed model popover + group-badge with rate, subscription & exclusivity
Pricing popover:
- Teleport to body + fixed-position re-measuring on hover/scroll/resize so it
  escapes the card's overflow-hidden clip that was chopping off the lower
  half of the panel.
- Header + border adopt the platform theme via platformBorderClass /
  platformBadgeLightClass so each model card reads at a glance.

Accessible groups:
- Backend AvailableGroupRef / user DTO now expose subscription_type,
  rate_multiplier and is_exclusive. User-specific rates continue to come
  from /groups/rates (same pattern as the API keys page).
- Table renders groups through the shared GroupBadge, which already deepens
  subscription-type colors and shows default vs custom rate as
  <s>default</s> <b>custom</b>.
- Exclusive groups are labelled with a purple shield row, public groups
  with a grey globe row so admins and users can tell at a glance which
  groups they got via explicit grant vs. public access.

i18n keys for exclusive / public / their tooltips added to zh + en.
2026-04-21 21:44:34 +08:00
erio
84b03efa0b fix(settings): inject channel_monitor & available_channels into SSR payload
Root cause: GetPublicSettingsForInjection used an inline struct that silently
drifted from dto.PublicSettings and omitted channel_monitor_enabled /
available_channels_enabled. On refresh window.__APP_CONFIG__ lacked these
keys, so cachedPublicSettings.available_channels_enabled resolved to
undefined and the opt-in sidebar entry (=== true) disappeared.

Backend: extract PublicSettingsInjectionPayload as a named type with all
feature-flag fields wired, and add a reflect-based drift test in the dto
package so forgetting a future flag fails CI instead of the browser.

Frontend: introduce utils/featureFlags.ts as the single registry for
public-settings-driven toggles, with explicit opt-in / opt-out modes that
encode the pre-load fallback. AppSidebar switches to makeSidebarFlag() so
adding a new switch only touches the registry.
2026-04-21 21:08:10 +08:00
IanShaw027
4c21320d1b fix(auth): require explicit choice for third-party signup 2026-04-21 20:36:58 +08:00
IanShaw027
2cebb0dc60 feat(settings): support dual-mode wechat oauth defaults 2026-04-21 20:36:10 +08:00
erio
3cdd5754df feat(channels): aggregate by channel with platform sections + rowspan table
Switch the user-facing 'Available Channels' view from "one row per
platform" to "one channel row-group with N platform sections".

Backend: userAvailableChannel now holds Platforms []section instead
of being exploded. buildPlatformSections replaces
explodeChannelByPlatform with the same per-platform grouping logic.

Frontend: drop the DataTable wrapper for this view and write a
four-column grid table (渠道名 / 平台 / 分组 / 支持模型) where the
channel name only renders on the first platform row of each channel —
visual rowspan without hacking DataTable.

- api/channels.ts: UserChannelPlatformSection + platforms[]
- AvailableChannelsTable: rewritten as native grid (header + per-
  channel section with hover row highlight)
- AvailableChannelsView: search now filters platforms sub-array;
  channel-name / description hits still keep the whole channel
- i18n: add availableChannels.columns.platform (zh/en)
2026-04-21 19:46:55 +08:00
erio
800802b8aa feat(channels): explode available channels by platform + apply platform theme
Backend: one source channel → N output rows, one per platform that
has user-visible groups. Each row carries a single platform, so the
frontend can color/icon an entire row without mixing sources.

- userAvailableChannel: add Platform field
- new explodeChannelByPlatform helper; drop now-redundant
  collectGroupPlatforms

Frontend: use the row platform to drive theming and stop repeating
"ANTHROPIC" / "OPENAI" labels on every model chip.

- api/channels.ts: UserAvailableChannel.platform
- AvailableChannelsTable: name cell — PlatformBadge next to channel
  name (replaces the two-line name/description block; description
  moves to the badge's title tooltip); groups cell — each chip uses
  platformBadgeLightClass + PlatformIcon; model list passes
  show-platform=false + platform-hint to child chips
- SupportedModelChip: chip bg/border driven by platformBadgeClass,
  leading PlatformIcon; platform-hint fallback when model.platform
  missing
2026-04-21 18:47:54 +08:00
IanShaw027
17c6348b57 fix(profile): restore source hints and upload-only avatar 2026-04-21 18:23:35 +08:00
IanShaw027
7309c02f0b refactor(profile): split avatar and bindings cards 2026-04-21 17:56:15 +08:00
IanShaw027
ee3f158f4e fix(settings): restore wechat and payment config persistence 2026-04-21 17:35:12 +08:00
IanShaw027
d08757ce9e refactor(admin): remove auth migration reports 2026-04-21 17:34:18 +08:00
erio
9ba42aa556 feat(channels): gate available channels behind feature switch (backend)
Add a DB-backed soft switch "available_channels_enabled" controlling
the user-facing /channels/available endpoint and sidebar entry. Default
to false (opt-in) — the feature stays invisible until an admin enables
it under Admin Settings > Features.

- domain_constants: SettingKeyAvailableChannelsEnabled
- settings_view: AllSettings/PublicSettings + AvailableChannelsEnabled
- setting_service: public+all read/write, seed default "false",
  GetAvailableChannelsRuntime helper (fail-closed on read error)
- admin setting_handler: UpdateSettingsRequest *bool + update branch
  + audit diff entry
- public setting_handler: expose via GET /api/v1/settings
- available_channel_handler: featureEnabled() guard — returns empty
  list after auth when disabled (401 precedes the feature check to
  preserve existing behavior)
2026-04-21 17:23:20 +08:00
erio
59290e39f9 chore(channels): drop admin-side available channels view
Remove the admin-side "Available Channels" aggregate view — admins
already see full channel configuration (groups, pricing, model
mappings) in the channel edit dialog, making a read-only admin
aggregate view redundant. The user-side "可用渠道" remains.

Backend:
- Delete handler/admin/available_channel_handler.go (+ test)
- Drop AdminHandlers.AvailableChannel field and wire injection
- Remove /admin/channels/available route

Frontend:
- Delete views/admin/AvailableChannelsView.vue
- Drop /admin/available-channels router entry
- Strip AvailableChannel types + listAvailable from api/admin/channels.ts
2026-04-21 17:18:37 +08:00
IanShaw027
c624cce88e fix: unblock auth identity compat backfill migration 2026-04-21 15:56:30 +08:00
shaw
78f691d2de chore: update sponsors 2026-04-21 14:57:59 +08:00
IanShaw027
49258dd3f6 fix: preserve scheduler transport compatibility defaults 2026-04-21 14:55:07 +08:00
IanShaw027
ed01c59916 feat: track authenticated user activity 2026-04-21 14:54:53 +08:00
IanShaw027
422f3449a2 chore: remove local docs from repo 2026-04-21 14:54:42 +08:00
erio
4a3652ec09 refactor(channels): normalize at cache fill and eliminate frontend as-cast
- channel.go: convert normalizeBillingModelSource into a (*Channel) method for entity cohesion
- channel_service.go: normalize in populateChannelCache so every cache-backed reader (gateway, billing, future endpoints) sees the default; drop the duplicate fallback inside resolveMapping
- table: tighten Row with status?: ChannelStatus / billing_model_source?: BillingModelSource, remove the [key: string]: unknown index signature
- admin view: drop the `as ChannelStatus` / `as BillingModelSource` assertions and add statusStyleOf / billingSourceLabelOf helpers with runtime fallback so unseen values render as "-" instead of crashing
2026-04-21 14:10:53 +08:00
IanShaw027
147ed42ad3 fix: restrict payment return urls to internal result page 2026-04-21 14:10:30 +08:00
IanShaw027
62ff2d803f fix: normalize chat completions service tier 2026-04-21 13:56:02 +08:00
IanShaw027
0fcddce69e fix: reject http responses continuation ids 2026-04-21 13:53:12 +08:00
IanShaw027
ace082066a fix: honor ws transport when scheduler is disabled 2026-04-21 13:50:55 +08:00
IanShaw027
65efef1eee feat: support replacing bound primary email 2026-04-21 13:47:15 +08:00
IanShaw027
12f1e19d68 fix: restore wechat oauth legacy callback compatibility 2026-04-21 13:36:19 +08:00
IanShaw027
0934f737d5 fix: snapshot merchant identity for alipay and easypay 2026-04-21 13:35:54 +08:00
IanShaw027
267844ebe6 fix: fail closed for legacy refund provider resolution 2026-04-21 13:10:59 +08:00
IanShaw027
ebd053c87e docs: clarify openai scheduler flag semantics 2026-04-21 13:07:40 +08:00
IanShaw027
64e401e224 fix: tighten payment legacy fallback paths 2026-04-21 13:03:53 +08:00
IanShaw027
276ce052a3 fix: align payment recovery query refs and resume authority 2026-04-21 13:01:21 +08:00
IanShaw027
119f784d19 fix: validate wxpay payments against order snapshots 2026-04-21 12:57:35 +08:00
IanShaw027
35aeeaa6e1 fix: pin payment read paths to provider snapshots 2026-04-21 12:50:55 +08:00
IanShaw027
561405ab00 feat: add payment order provider snapshots 2026-04-21 12:41:27 +08:00
shaw
960b2bb8e6 feat(legal): add CLA with automated GitHub Actions enforcement
Introduce Individual Contributor License Agreement (ICLA) to enable
dual licensing (LGPL-V3 open source + future closed-source releases).

- CLA.md: Apache ICLA-style license grant with moral rights waiver,
  patent license, electronic signature clause, and assignability
- .github/workflows/cla.yml: CLA Assistant Lite bot that auto-checks
  PRs, posts signing prompts, and stores signatures on a separate
  `cla-signatures` branch to keep main branch history clean
2026-04-21 12:06:45 +08:00
IanShaw027
440536a93d docs: align wechat payment required fields 2026-04-21 11:41:14 +08:00
IanShaw027
9742796ee7 fix: retire public payment verify and backfill trade no 2026-04-21 11:41:02 +08:00
erio
375aefa209 refactor(channels): centralize BillingModelSource normalization and exhaustive enum maps
- service: add normalizeBillingModelSource helper, apply in Create/GetByID/Update/List/ListAvailable outputs
- handler: drop channelToResponse fallback now that service owns the default; add passthrough test
- frontend: replace ternary status/billing-source lookups with Record<Enum, ...> maps so new union members fail the build
- chip/table: drop local type aliases, reuse UserSupportedModel/UserPricingInterval directly
- tests: assert short-circuit on ListAll error, wrap-prefix preservation, and Name-based default lookup
2026-04-21 11:31:54 +08:00
IanShaw027
33b208ab6f fix: restore legacy oauth callback fragment compatibility 2026-04-21 11:00:18 +08:00
IanShaw027
f398650166 fix: harden oidc compat email and email bind tx 2026-04-21 11:00:08 +08:00
IanShaw027
7e89bca5e6 fix: tighten pending oauth email routing and binding state 2026-04-21 10:41:29 +08:00
IanShaw027
dcd5c43da4 feat: complete email binding and pending oauth verification flows 2026-04-21 10:00:06 +08:00
IanShaw027
6da08262d7 feat avatar compress uploads to 20kb 2026-04-21 08:53:59 +08:00
Wesley Liddick
ffc9c38722 Merge pull request #1766 from touwaeriol/fix/codex-drop-removed-models
fix(openai): drop removed Codex models and fix normalization fallback side-effects
2026-04-21 08:50:00 +08:00
Wesley Liddick
a8854947c0 Merge pull request #1764 from touwaeriol/feat/wxpay-pubkey-hardening
feat(payment): wxpay pubkey-only hardening with structured errors and i18n
2026-04-21 08:49:00 +08:00
IanShaw027
07f23aaa7d fix wxpay config contract and h5 scene info 2026-04-21 08:35:53 +08:00
IanShaw027
2626e8f22c fix legacy email identity backfill grants 2026-04-21 08:28:48 +08:00
IanShaw027
09351e9459 fix auth completion and payment resume hardening 2026-04-21 08:23:26 +08:00
IanShaw027
f11b7d5105 docs: align payment defaults and wechat capability notes 2026-04-21 02:25:22 +08:00
IanShaw027
07bde2b665 docs: align payment guide with visible routing 2026-04-21 02:17:49 +08:00
IanShaw027
ebe7524415 fix profile activity and migration remediation 2026-04-21 02:08:56 +08:00
IanShaw027
a27a7add3d fix payment resume result consistency 2026-04-21 02:08:34 +08:00
IanShaw027
e12599c1b9 fix settings auth source default persistence 2026-04-21 02:08:04 +08:00
IanShaw027
cd0338fbae fix frontend wechat oauth capability recovery 2026-04-21 01:48:23 +08:00
IanShaw027
7c6491c2d3 fix auth pending session hardening 2026-04-21 01:45:25 +08:00
erio
88decb6e0c refactor(channels): tighten types and error paths per second review
- service: drop groupRepo nil guard (DI must inject), switch SupportedModels to SliceStable to match doc
- frontend: reuse user-side DTO types in SupportedModelChip/AvailableChannelsTable instead of duplicating shapes; narrow admin statusLabel param to ChannelStatus
- tests: replace nil-groupRepo case with ListAll/ListActive error propagation and BillingModelSource default-backfill coverage
2026-04-21 01:42:18 +08:00
IanShaw027
1d8432b8a4 fix: harden payment resume and wxpay webhook routing 2026-04-21 01:40:56 +08:00
IanShaw027
0a461d8248 fix: harden auth identity legacy migrations 2026-04-21 01:30:37 +08:00
IanShaw027
a70f7aca07 fix pending auth session restore 2026-04-21 01:05:59 +08:00
erio
365ef1fdf7 refactor(channels): consolidate pricing index, tighten types, polish DTOs
Follow-up to the available-channels review pass. No behavior change for
end users; tightens internals based on three independent code reviews.

Backend
- service/channel.go: collapse buildPricingLookup + pricedNamesFor
  into a single platformPricingIndex (byLower + originalCase + ordered
  names), built once per SupportedModels call. Fixes a casing-
  consistency bug where the same logical model appeared with mapping
  case in the exact branch but pricing case in the wildcard branch —
  pricing's original case now wins everywhere.
- service/channel.go: doc that a mapping key of just "*" expands to
  every priced model on the platform (intentional "passthrough all").
- service/channel_available.go: normalize empty BillingModelSource to
  channel_mapped at construction time, removing the same fallback
  duplicated in the admin DTO mapper and the admin Vue template.
- handler/admin/available_channel_handler.go: unexport
  availableChannelToAdminResponse (same-package usage only); mapper
  is now a pure passthrough.
- handler/available_channel_handler.go: drop the middleware2 alias
  (no name collision in this file).

Frontend
- utils/pricing.ts: extract formatScaled, used by SupportedModelChip
  and PricingRow.
- api/admin/channels.ts: re-export BillingMode from constants/channel;
  tighten Channel.status / billing_model_source to ChannelStatus /
  BillingModelSource (and same for AvailableChannel).
- components/channels/AvailableChannelsTable.vue: drop dead
  withDefaults wrapper (loading is required, both call sites pass it).
- views/admin/AvailableChannelsView.vue: drop the redundant
  || BILLING_MODEL_SOURCE_CHANNEL_MAPPED fallback (now applied in
  service layer); remove unused import.
- i18n zh + en: delete unused tierLabel and tokenRange keys from
  both availableChannels.pricing and admin.availableChannels.pricing.

Tests
- New: SupportedModels_ExactKeyUsesPricedCaseWhenAvailable locks the
  pricing-case-wins rule.
- New: SupportedModels_AsteriskOnlyMappingExpandsAllPriced documents
  the "*" expansion rule.
- Admin handler: existing tests adjusted to pass an explicit
  BillingModelSource (default-fill is now exercised by service tests).
2026-04-21 01:05:14 +08:00
IanShaw027
ea27ac6fd7 fix: unify email identity sync and retry first-bind defaults 2026-04-21 01:00:59 +08:00
IanShaw027
7a9488ff37 Add legacy identity safety remediation migration 2026-04-21 00:59:20 +08:00
IanShaw027
c297d0112e Keep pending payment results in processing state 2026-04-21 00:53:52 +08:00
IanShaw027
067eb23d8e Tighten WeChat OAuth capability mode selection 2026-04-21 00:46:40 +08:00
IanShaw027
12f4af742f fix auth pending adoption and turnstile flow 2026-04-21 00:45:56 +08:00
IanShaw027
e4fe9fae2a Fix profile refresh identity compatibility 2026-04-21 00:42:55 +08:00
IanShaw027
030da8c2f6 fix: close admin settings review gaps 2026-04-21 00:41:29 +08:00
IanShaw027
55e8dd550a Tighten WeChat payment resume flow 2026-04-21 00:33:23 +08:00
IanShaw027
1521d50399 fix: apply email first-bind defaults on legacy login 2026-04-21 00:31:52 +08:00
erio
654cfb6480 feat(channels): add "Available Channels" aggregate view
Add a read-only aggregate view per channel: its linked groups and a
deterministic wildcard-free supported-model list with pricing details.

Backend
- service.Channel.SupportedModels(): combine ModelMapping keys with
  same-platform ModelPricing.Models; trailing "*" keys expand via
  pricing prefix match; platforms without a mapping produce no
  entries (intentional "no mapping = not shown" rule).
- Extract splitWildcardSuffix() shared with toModelEntry.
- Build a per-call pricing lookup map (platform+lowerName -> *pricing)
  to avoid O(N*M) scans in SupportedModels.
- ChannelService.ListAvailable() aggregates channels + active groups;
  filters out group IDs no longer active.
- Admin route GET /api/v1/admin/channels/available returns the full
  DTO (id, status, billing_model_source, restrict_models, groups,
  supported_models).
- User route GET /api/v1/channels/available applies three filters:
  Status==active, visible-group intersection, and platform filter
  on supported_models (prevents cross-platform leak when a channel
  links to both a user-accessible group and an inaccessible one on
  another platform). Response is a plain array (matches the
  /groups/available sibling shape). Field whitelist omits
  billing_model_source, restrict_models, ids, status, sort_order.

Frontend
- New /admin/available-channels and /available-channels views backed
  by a shared AvailableChannelsTable component (admin adds status +
  billing-source columns via slots).
- PricingRow extracted to its own SFC; SupportedModelChip references
  shared billing-mode constants in constants/channel.ts.
- Sidebar: new entry above "渠道管理" for admin; matching entry in
  user nav.
- i18n: zh + en coverage for both namespaces.

Tests
- SupportedModels: wildcard-only pricing skipped, prefix-matches-
  nothing, cross-platform bleed, case-insensitive dedup, empty
  platform mapping.
- ListAvailable: nil groupRepo, inactive-group-ID dropped, stable
  case-insensitive name sort.
- User handler: 401 on unauthenticated, visible-group intersection,
  platform filter on supported_models, JSON whitelist.
- Admin handler: full DTO including default BillingModelSource
  fallback.

Refs: issue #1729
2026-04-21 00:27:10 +08:00
erio
c46744f366 refactor(channel-monitor): tighten runner lifecycle + add unit tests
- pool 改在 NewChannelMonitorRunner 构造时初始化,消除 Start 在 mu 内
  赋值、fire/Stop 在 mu 外读取的竞态隐患
- Schedule 在 !started 时由静默 return 改为 slog.Warn,错过的调度可见
- Schedule 在 interval<=0 时升为 slog.Error:Create/Update validateInterval
  已保证不可达,真触发即数据/校验链 bug
- 抽出 monitorRunnerSvc 内部接口(仅 ListEnabledMonitors+RunCheck),
  生产 *ChannelMonitorService 自然满足;runner 单元测试可注入轻量 stub
- 新增 channel_monitor_runner_test.go(10 个用例,//go:build unit):
  覆盖 Schedule/Unschedule/Start/Stop 生命周期、in-flight 槽对称释放、
  Stop 等待正在执行的 RunCheck 退出(无游离 goroutine)

启动失败的恢复策略:保持现状(log+return)。CLAUDE.md 明确"配置应保证启动
成功(必填项校验+正确数据校验)",validate{Provider,Interval,Endpoint,
APIKey,PrimaryModel} 已在 Create/Update 全部覆盖;DB 不可用是基础设施问题,
不该靠应用层无限重试兜底。
2026-04-22 20:08:31 +08:00
erio
c2f9ad7a21 refactor(channel-monitor): event-driven scheduler + sidebar cleanup
后端 - ChannelMonitorRunner 重写为事件驱动调度
- 删除 5 秒轮询架构(每次 ListEnabled + listDueForCheck 全表扫描),
  改为每个 enabled monitor 一个独立 goroutine + ticker(按各自 IntervalSeconds)
- 新增 MonitorScheduler 接口,service 通过 setter 注入避免依赖环
- ChannelMonitorService.Create/Update/Delete 直接回调 scheduler.Schedule/Unschedule
- runner.Start 一次性加载所有 enabled monitor 建立任务表
- 新建/启用立即触发首次检测,禁用/删除即时撤销 ticker
- 保留 inFlight 去重 + pond 池并发上限 + 全局开关每次 fire 实时校验
- 删除 listDueForCheck / monitorTickerInterval / monitorListDueTimeout

前端 - 可用渠道改为用户级菜单
- 从 adminNavItems 移除 /available-channels(admin 主菜单不再重复出现)
- buildSelfNavItems 始终包含可用渠道入口,普通用户主菜单和
  管理员"我的账户"区都能看到
2026-04-22 19:17:08 +08:00
erio
e1193212b5 feat(monitor): switch headers input to key-value rows
- AdvancedRequestConfig 把 headers textarea 换成行式:每行 name 输入 + value 输入
  + 删除按钮,底部「+ 添加 Header」。直观区分名/值,不用再一行 "Key: Value" 自己拆。
- 校验下放到行级:name 含空格或冒号才报错,未填仅占位不报错(避免输入时频繁红字)。
- 外部 props 同值不回写,避免 commit 后行被重排。
- chore: 移除 CLAUDE.md 里 silentflower remote 行(不再追踪)。
2026-04-21 15:37:57 +08:00
erio
a7415d4d2e feat(monitor): 30-day raw retention + timeline 4-tier style + CC template seed + JSON format button
- History retention 1d → 30d(60s × 30d ≈ 43200 行/model,PG 无压力);
  ComputeAvailability* 不再 UNION rollup 表,直接扫 histories 精度更高。
- Timeline bar 四级高度+颜色双重编码:operational 高+绿 / degraded 中+黄 /
  failed+error 短+红 / 未测试 很短+灰。
- migration 113 seed「Claude Code 伪装」模板(ON CONFLICT DO NOTHING)。
  user_id 用 legacy 格式(user_<64hex>_account_<uuid>_session_<uuid>),
  避免新版 JSON 字符串内嵌 JSON 在编辑器里一长串 \" 难读。
- MonitorAdvancedRequestConfig 加「格式化」按钮 + white-space:pre
  让 body textarea 对长字符串不压扁。
2026-04-21 15:24:48 +08:00
erio
6925ac25c4 feat(channel-monitor): apply template via subset picker; CC 2.1.114 baseline doc
Apply flow:
- POST /admin/channel-monitor-templates/:id/apply now requires monitor_ids
  (non-empty array). Service applies the template only to the selected
  subset, gated by AND template_id = :id (so users can't sneak in
  unrelated monitor IDs).
- New GET /admin/channel-monitor-templates/:id/monitors returns the
  associated monitor briefs (id/name/provider/enabled) for the picker.
- ApplyToMonitors signature gains monitorIDs []int64; empty list returns
  ErrChannelMonitorTemplateApplyEmpty.

Frontend:
- New MonitorTemplateApplyPickerDialog.vue: list of associated monitors
  with checkboxes (default all checked), 全选 / 全不选 shortcuts, live
  selected/total count. Submit calls apply(id, ids).
- MonitorTemplateManagerDialog replaces the old ConfirmDialog flow with
  the picker; onApplied refetches the list to refresh associated counts.

i18n: applyPicker* + common.selectAll keys.

chore: bump version to 0.1.114.33

The CC 2.1.114 (sdk-cli) UA / APIKeyBetaHeader / JSON metadata.user_id
baseline (already verified working via the in-process apply on prod
template id=1) is documented in internal/pkg/claude/constants.go and
is what the seed template in the manager UI should follow.
2026-04-21 14:39:19 +08:00
erio
a296425994 feat(channel-monitor): request templates with snapshot apply + headers/body override
Problem:
Upstream channels can reject monitor probes based on client fingerprint
(e.g. "only Claude Code clients allowed"). The monitor had no way to
customize the outgoing request to bypass such restrictions.

Solution:
Introduce reusable request templates that carry extra_headers plus an
optional body override; monitors reference a template and receive a
snapshot copy on apply. Template edits do NOT auto-propagate — users
must click "apply to associated monitors" to refresh snapshots, so a
bad template edit cannot instantly break all production monitors.

Data model (migration 112):
- channel_monitor_request_templates: id, name, provider, description,
  extra_headers jsonb, body_override_mode ('off'|'merge'|'replace'),
  body_override jsonb. Unique (provider, name).
- channel_monitors: +template_id (FK, ON DELETE SET NULL), +extra_headers,
  +body_override_mode, +body_override (the three runtime snapshot fields).

Checker (channel_monitor_checker.go):
- callProvider + runCheckForModel accept a CheckOptions carrying the
  snapshot fields. mergeHeaders applies user headers on top of adapter
  defaults (forbidden list: Host / Content-Length / Transfer-Encoding /
  Connection / Content-Encoding).
- buildRequestBody:
    off     -> adapter default body
    merge   -> shallow-merge over default; per-provider deny list
               (model/messages/contents) protects the challenge contract
    replace -> user body verbatim
- Replace mode skips challenge validation; instead HTTP 2xx + non-empty
  extracted response text = operational, empty = failed.
- 4 new unit tests cover all three modes + replace/empty-response case.

Admin API:
- /admin/channel-monitor-templates CRUD + /:id/apply (overwrite snapshot
  on all template_id=id monitors, returns affected count).
- channel_monitor request/response DTOs gain the 4 new fields.

Frontend:
- channelMonitorTemplate.ts API client.
- MonitorAdvancedRequestConfig.vue shared component for headers textarea
  + body mode radio + body JSON editor; used by both template and monitor
  forms.
- MonitorTemplateManagerDialog.vue: provider tabs, list/create/edit/
  delete/apply, live "associated monitors" count per row.
- MonitorFiltersBar: new 模板管理 button next to 新增监控.
- MonitorFormDialog: collapsible 高级 section with template dropdown
  (filtered by form.provider, clears on provider change) + embedded
  AdvancedRequestConfig. Picking a template copies its fields into the
  form (snapshot semantics mirrored on the client).
- i18n zh/en entries for all new copy.

chore: bump version to 0.1.114.32
2026-04-21 14:14:49 +08:00
erio
0c48f08f5c refactor(channel-status): drop breadcrumb + subtitle from MonitorHero
The "CHANNEL · STATUS" breadcrumb and the zh/en subtitles above the
window-picker were redundant with the existing "渠道状态" page title
shown in the layout header. Remove the left column and right-align the
7d/15d/30d tabs + overall chip.

Also drop the now-unreferenced channelStatus.hero.* i18n keys from both
locales (grep confirms no remaining usage).

chore: bump version to 0.1.114.31
2026-04-21 12:12:08 +08:00
erio
b363bff1d8 feat(channel-monitor): preserve upstream error body
Monitor:
- callProvider now returns both textPath-extracted text and raw body;
  runCheckForModel uses rawBody on non-2xx so history.message stops being
  "upstream HTTP 503: " with empty body (gjson textPath produces "" for
  error responses like {"error":{"message":"No available accounts..."}})
- truncateForErrorBody collapses whitespace then caps at 300 bytes
  (monitorErrorBodySnippetMaxBytes); final truncateMessage still enforces
  the 500-byte DB column cap

Frontend:
- MonitorFormDialog: primary_model input text color and ModelTagInput tags
  now both track form.provider (via new getPlatformTextClass + existing
  getPlatformTagClass with platform prop).

(cherry-picked from 1d3b0418; dropped gateway_handler logging改动,不在本 PR 范围)
2026-04-21 11:59:11 +08:00
erio
ef6ec8a15a fix(channel-monitor): drop soft delete, refactor feature flag to declarative form
### 后端修复:日志表不该用软删除

channel_monitor_histories / channel_monitor_daily_rollups 都是日志/聚合表,
没有恢复需求。110 里加的 SoftDeleteMixin 会让 DELETE 自动变成 UPDATE deleted_at,
导致行和索引只增不减,徒增磁盘占用和查询成本。

改回分批物理删(参考 OpsCleanupService.deleteOldRowsByID 模板):

- ent schema 移除 SoftDeleteMixin,重新 go generate
- repo 新增 deleteChannelMonitorBatched 辅助 + 两条 prune SQL 常量
  (WITH batch AS SELECT id LIMIT 5000 → DELETE IN batch)
- DeleteHistoryBefore / DeleteRollupsBefore 改调分批 raw SQL
- 移除 ComputeAvailability / ComputeAvailabilityForMonitors / UpsertDailyRollupsFor /
  ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors 等
  raw SQL 中的 deleted_at IS NULL 过滤
- UpsertDailyRollupsFor 的 ON CONFLICT 去掉 deleted_at = NULL 重置
- migration 111 DROP COLUMN deleted_at + 对应索引(110 已部署但 maintenance
  首跑在次日 02:00,此时尚无业务数据在依赖软删除)

### 前端重构:feature flag 声明式 + 复用

AppSidebar.vue 里 7 处 `...(flag ? [item] : [])` 样板代码删光,改为 NavItem 加
featureFlag?: () => boolean | undefined 字段,加一个 applyFeatureFlags 递归
过滤(含 children)。语义统一为 `!== false`(宽容策略,undefined 时默认显示,
避免 public settings 未加载完成时菜单闪烁消失 — 对应用户反馈"刷新后菜单消失
要去保存设置才回来")。

- 集中声明 4 个 flag getter:flagChannelMonitor / flagPayment /
  flagOpsMonitoring / flagAdminPayment
- 提取 buildSelfNavItems 复用用户端主菜单和管理员"我的账户"子菜单
- 未来新增开关:在统一位置加一个 flag getter + 给对应 NavItem 加字段
  (不用再动渲染逻辑)

bump 0.1.114.29
2026-04-23 17:31:15 +08:00
erio
8cf83c984e feat(channel-monitor): aggregate history to daily rollups + soft delete
明细只保留 1 天,超过 1 天聚合到新表 channel_monitor_daily_rollups(按
monitor_id/model/bucket_date 维度),聚合保留 30 天。两张表都用 SoftDeleteMixin
软删除(DELETE 自动改为 UPDATE deleted_at = NOW())。

聚合 + 清理任务由 OpsCleanupService 的 cron 统一调度,与运维监控的清理共享
schedule(默认 0 2 * * *)和 leader lock。ChannelMonitorRunner 的 cleanupLoop
被移除,只保留 dueCheckLoop。

读取路径 ComputeAvailability* 改为 UNION 明细(今天 deleted_at IS NULL)+
聚合(过去 windowDays 天 deleted_at IS NULL),SUM(ok)/SUM(total) 自然加权
计算可用率,AVG latency 用 SUM(sum_latency_ms)/SUM(count_latency)。

watermark 表 channel_monitor_aggregation_watermark 单行(id=1),记录
last_aggregated_date,重启后从该日期 +1 继续聚合,首次为 nil 则从
today - 30d 开始回填,单次最多 35 天上限避免长事务。

raw SQL 的 ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors
都补上 deleted_at IS NULL 过滤(SoftDeleteMixin interceptor 只对 ent query 生效)。

bump version to 0.1.114.28

GroupBadge 在 MonitorKeyPickerDialog 中复用平台主题色 + 倍率/专属倍率
(顺手优化)。
2026-04-21 10:10:56 +08:00
erio
ba98243cc2 feat(channel-monitor): gate UI by feature switch + polish form UX
- AppSidebar 三处菜单项(管理端渠道监控、用户端/个人页渠道状态)按
  channel_monitor_enabled 条件展开,关闭时隐藏
- ChannelStatusView setInterval 随开关启停:关闭 clearInterval,
  开启/未知态自动启动,避免禁用功能后仍在轮询
- MonitorFormDialog provider Select 改为 3 色单选按钮
  (openai=emerald / anthropic=orange / gemini=sky),i18n 文案
  供应商 → 平台 / Provider → Platform
- MonitorKeyPickerDialog 按钮列表改为 name/key/group 三列表格 +
  搜索框,按 key.group.platform === provider 过滤,避免跨平台误选
- form.provider 变化时清空 api_key,修复切换平台仍保留旧 key 的
  错配 bug
- providerPickerClass 抽取到 useChannelMonitorFormat composable,
  统一 emerald/orange/sky 颜色语义,消除硬编码 Tailwind class 重复
- maskApiKey 工具函数统一(utils/maskApiKey.ts),KeysView 与
  MonitorKeyPickerDialog 共用 slice(0,6)...slice(-4) 策略
- bump version to 0.1.114.27
2026-04-21 01:42:58 +08:00
erio
0d01bd908e refactor(channel-monitor): remove INTELLIGENCE MONITOR hero title
Subtitle + breadcrumb already convey context; the giant h1 was visual
noise. Drops orphan i18n key `channelStatus.hero.title` and shrinks
hero section vertical padding accordingly.

Bump VERSION to 0.1.114.26
2026-04-21 00:27:07 +08:00
IanShaw027
bf3ef2d19a add admin user last used support 2026-04-21 00:22:17 +08:00
erio
7da5124067 feat(channel-monitor): add feature switch settings + fix extra_models save
Settings:
- New "功能开关" tab between 通用设置 and 安全与认证
- ChannelMonitorEnabled toggle: runner skips scheduling when false,
  user-facing list returns empty
- ChannelMonitorDefaultIntervalSeconds (15-3600): pre-fills interval
  when creating a new monitor; each monitor can still override

Bug fix:
- ModelTagInput now commits pending input on blur, not just Enter/Tab.
  Previously clicking "save" with an un-Enter'd extra model would drop
  the value (DB stored extra_models=[] even when user typed entries).

Backend:
- domain_constants: SettingKeyChannelMonitor{Enabled,DefaultIntervalSeconds}
- SettingService.GetChannelMonitorRuntime: lightweight getter used by
  runner tick + user handler per-request (fail-open on DB error)
- Runner tickDueChecks: bails early when feature disabled
- ChannelMonitorUserHandler: checks feature flag before serving
- Comment on runner doc: scheduler state is implicit (every tick re-reads
  ListEnabled from DB), so CRUD ops on monitors self-maintain the schedule

Bump VERSION to 0.1.114.25
2026-04-21 00:21:29 +08:00
IanShaw027
beeab54ae3 Implement latest-used user repo queries 2026-04-21 00:17:48 +08:00
IanShaw027
b79052aaf2 Decouple email sync tests from local stubs 2026-04-21 00:16:06 +08:00
IanShaw027
16be82b959 fix payment visible methods and resume recovery 2026-04-21 00:14:05 +08:00
IanShaw027
5d58c7c6fb Add auth identity legacy backfill and email sync 2026-04-21 00:13:40 +08:00
IanShaw027
9204145746 Close profile identity and avatar loop 2026-04-21 00:11:03 +08:00
IanShaw027
f73117f9b1 feat: add admin auth migration reports view 2026-04-21 00:07:14 +08:00
IanShaw027
85fc54b205 fix(frontend): restore pending auth session flow 2026-04-21 00:05:44 +08:00
IanShaw027
4f6966d7b3 frontend: route wechat oauth entry by public settings 2026-04-21 00:05:42 +08:00
IanShaw027
9e84e2fd2b fix: persist admin payment visibility and scheduler settings 2026-04-21 00:05:17 +08:00
IanShaw027
f83fd59dca Refine payment UX for wallet flows 2026-04-21 00:05:09 +08:00
IanShaw027
4ebdfcd13a test(admin): constrain payment visible method sources 2026-04-21 00:03:27 +08:00
IanShaw027
0fa47f18ed feat: complete pending oauth account creation UI 2026-04-21 00:02:51 +08:00
erio
a1425b457d feat(channel-monitor): redesign user dashboard as card grid
Reference check-cx UI: INTELLIGENCE MONITOR hero + 3-column card grid
with 60-point timeline bars.

Backend:
- Add PrimaryPingLatencyMs + Timeline[60] to UserMonitorView
- ListRecentHistoryForMonitors: batch CTE + ROW_NUMBER() window query
- indexLatestByModel / indexAvailabilityByModel helpers

Frontend:
- 7 new components: ProviderIcon, MonitorMetricPair, MonitorAvailabilityRow,
  MonitorTimeline, MonitorHero, MonitorCard, MonitorCardGrid
- ChannelStatusView 381→~180 lines (delegated to subcomponents)
- AbortController reload concurrency protection
- HSL 0-120° availability color mapping
- Replace emoji with Icon component (bolt / globe)
- i18n: monitorCommon.* shared namespace, channelStatus.hero.*

Bump VERSION to 0.1.114.24
2026-04-20 23:38:59 +08:00
IanShaw027
7ef7fd19e7 fix: restore wechat payment oauth and jsapi flow 2026-04-20 23:34:57 +08:00
IanShaw027
6f00efa350 fix: support legacy payment method aliases 2026-04-20 23:05:29 +08:00
IanShaw027
e1a28848fa fix: clarify wechat existing account binding 2026-04-20 22:54:47 +08:00
IanShaw027
7fdede579a fix: preserve wechat bind resume state 2026-04-20 22:52:56 +08:00
IanShaw027
4d10ba4297 fix: complete wechat pending auth callback flows 2026-04-20 22:50:41 +08:00
IanShaw027
bffcc2042e fix: complete oidc pending auth callback flows 2026-04-20 22:37:25 +08:00
IanShaw027
724f8e89a1 feat: resolve auth identity migration reports 2026-04-20 22:29:21 +08:00
IanShaw027
452e55a53c feat: add admin auth identity repair binding 2026-04-20 22:22:14 +08:00
IanShaw027
3bd3027251 feat: expose auth identity migration reports 2026-04-20 22:05:33 +08:00
erio
bbc4aed3d9 fix(openai): 移除已下线 Codex 模型并修复归一化兜底副作用
- backend: 删除 gpt-5 / 5.1 / 5.1-codex / 5.1-codex-max / 5.1-codex-mini / 5.2-codex / 5.4-nano 的内置映射与 DefaultModels 条目
- backend: normalizeCodexModel 默认兜底由 gpt-5.1 改为 gpt-5.4,gpt-5.3-codex-spark 独立保留映射
- backend: 修复 isOpenAIGPT54Model 与 shouldAutoInjectPromptCacheKeyForCompat 对 claude / gpt-4o 的误判(之前依赖 gpt-5.1 作为非 GPT 族的隐式 sentinel,改后需要显式前缀守卫)
- backend: 清理 billing_service 中已不可达的 fallback 价格与 switch 分支
- frontend: 从白名单、OpenCode 配置、预设映射中移除已下线模型
- 同步更新所有相关单测

Refs: #1758, parallels upstream #1759 but adds downstream guard fixes
2026-04-20 22:01:41 +08:00
IanShaw027
aaf4946b27 fix: normalize pending oauth email lookups 2026-04-20 21:59:03 +08:00
IanShaw027
31d0183d45 fix: normalize repository email lookups 2026-04-20 21:51:57 +08:00
IanShaw027
b309822199 fix: tighten legacy payment provider resolution 2026-04-20 21:46:24 +08:00
IanShaw027
422f60a145 fix: normalize legacy wechat auth identity keys 2026-04-20 21:42:35 +08:00
IanShaw027
f65429145e fix: route legacy linuxdo users to account binding 2026-04-20 21:31:05 +08:00
IanShaw027
5adefb466b fix: finalize oauth identity bindings 2026-04-20 21:24:33 +08:00
IanShaw027
bdcd3d87e5 fix: resolve unique legacy payment providers 2026-04-20 21:09:38 +08:00
IanShaw027
32059ae9d5 fix: backfill email identities on successful login 2026-04-20 20:58:19 +08:00
IanShaw027
9bebf1c1a6 feat: resolve payment results by resume token 2026-04-20 20:53:46 +08:00
IanShaw027
c0b24aefba feat: snapshot payment provider keys on orders 2026-04-20 20:47:14 +08:00
IanShaw027
e3f69e0246 fix: tighten webhook provider resolution 2026-04-20 20:42:01 +08:00
IanShaw027
7c7924e9fa fix: guard payment fulfillment provider mismatch 2026-04-20 20:31:19 +08:00
IanShaw027
97c9b992cb fix: require wechat unionid for oauth identity 2026-04-20 20:27:15 +08:00
erio
40d4e167cd feat(payment): i18n payment error codes and label localization
Pairs with the backend structured payment errors (reason + metadata). The
frontend now maps reason codes to localized messages with metadata as
interpolation variables, and automatically localizes raw config-field names
(e.g. "certSerial" → "证书序列号") using the existing UI-label i18n
namespace.

- frontend/src/utils/apiError.ts
  - extractApiErrorCode now prefers the string `reason` over the numeric HTTP
    `code`; reason is granular enough to drive i18n lookup, HTTP code is not.
  - New extractApiErrorMetadata to pull interpolation params off the error.
  - New extractI18nErrorMessage(err, t, namespace, fallback): looks up
    `<namespace>.<REASON>` in i18n and substitutes metadata. Before
    substitution, `metadata.key` and `metadata.keys` (slash-joined) are
    re-translated through `admin.settings.payment.field_<key>` so users see
    "缺少必填项:证书序列号" instead of "缺少必填项:certSerial".

- frontend/src/i18n/locales/{zh,en}.ts
  - Add payment.errors entries for every structured reason code returned by
    the backend (PAYMENT_DISABLED, INVALID_AMOUNT, TOO_MANY_PENDING,
    DAILY_LIMIT_EXCEEDED, NO_AVAILABLE_INSTANCE, PAYMENT_PROVIDER_MISCONFIGURED,
    WXPAY_CONFIG_MISSING_KEY / INVALID_KEY_LENGTH / INVALID_KEY, NOT_FOUND,
    FORBIDDEN, CONFLICT, INVALID_ORDER_TYPE, INVALID_STATUS,
    BALANCE_NOT_ENOUGH, REFUND_AMOUNT_EXCEEDED, REFUND_FAILED, and more),
    with placeholders for template variables.

- 13 payment-related Vue files
  - Migrate catch-block error reporting from extractApiErrorMessage to
    extractI18nErrorMessage(err, t, 'payment.errors', fallback).
  - Remove the ad-hoc paymentErrorMap computed in SettingsView.vue, which the
    new helper supersedes (it reads i18n directly via t).

- frontend/src/components/payment/providerConfig.ts
  - wxpay: publicKey and publicKeyId are now required (was optional), matching
    the pubkey-only verifier direction; certSerial is already required.

This PR is drop-in safe: reason-preferring extractApiErrorCode is backward
compatible with callers that pass their own i18nMap, and error codes missing
from i18n fall back to the existing message-based path.
2026-04-20 20:23:16 +08:00
IanShaw027
58b2cc380f test: harden payment result resume flow 2026-04-20 20:22:00 +08:00
erio
20a4e41872 feat(monitor): admin channel monitor MVP with SSRF protection and batch aggregation
新增 admin「渠道监控」模块(参考 BingZi-233/check-cx),独立于现有 Channel 体系。
admin 配置 + 后台定时调用上游 LLM chat completions 健康检查 + 所有登录用户只读可见。

后端:
- ent: channel_monitor + channel_monitor_history(AES-256-GCM 加密 api_key)
- service 按职责拆分:service/aggregator/validate/checker/runner/ssrf
- provider strategy map 替代 switch(openai/anthropic/gemini)
- repository batch 聚合(ListLatestForMonitorIDs + ComputeAvailabilityForMonitors)消除 N+1
- runner: ticker(5s) + pond worker pool(5) + inFlight 防并发 + TrySubmit 防雪崩
  + 凌晨 3 点 cron 清理 30 天历史
- SSRF 防护:强制 https + 私网/loopback/云元数据 IP 拒绝(127/8、10/8、172.16/12、
  192.168/16、169.254/16、100.64/10、::1、fc00::/7、fe80::/10)+ DialContext
  在 socket 层防 DNS rebinding
- API key sanitize:擦除 url.Error 与上游响应 body 中的 sk-/sk-ant-/AIza/JWT 模式
- APIKeyDecryptFailed 标志位 + 单 monitor 路径检测,避免空 key 调用上游

handler:
- admin: CRUD + 手动触发 + 历史接口(api_key 脱敏)
- user: 只读列表 + 状态详情(去除 api_key/endpoint)
- ParseChannelMonitorID 共用 + dto.ChannelMonitorExtraModelStatus 共用

前端:
- 路由 /admin/channels/{pricing,monitor} + /monitor(用户只读)
- AppSidebar 父项 expandOnly 支持
- ChannelMonitorView 拆为 8 个子组件 + ChannelStatusView 拆出 detail dialog
- composables/useChannelMonitorFormat + constants/channelMonitor 共享
- i18n monitorCommon namespace 消除 admin/user 两 view 重复

合规:所有文件符合 CLAUDE.md(Go ≤ 500 行 / Vue ≤ 300 行 / 函数 ≤ 30 行)
CI: go build / gofmt / golangci-lint(0 issues) / make test-unit / pnpm build 全绿
2026-04-20 20:21:02 +08:00
IanShaw027
b51bc7ee24 feat: wire payment return url payloads 2026-04-20 20:19:23 +08:00
IanShaw027
7826e9880c feat: support linuxdo pending bind 2fa callback 2026-04-20 19:53:22 +08:00
IanShaw027
fb6204ea8b feat: apply oauth first-bind defaults and pending bind 2fa 2026-04-20 19:53:22 +08:00
erio
79192cf65b feat(payment): harden wxpay config validation with structured errors
Motivation: platform-certificate mode is being phased out by WeChat (2024-10+,
newly-provisioned merchants already cannot download platform certificates at
all), and wxpay config errors currently surface only when an order is being
created — admins have no feedback at save time. Also, errors were returned as
natural-language strings, leaving the frontend no way to localize them.

Changes:

- backend/internal/payment/provider/wxpay.go
  - Replace fmt.Errorf with structured infraerrors.BadRequest errors:
    - WXPAY_CONFIG_MISSING_KEY    (metadata: key)
    - WXPAY_CONFIG_INVALID_KEY_LENGTH  (metadata: key, expected, actual)
    - WXPAY_CONFIG_INVALID_KEY    (metadata: key) for malformed PEMs
  - Parse privateKey and publicKey PEMs in NewWxpay so malformed keys fail
    at save time instead of at order creation.
  - Keep the pubkey verifier (NewSHA256WithRSAPubkeyVerifier) as the single
    supported verifier; no more loadKeyPair helper.

- backend/internal/service/payment_order.go invokeProvider
  - If CreateProvider or CreatePayment returns a structured ApplicationError,
    pass it through (optionally enriching metadata with provider/instance_id)
    instead of wrapping it as generic PAYMENT_GATEWAY_ERROR — so clients see
    the actual reason code (e.g. WXPAY_CONFIG_MISSING_KEY) and can localize.
  - Simplify a few messages (TOO_MANY_PENDING, DAILY_LIMIT_EXCEEDED,
    PAYMENT_GATEWAY_ERROR, NO_AVAILABLE_INSTANCE) to keyword form with
    metadata for template variables.

- backend/internal/service/payment_config_providers.go
  - New helper validateProviderConfig calls provider.CreateProvider at save
    time. Enabled instances are validated on both Create and Update so admins
    see config errors immediately in the dialog, not later at order creation.
  - Disabled instances are not validated (half-filled drafts are allowed).

- backend/internal/payment/provider/wxpay_test.go
  - Add generateTestKeyPair helper that produces valid RSA-2048 PKCS8/PKIX
    PEMs per test, used by the valid-config baseline (prior fake strings no
    longer pass the eager PEM check).
  - Cover each structured-error branch (missing/invalid-length/malformed PEM).
2026-04-20 19:49:45 +08:00
IanShaw027
6ea3f42e2f feat: add oauth callback email binding ui 2026-04-20 19:30:19 +08:00
IanShaw027
6a75bd77e3 feat: add pending oauth email onboarding flow 2026-04-20 19:30:09 +08:00
IanShaw027
d47580a144 test: pin email signup defaults in register tests 2026-04-20 18:42:28 +08:00
IanShaw027
0353c3870f test: update user service stubs for identity summaries 2026-04-20 18:40:34 +08:00
IanShaw027
4e0e691546 feat: apply auth source signup defaults 2026-04-20 18:39:53 +08:00
IanShaw027
c6d8592484 feat: add profile auth identity binding flow 2026-04-20 18:28:44 +08:00
IanShaw027
13d9780df4 feat: expose user activity timestamps in admin list 2026-04-20 17:48:30 +08:00
IanShaw027
e9de839d87 feat: rebuild auth identity foundation flow 2026-04-20 17:39:57 +08:00
IanShaw027
fbd0a2e3c4 feat: carry suggested third-party profile through pending oauth 2026-04-20 16:27:23 +08:00
IanShaw027
d3d4267731 fix: harden oidc callback security 2026-04-20 16:23:42 +08:00
IanShaw027
584ded2182 docs: harden auth identity payment design 2026-04-20 14:41:12 +08:00
IanShaw027
b6751f7ebc docs: add auth identity implementation plan 2026-04-20 13:47:00 +08:00
IanShaw027
721d7ab3ab docs: add audit synthesis to auth identity spec 2026-04-20 13:40:31 +08:00
IanShaw027
e01c1eaceb docs: add auth identity payment foundation design spec 2026-04-20 13:18:30 +08:00
shaw
23def40bc5 chore: change license from MIT to LGPL v3.0 2026-04-19 22:06:04 +08:00
Wesley Liddick
f5ee93796d Merge pull request #1753 from touwaeriol/feat/fix-orphaned-scheduled-tests
fix: delete scheduled test plans when account is deleted
2026-04-19 21:14:23 +08:00
Wesley Liddick
e8be434498 Merge pull request #1752 from touwaeriol/fix/quota-exceeded-scheduling
fix(account): prevent quota-exceeded API key/Bedrock accounts from being scheduled
2026-04-19 21:14:06 +08:00
Wesley Liddick
061fd48df7 Merge pull request #1749 from touwaeriol/fix/xhigh-reasoning-effort
fix: support xhigh reasoning effort in usage records
2026-04-19 21:13:45 +08:00
erio
6579f28b64 fix: delete scheduled test plans when account is deleted
Accounts use soft-delete (setting deleted_at), so PostgreSQL's
ON DELETE CASCADE on scheduled_test_plans.account_id never fires.
Add plan deletion to the existing account deletion transaction
to ensure atomicity.

Closes Wei-Shaw/sub2api#1728
2026-04-19 20:38:57 +08:00
erio
258fd145ff fix(account): prevent quota-exceeded API key/Bedrock accounts from being scheduled
Add quota exceeded check to IsSchedulable() and refactor
shouldClearStickySession to delegate to IsSchedulable(), eliminating
duplicated logic and fixing missed overload/rate-limit/expired checks.
Frontend displays quota exceeded status independently via quota fields.
2026-04-19 18:45:04 +08:00
erio
6530776a62 fix: support xhigh reasoning effort in usage records for Claude Messages API
Closes #1732
2026-04-19 18:05:25 +08:00
Wesley Liddick
51af8df31d Merge pull request #1731 from touwaeriol/fix/rate-billing-autofill-response-limit
fix: subscription billing, alipay redirect + H5, payment secrets, 128MB response limit
2026-04-19 09:43:24 +08:00
erio
235f710853 feat(payment): redact provider secrets in admin config API
Admin GET /api/v1/admin/payment/providers previously returned every
config value — including privateKey / apiV3Key / secretKey etc. —
verbatim. Any future XSS on the admin UI would hand attackers the
full set of production payment credentials, and the plaintext values
sat unnecessarily in browser memory for every operator.

Treat those fields as write-only from the admin surface:

- decryptAndMaskConfig() strips sensitive keys from the GET response.
  The authoritative list is an explicit per-provider registry that
  mirrors the frontend's PROVIDER_CONFIG_FIELDS sensitive flag:
    alipay   → privateKey, publicKey, alipayPublicKey
    wxpay    → privateKey, apiV3Key, publicKey
    stripe   → secretKey, webhookSecret (publishableKey stays plain)
    easypay  → pkey
  Payment runtime still reads the full config via decryptConfig, so
  nothing at the gateway changes.

- mergeConfig() treats an empty value for a sensitive key as "leave
  unchanged" — the admin UI omits unchanged secrets so operators can
  tweak non-sensitive settings without re-entering credentials.

- Admin dialog (PaymentProviderDialog.vue):
  * secret inputs get autocomplete="new-password", data-1p-ignore,
    data-lpignore and data-bwignore so password managers do not
    offer to save provider credentials
  * in edit mode the required-field check skips sensitive fields
    (empty is the "keep existing" signal) and the placeholder shows
    "leave empty to keep" instead of the default example value
  * create mode still requires every non-optional field, including
    secrets, since there is nothing to preserve

- Unit test renamed to TestIsSensitiveProviderConfigField, covers
  the per-provider registry and specifically asserts that Stripe's
  publishableKey is NOT treated as a secret.
2026-04-19 02:22:53 +08:00
erio
c3cb0280ef fix(payment): alipay redirect-only flow, H5 detection and popup sizing
The native Alipay provider previously tried to embed the payment page
URL into a QR code on the client — the URL is not a scannable payload
so the QR never worked. Merchants also hit a H5 detection mismatch
whenever the backend UA sniffer missed iPadOS 13+ or embedded browsers,
and the popup window was too small for Alipay's standard checkout
layout (QR + account-login panel on the right), forcing the user to
scroll horizontally and vertically.

Changes:

Backend
- alipay.go: drop QR-on-URL path. Use redirect-only flow —
  alipay.trade.page.pay for PC (returns a gateway URL the browser
  opens in a new window) and alipay.trade.wap.pay for H5 (returns a
  URL the browser jumps to). Both flows produce pages on
  openapi.alipaydev.com / excashier.alipay.com; the client never
  renders a QR itself.
- payment_handler.go: add optional is_mobile bool to
  CreateOrderRequest so the frontend can declare the device
  explicitly. Server still falls back to UA sniffing when absent.

Frontend
- types/payment.ts, PaymentView.vue: declare is_mobile in
  CreateOrderRequest and pass the computed isMobileDevice() value.
- providerConfig.ts: replace the two fixed POPUP_WINDOW_FEATURES
  constants with getPaymentPopupFeatures(), which prefers 1250×900
  (Alipay's checkout footprint), clamps to window.screen.avail* and
  centers the popup so it never overflows on smaller laptops.
- PaymentQRDialog.vue, PaymentStatusPanel.vue, StripePaymentInline.vue,
  PaymentView.vue: use the new helper at all popup call sites.
2026-04-19 02:22:41 +08:00
Wesley Liddick
6c73b6212c Merge pull request #1734 from touwaeriol/docs/payment-recommend-kyren-topup
docs(payment): add Kyren Topup as international EasyPay provider option
2026-04-18 18:33:14 +08:00
erio
0c538a584f docs: note Kyren Topup $200 account fee waived via referral link 2026-04-18 14:48:47 +08:00
erio
6ae1cc8f3f docs: use 易支付 in Chinese coexistence note 2026-04-18 14:45:25 +08:00
erio
37123cef8f docs(payment): add Kyren Topup as international EasyPay provider option
Restructure the EasyPay recommendation block to present two options side
by side so users can pick by funding channel and settlement currency:

- Domestic / CNY — ZPay: official Alipay/WeChat API with 1.6% fee and
  T+1 automatic settlement (existing recommendation, expanded with fee
  and settlement details).
- International / USDT or USD — Kyren Topup (https://kyren.top): global
  payment stack supporting WeChat Pay and Alipay with local-currency
  checkout, USD settlement, and USDT/USD withdrawal. Fees: WeChat 2%,
  Alipay 2.5%, withdrawal 0.1% ($40 min / $150 max). Fills the gap for
  users who cannot use domestic Chinese channels or tolerate Stripe's
  6%+ fees.

Both recommendations share a single disclaimer at the end. The Chinese
heading uses "易支付" while the English one keeps "EasyPay".
2026-04-18 14:42:55 +08:00
erio
61a008f7e4 chore(payment): mark legacy AES ciphertext fallback as deprecated
明文 JSON 已经是新写入的默认格式;保留 AES 密文读取仅为兼容迁移期间的旧
记录,一旦所有部署通过管理后台重存过一次即可删除。标记为 deprecated 并加
TODO,几个版本后统一清理掉:payment.Encrypt / payment.Decrypt、两处
decryptConfig 的 AES 分支、PaymentConfigService.encryptionKey 和
DefaultLoadBalancer.encryptionKey 字段。
2026-04-17 23:24:27 +08:00
erio
bf0bbe0be7 feat(gateway): raise upstream response read limit 8MB -> 128MB (configurable)
图片生成 API 返回的 base64 内联图响应经常超过 8MB 单次读取上限,被
ReadUpstreamResponseBody 拦截成 502 upstream_error。

单张 4K PNG base64 最坏约 67MB,多张候选图或 imageSize=4K 的 image_generation
一次请求能轻松到 30MB+。把默认上限提到 128MB 能覆盖 2-3 张 4K 图,相对
请求体上限 256MB 仍有缓冲;同时抽出 config.DefaultUpstreamResponseReadMaxBytes
共享常量,viper 默认值和 service 层兜底共用,消除两处同步魔法数字。

仍可通过 gateway.upstream_response_read_max_bytes 配置项覆盖。
2026-04-17 22:07:15 +08:00
erio
df57d2776b fix(billing): reject rate_multiplier <= 0 on save; clamp negatives to 0 in compute
分组倍率和用户专属倍率在保存时没有校验,0 会触发计费层的 `<=0 → 1.0`
防御条款,结果订阅/余额分组按标准价扣费;完全是沉默地绕过了业务规则。

- 保存校验(admin_service):CreateGroup / UpdateGroup / BatchSetGroupRateMultipliers /
  UpdateUser.SyncUserGroupRates 全部要求 > 0
- 计算层(billing_service):三处 `<=0 → 1.0` 改为 `<0 → 0`;负数按 0 结算,
  避免配置异常被静默按 1x 收费
- 前端:分组倍率 / 用户专属倍率输入 min 统一到 0.001
- 删除未使用的 IsFreeSubscription 方法

测试:新增 billing_service_rate_multiplier_test.go 端到端验证;更新原有锁定
旧 `<=0 → 1.0` 行为的测试。
2026-04-17 22:06:32 +08:00
erio
948d8e6d02 fix(admin): prevent browser password manager from autofilling account API key
Chrome's password manager matched the apikey-type account's Base URL + API Key
inputs as a login form and autofilled the last saved password by domain, so
editing a Gemini account could overwrite its apikey with a Claude key that
shared the same Base URL. Add autocomplete="new-password" plus data-*-ignore
attributes for 1Password / LastPass / Bitwarden to opt the field out of every
major password manager's autofill.
2026-04-17 22:06:32 +08:00
erio
44cdef7934 fix(usage): subscription billing honours group rate multiplier
Subscription-mode billing was consuming quota at TotalCost (raw) instead of
ActualCost (TotalCost * RateMultiplier), so per-group rate multipliers —
including free subscriptions (multiplier = 0) — were silently ignored.
Switch the three subscription cost writes in buildUsageBillingCommand,
finalizePostUsageBilling, and the legacy postUsageBilling fallback to
ActualCost, and add a table-driven test covering 2x / 0.5x / free multipliers
plus a balance-mode regression check.
2026-04-17 22:06:32 +08:00
erio
fd0c9a1305 fix(payment): store provider config as plaintext JSON with legacy ciphertext fallback
Without TOTP_ENCRYPTION_KEY, saved payment configs were lost on restart because
the AES round-trip failed silently. Write new records as plaintext JSON; read
path tries JSON first, falls back to legacy AES decrypt when a key is present,
and treats unreadable values as empty so admins can re-enter them via the UI.
2026-04-17 22:06:32 +08:00
github-actions[bot]
6cfdf4ec05 chore: sync VERSION to 0.1.114 [skip ci] 2026-04-17 02:51:18 +00:00
Wesley Liddick
358ff6a608 Merge pull request #1683 from FjlI5/dev-main
fix:修复上游账号为OpenAI API key时Claude Code调用缓存率低的问题
2026-04-17 10:28:12 +08:00
Wesley Liddick
41fbdba104 Merge pull request #1687 from touwaeriol/refactor/upstream-response-limit-dedup
refactor: extract ReadUpstreamResponseBody to deduplicate response read + too-large handling
2026-04-17 10:19:14 +08:00
Wesley Liddick
c22d11cedd Merge pull request #1702 from StarryKira/fix/outbox-watermark-context-dedup-1691
fix: fix outbox watermark context expiry and add in-batch group rebuild dedup
2026-04-17 10:18:56 +08:00
shaw
5d586a9f3a fix: 上游返回 KYC 身份验证要求时停止账号调度 2026-04-17 10:17:50 +08:00
shaw
a789c8c4c7 feat: 支持opus-4.7 2026-04-17 09:37:25 +08:00
Elysia
697c41a3f6 fix: create fresh context per watermark write retry attempt
Each retry in the SetOutboxWatermark loop now gets its own 5s context.
Previously a shared context could already be expired when the second or
third attempt ran, making the retries pointless.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:41:40 +08:00
Elysia
e44baa1094 fix: fix outbox watermark context expiry and add in-batch group rebuild dedup
Fixes #1691

- pollOutbox() reused a 10s context for SetOutboxWatermark after event
  processing could take much longer, causing "outbox watermark write
  failed: context deadline exceeded". The watermark never advanced so
  the same 200 events were reprocessed every poll cycle, spiking CPU.
  Now uses an independent 5s context with up to 3 retries (200ms apart).

- When multiple Codex accounts sharing the same 21-22 groups are all
  rate-limited in quick succession, each account_changed event triggered
  redundant bucket rebuild attempts for the same groups. Introduce
  batchSeenKey{groupID, platform} and thread a seen map through the
  handler chain; rebuildBucketsForPlatform skips (group, platform) pairs
  already rebuilt within the same poll batch (~80% fewer rebuild calls in
  the 5-accounts-same-groups scenario).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:09:40 +08:00
Wesley Liddick
e6e73b4f52 Merge pull request #1690 from KnowSky404/fix/ws-codex-scheduler-cache-1662
fix: preserve openai ws flags in scheduler cache
2026-04-16 17:21:32 +08:00
shaw
7ea8e7e667 chore: update sponsors 2026-04-16 17:19:32 +08:00
shaw
a55ead5ea8 chore: remove empty dir Antigravity-Manager 2026-04-16 16:42:40 +08:00
KnowSky404
836092a666 fix: restore ctx pool ws mode option in account ui 2026-04-16 02:13:04 +00:00
KnowSky404
3944b3d216 fix: preserve openai ws flags in scheduler cache 2026-04-16 02:01:50 +00:00
erio
10699eeb34 refactor: extract ReadUpstreamResponseBody to deduplicate upstream response read + too-large error handling
Consolidates 9 call sites of resolveUpstreamResponseReadLimit + readUpstreamResponseBodyLimited + ErrUpstreamResponseBodyTooLarge error handling into a single ReadUpstreamResponseBody function with TooLargeWriter callback for API-format-specific error responses (Anthropic, OpenAI, countTokens).
2026-04-16 01:53:22 +08:00
fjl5
6c89d8d35c add prompt_cache_key injection for messages→responses 2026-04-15 23:56:56 +08:00
github-actions[bot]
be7551b9f4 chore: sync VERSION to 0.1.113 [skip ci] 2026-04-15 09:34:24 +00:00
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
shaw
074bd0dfda fix: 临时移除context-1m-2025-08-07以确保避免sonnet1m触发429 2026-02-18 18:41:30 +08:00
shaw
b41fa5e15f feat: 前端新增sonnet4.6快捷映射按钮 2026-02-18 17:06:37 +08:00
Wesley Liddick
beceb45d23 Merge pull request #591 from miraserver/main
feat: add Cache TTL Override per account
2026-02-18 15:59:25 +08:00
Wesley Liddick
9450edf462 Merge pull request #589 from 0-don/fix/strip-unsupported-codex-params
fix: strip unsupported parameters from Codex model requests
2026-02-18 15:58:05 +08:00
Wesley Liddick
785a7397f8 Merge pull request #579 from KortanZ/main
fix: accept openai x-stainless-* header to fix CORS error
2026-02-18 15:57:44 +08:00
John Doe
3d1f03c286 feat: add Cache TTL Override per account + bump VERSION to 0.1.83
- Account-level cache TTL override: rewrite Anthropic cache_creation
  token classification (5m↔1h) in streaming/non-streaming responses
- New DB field cache_ttl_overridden in usage_log for billing tracking
- Migration 055_add_cache_ttl_overridden
- Frontend: CacheTTL override toggle in account create/edit modals
- Ent schema regenerated for new usage_log fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:19:24 +03:00
0-don
8ff40f52e0 fix: remove unsupported parameters from Codex model requests 2026-02-17 00:06:32 +01: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
shaw
a817cafe3d feat: 区分 Anthropic 5m/1h 缓存创建 token 的差异化计费
Anthropic API 的 cache_creation 对象区分了 ephemeral_5m 和 ephemeral_1h
两种缓存创建 token,1h 单价远高于 5m(如 claude-3-5-haiku: 5m=$1/MTok,
1h=$6/MTok)。此前系统统一按 5m 单价计费,导致计费偏低。

后端:
- pricing_service: 加载 LiteLLM 的 cache_creation_input_token_cost_above_1hr
- billing_service: GetModelPricing 启用分类计费(安全守卫 1h>5m),
  CalculateCost 按 5m/1h 分别计费,无明细时回退到 5m 单价
- gateway_service: parseSSEUsage/handleNonStreamingResponse 用 gjson
  提取嵌套 cache_creation 对象的 ephemeral_5m/1h_input_tokens
- antigravity_gateway_service: extractSSEUsage/extractClaudeUsage 同步提取
- usage_log: 修复 GORM column tag 确保写入正确的数据库列
- 新增迁移 054: 删除 GORM 自动生成的重复列

前端:
- 使用记录 tooltip 展示 5m/1h 缓存创建明细(带彩色 badge 区分)
- 表格单元格缓存写入数值旁显示 1h 标识
2026-02-14 18:15:35 +08:00
Kortan
ab14df043a fix: accept openai x-stainless-* header to fix CORS error 2026-02-14 16:52:07 +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
Wesley Liddick
2857fa2ef7 Merge pull request #577 from qwIvan/patch-1
docker-compose.yml add PGDATA env
2026-02-14 00:24:23 +08:00
shaw
e681431454 fix: Anthropic 429 限流使用精确的窗口重置时间而非聚合最大值
当账号仅触发 5h 窗口限流时,旧逻辑从聚合头
anthropic-ratelimit-unified-reset 读取重置时间,该值为所有窗口的
最大值(即 7d 重置时间),导致账号被标记为不可调度约 6 天。

新增 calculateAnthropic429ResetTime 函数,解析 Anthropic 的
per-window 头(5h-utilization/reset、7d-utilization/reset、
surpassed-threshold),判断实际触发的窗口并使用对应的重置时间:
- 仅 5h 超标 → 使用 5h-reset(约 5 小时)
- 仅 7d 超标 → 使用 7d-reset
- 两者均超标 → 使用 7d-reset(较长冷却)
- per-window 头不存在 → 回退到聚合头(向后兼容)
2026-02-14 00:21:56 +08:00
yang chanfa
5b568aa9d4 docker-compose.yml add PGDATA env
`docker-compose.yml` also have to define the `PGDATA` env
2026-02-13 23:47:52 +08:00
Wesley Liddick
471943269c Merge pull request #573 from wucm667/fix/stat-card-value-overflow
修复:StatCard 数值溢出问题
2026-02-13 20:29:12 +08:00
Wesley Liddick
28a5e2f0e6 Merge pull request #570 from wucm667/fix/sidebar-logo-load-flicker
fix: 修复侧边栏 Logo 加载时的闪烁问题
2026-02-13 20:29:01 +08:00
Wesley Liddick
b4c22ce6ce Merge pull request #561 from james-6-23/main
feat(admin): Add group filtering for account listings
2026-02-13 20:23:56 +08:00
shaw
5248097f90 fix: 修复 gosec 配置文件格式错误导致 CI 失败
gosec -conf 只支持 JSON 格式,将 .gosec.yaml 转换为 .gosec.json
2026-02-13 20:12:50 +08:00
Wesley Liddick
8e2c22d0bd Merge pull request #571 from wucm667/chore/configure-gosec-exclusions
chore: 配置 gosec 排除规则
2026-02-13 20:05:02 +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
wucm667
be56a282f2 修复:StatCard 数值溢出问题
- 添加 title 属性,鼠标悬停时显示完整数值
- 添加 truncate 类防止数值溢出
- 优化长数值的显示效果
2026-02-13 15:59:30 +08:00
yangjianbo
2459eafb71 feat: 完善日志 2026-02-13 13:35:47 +08:00
yangjianbo
ed681d0830 feat: 整理 2026-02-13 12:49:08 +08:00
wucm667
5f4eb9f9d0 chore: 配置 gosec 排除规则
- 新增 backend/.gosec.yaml 配置文件,排除 G704 (SSRF) 检查
- 更新 security-scan.yml workflow,使用 gosec 配置文件
- 原因:作为 API 网关平台,需要代理请求到配置的上游服务,所有上游 URL 来自管理员配置而非用户输入
2026-02-13 10:48:33 +08:00
wucm667
d1cd5c0a73 Update frontend/src/components/layout/AppSidebar.vue
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 10:29:26 +08:00
wucm667
5429c74c10 fix: 修复侧边栏 Logo 加载时的闪烁问题
- 添加 settingsLoaded 条件判断,确保公共设置加载完成后再显示 Logo
- 避免在设置未加载时显示默认 Logo 造成的闪烁效果
2026-02-13 10:21:17 +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
kyx236
fe1d46a8ea feat(admin): Add group filtering for account listings
- Add groupID parameter to ListAccounts and ListWithFilters methods
- Implement account filtering by group ID in repository query
- Add group query parameter parsing in account handler
- Update all ListAccounts/ListWithFilters call sites with groupID parameter
- Add group filter UI component to AccountTableFilters
- Add i18n translations for group filter label in English and Chinese
- Update API contract and test stubs to reflect new signature
- Enable filtering accounts by their assigned groups in admin panel
2026-02-12 03:47:06 +08:00
yangjianbo
a88bb8684f fix(openai): 修复 OAuth 透传流式断开与压缩头问题
- 透传流式在客户端断开后继续 drain 上游并解析 usage,避免计费信息丢失

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

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

- 补充回归测试:请求头阻断与断流后 usage 采集
2026-02-11 22:17:38 +08:00
Wesley Liddick
c7b42148a5 Merge pull request #559 from wucm667/fix/auth-page-logo
fix: 修复登录/注册页面自定义 Logo 不显示及闪烁问题
2026-02-11 20:01:03 +08:00
Wesley Liddick
bc1abb6a23 Merge pull request #557 from james-6-23/main
feat(admin): 为账户和兑换码新增邮箱搜索及限流过滤功能
2026-02-11 20:00:43 +08:00
Wesley Liddick
d307d48def Merge pull request #551 from SilentFlower/opus4.6-think
[UPDATE] 增强 Claude Thinking 模式支持与 Opus 4.6 动态预算适配
2026-02-11 20:00:22 +08:00
Wesley Liddick
1bb40084fc Merge pull request #550 from Tian-orz/feat/antigravity-refresh-token-import
feat(antigravity): 支持 Refresh Token 批量导入创建 OAuth 账号
2026-02-11 19:59:52 +08:00
Wesley Liddick
8f0efa16ca Merge pull request #555 from sususu98/fix/gemini-thoughts-token-billing
fix: include Gemini thoughtsTokenCount in output token billing
2026-02-11 19:53:43 +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
wucm667
ef2c35dbb1 🐛 fix: 修复登录/注册页面自定义 Logo 不显示及闪烁问题
- sanitizeUrl 新增 allowDataUrl 选项,支持 data:image/ 格式的 base64 图片 URL
- AuthLayout 改用 appStore 缓存数据,避免重复 API 请求和默认 Logo 闪烁

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:04:57 +08:00
kyx236
04a1a7c2b5 feat(admin): Add email search and rate limit filtering for accounts and redeem codes
- Add used_by_email column to redeem code export CSV for better user identification
- Implement rate_limited status filter in account listing with RateLimitResetAt check
- Extend redeem code search to include user email in addition to code matching
- Add API key search capability to user listing filters
- Display user email in redeem code table used_by column for improved visibility
- Update search placeholders in UI to reflect expanded search capabilities (email, username, notes, API key)
- Improve Chinese and English localization strings for search hints
2026-02-11 16:39:42 +08:00
sususu98
d21d70a5cf fix: include Gemini thoughtsTokenCount in output token billing
Gemini 2.5 Pro/Flash thinking models return thoughtsTokenCount separately
from candidatesTokenCount in usageMetadata, but this field was not parsed
or included in billing calculations, causing thinking tokens to be
unbilled.

- Add ThoughtsTokenCount field to GeminiUsageMetadata struct
- Include thoughtsTokenCount in OutputTokens across all 3 Gemini usage
  parsing paths (non-streaming, streaming, compat layer)
- Add tests covering thinking token scenarios

Closes #554
2026-02-11 15:41:54 +08:00
SilentFlower
e73b778d2b Merge branch 'main' into opus4.6-think 2026-02-11 13:56:30 +08:00
Wesley Liddick
723102766b Merge pull request #553 from Edric-Li/feat/antigravity-onboard-projectid
feat(antigravity): 添加 onboardUser 支持,修复 project_id 缺失问题
2026-02-11 13:52:44 +08:00
Edric Li
a4a46a8618 feat(antigravity): 添加 onboardUser 支持并修复 project_id 补齐逻辑
- 新增 OnboardUser API 客户端方法,支持账号 onboarding 获取 project_id
- loadProjectIDWithRetry 增加 onboard 回退:LoadCodeAssist 未返回 project_id 时自动触发 onboarding
- GetAccessToken 中 project_id 补齐改用轻量 FillProjectID 替代全量 RefreshAccountToken
- 补齐逻辑增加 5 分钟冷却机制,防止频繁重试
- OnboardUser 轮询等待改为 context 感知,支持提前取消
- 提取 mergeCredentials 辅助方法消除重复代码
- 新增 extractProjectIDFromOnboardResponse 和 resolveDefaultTierID 单元测试
2026-02-11 13:41:55 +08:00
SilentFlower
6ae82e04d5 [UPDATE] 优化思考预算逻辑与代码结构
🧠 refactor(antigravity): 完善 thinking 预算分配策略并重构工具构建逻辑
2026-02-11 10:39:54 +08:00
SilentFlower
19cca11e00 [UPDATE] 增强 Claude Thinking 模式支持与 Opus 4.6 动态预算适配
 feat(antigravity): 支持 thinking adaptive 类型并适配 Opus 4.6 动态预算
🧪 test(gateway): 增加 thinking 模式解析与签名块过滤的边界用例测试
2026-02-11 10:31:16 +08:00
Tian
c8f87a9c92 feat(antigravity): 支持 Refresh Token 批量导入创建 OAuth 账号
后端新增 ValidateRefreshToken service 方法和 POST /oauth/refresh-token 端点,
前端新增 API/Composable/UI 集成,OAuthAuthorizationFlow i18n 动态化,
支持在 Antigravity 创建账号时批量粘贴 Refresh Token 自动验证并创建账号。
2026-02-11 01:23:21 +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
Wesley Liddick
ae6fed15cc Merge pull request #548 from Edric-Li/main
feat: 错误处理增强、重试优化与性能改进
2026-02-10 22:46:58 +08:00
yangjianbo
4b309fa8b5 fix(gateway): 优化 ParseGatewayRequest 函数,使用 unsafe 提高性能并增加 JSON 校验 2026-02-10 22:12:24 +08:00
Edric Li
378e476e48 fix: 修复 CI 检查失败
- gofmt: 修复 error_passthrough_service.go 格式问题
- errcheck: 修复 error_passthrough_runtime_test.go 类型断言未检查
- staticcheck: if-else 改为 switch (gateway_service.go)
- test: 修复两个测试用例错误使用 MODEL_CAPACITY_EXHAUSTED 导致走错路径
2026-02-10 22:08:49 +08:00
Edric Li
2a1067c82b Merge remote-tracking branch 'upstream/main' 2026-02-10 21:52:33 +08:00
Edric Li
a54b81cf74 perf: 错误处理性能优化
- MatchRule 延迟/限制 body ToLower,先用 statusCode 短路,只在需要关键词匹配时转换且限制 8KB
- 预计算规则的小写关键词/平台和 error code set,消除运行时重复 ToLower 和线性扫描
- MODEL_CAPACITY_EXHAUSTED 全局去重,避免并发请求重复重试同一模型
- 503 重试 body 读取限制从 2MB 降至 8KB
- time.After 替换为 time.NewTimer,防止 context 取消时 timer 泄漏
2026-02-10 21:40:31 +08:00
Edric Li
2d4236f76e fix: 修复错误透传规则 skip_monitoring 未生效的问题
- ops_error_logger: status < 400 分支增加 OpsSkipPassthroughKey 检查
- ops_upstream_context: 新增 checkSkipMonitoringForUpstreamEvent,中间重试/故障转移事件也能触发跳过标记
- gateway_handler/openai_gateway_handler/gemini_v1beta_handler: handleFailoverExhausted 匹配规则后设置 OpsSkipPassthroughKey
- antigravity_gateway_service: writeMappedClaudeError 增加 applyErrorPassthroughRule 调用
2026-02-10 20:56:01 +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
Wesley Liddick
84ced1c497 Merge pull request #543 from slovx2/upstream_main
feat(antigravity): 转发与测试支持 daily/prod 单 URL 切换
2026-02-10 14:57:46 +08:00
song
b161312183 test(antigravity): 更新单URL策略下的重试断言 2026-02-10 14:36:09 +08:00
程序猿MT
1dd3158c7e Merge branch 'Wei-Shaw:main' into main 2026-02-10 13:55:51 +08:00
song
1f647b120a feat(antigravity): 转发与测试支持daily/prod单URL切换 2026-02-10 13:51:29 +08:00
Edric Li
7d0a30fa8f merge: sync upstream main (antigravity single-account 503 retry)
合并上游新增的 Antigravity 单账号 503 退避重试机制,
解决与本地 MODEL_CAPACITY_EXHAUSTED 逻辑的冲突,两者共存。
2026-02-10 12:00:21 +08:00
Edric Li
d95e04fd1f feat: 错误透传规则支持 skip_monitoring 跳过运维监控记录
在每条错误透传规则上新增 skip_monitoring 选项,开启后匹配该规则的错误
不会被记录到 ops_error_logs,减少监控噪音。默认关闭,不影响现有规则。
2026-02-10 11:42:39 +08:00
shaw
5dd83d3cf2 fix: 移除特定system以适配新版cc客户端缓存失效的bug 2026-02-10 10:28:34 +08:00
Wesley Liddick
14e1aac9b5 Merge pull request #533 from GuangYiDing/feat/antigravity-single-account-503-retry
feat: Antigravity 单账号分组 503 退避重试机制
2026-02-10 09:59:48 +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
Edric Li
6114f69cca feat: MODEL_CAPACITY_EXHAUSTED 使用固定1s间隔重试60次,不切换账号
MODEL_CAPACITY_EXHAUSTED (503) 表示模型容量不足,所有账号共享同一容量池,
切换账号无意义。改为固定1s间隔重试最多60次,重试耗尽后直接返回上游错误。

- 新增 antigravityModelCapacityRetryMaxAttempts=60 和 antigravityModelCapacityRetryWait=1s
- shouldTriggerAntigravitySmartRetry 新增 isModelCapacityExhausted 返回值
- handleSmartRetry 对 MODEL_CAPACITY_EXHAUSTED 使用独立重试策略
- handleModelRateLimit 对 MODEL_CAPACITY_EXHAUSTED 仅标记 Handled,不设限流
- 重试耗尽后不设置模型限流、不清除粘性会话、不切换账号
2026-02-10 02:03:06 +08:00
Edric Li
d6c2921f2b feat: same-account retry before failover for transient errors
For retryable transient errors (Google 400 "invalid project resource name"
and empty stream responses), retry on the same account up to 2 times
(with 500ms delay) before switching to another account.

- Add RetryableOnSameAccount field to UpstreamFailoverError
- Add same-account retry loop in both Gemini and Claude/OpenAI handler paths
- Move temp-unschedule from service layer to handler layer (only after
  all same-account retries exhausted)
- Reduce temp-unschedule cooldown from 30 minutes to 1 minute
2026-02-10 00:53:54 +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
Edric Li
61c73287dc feat: failover and temp-unschedule on empty stream response
- Empty stream responses now return UpstreamFailoverError instead of
  plain 502, triggering automatic account switching (up to 10 retries)
- Add tempUnscheduleEmptyResponse: accounts returning empty responses
  are temp-unscheduled for 30 minutes
- Apply to both Claude and Gemini non-streaming paths
- Align googleConfigErrorCooldown from 60m to 30m for consistency
2026-02-09 23:25:30 +08:00
Edric Li
89905ec43d feat: failover and temp-unschedule on Google "Invalid project resource name" 400
Google 后端间歇性返回 400 "Invalid project resource name" 错误,
此前该错误直接透传给客户端且不触发账号切换,导致请求失败。

- 在 Antigravity 和 Gemini 两个平台的所有转发路径中,
  精确匹配该错误消息后触发 failover 自动换号重试
- 命中后将账号临时封禁 1 小时,避免反复调度到同一故障账号
- 提取共享函数 isGoogleProjectConfigError / tempUnscheduleGoogleConfigError
  消除跨 Service 的代码重复
2026-02-09 22:48:32 +08:00
shaw
aa4b102108 fix: 移除Antigravity的apikey账户额外的表单 2026-02-09 22:15:14 +08:00
Rose Ding
e4bc35151f test: 添加单账号 503 退避重试机制的单元测试
覆盖 Service 层和 Handler 层的所有新增逻辑:
- isSingleAccountRetry context 标记检查
- handleSmartRetry 中 503 + SingleAccountRetry 分支
- handleSingleAccountRetryInPlace 原地重试逻辑
- antigravityRetryLoop 预检查跳过限流
- sleepAntigravitySingleAccountBackoff 固定延迟退避
- 端到端集成场景验证

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 22:06:06 +08:00
yangjianbo
2bfb16291f fix(unit): 修复 unit tag 测试编译与账号选择用例 2026-02-09 21:35:41 +08:00
Wesley Liddick
56da498b7e Merge pull request #532 from touwaeriol/fix/clear-model-rate-limits
fix: support clearing model-level rate limits from action menu and temp-unsched reset
2026-02-09 20:52:44 +08:00
Wesley Liddick
1bba1a62b1 Merge pull request #531 from touwaeriol/fix/gemini-error-policy-before-retry
fix: Gemini error policy check should precede retry logic
2026-02-09 20:52:32 +08:00
yangjianbo
d367d1cde6 Merge branch 'main' into test-sora 2026-02-09 20:40:09 +08:00
erio
4a84ca9a02 fix: support clearing model-level rate limits from action menu and temp-unsched reset 2026-02-09 20:37:30 +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
erio
a70d37a676 fix: Gemini error policy check should precede retry logic 2026-02-09 19:55:17 +08:00
erio
6892e84ad2 fix: skip rate limiting when custom error codes don't match upstream status
Add ShouldHandleErrorCode guard at the entry of handleGeminiUpstreamError
and AntigravityGatewayService.handleUpstreamError so that accounts with
custom error codes (e.g. [599]) are not rate-limited when the upstream
returns a non-matching status (e.g. 429).
2026-02-09 19:55:05 +08:00
erio
73f455745c feat: ErrorPolicySkipped returns 500 instead of upstream status code
When custom error codes are enabled and the upstream error code is NOT
in the configured list, return HTTP 500 to the client instead of
transparently forwarding the original status code.

Also adds integration test TestCustomErrorCode599 verifying that 429,
500, 503, 401, 403 all return 500 without triggering SetRateLimited
or SetError.
2026-02-09 19:54:54 +08:00
Rose Ding
021abfca18 fix: 单账号分组首次 503 不设模型限流标记,避免后续请求雪崩
单账号 antigravity 分组收到 503 (MODEL_CAPACITY_EXHAUSTED) 时,
原逻辑会设置 ~29s 模型限流标记。由于只有一个账号无法切换,
后续所有新请求在预检查时命中限流 → 几毫秒内直接返回 503,
导致约 30 秒的雪崩窗口。

修复:在 Handler 入口处检查分组是否只有单个 antigravity 账号,
如果是则提前设置 SingleAccountRetry context 标记,让 Service 层
首次 503 就走原地重试逻辑(不设限流标记),避免污染后续请求。
2026-02-09 17:25:36 +08:00
Wesley Liddick
7d66f7ff0d Merge pull request #527 from touwaeriol/fix/group-badge-platform-color
fix: pass platform prop to GroupBadge in GroupSelector
2026-02-09 14:39:51 +08:00
erio
470b37be7e fix: pass platform prop to GroupBadge in GroupSelector
GroupBadge in GroupSelector was missing the platform prop, causing all
group badges in account edit/detail pages to use fallback colors instead
of platform-specific colors (e.g. Claude=orange, Gemini=blue).
2026-02-09 14:33:05 +08:00
Rose Ding
f6cfab9901 feat: 添加 Antigravity 单账号 503 退避重试机制
当分组内只有一个可用账号且上游返回 503 (MODEL_CAPACITY_EXHAUSTED) 时,
不再设置模型限流+切换账号(因为切换回来还是同一个账号),而是在 Service 层
原地等待+重试,避免双重等待问题。

主要变更:
- Handler 层:检测单账号 503 场景,清除排除列表并设置 SingleAccountRetry 标记
- Service 层:新增 handleSingleAccountRetryInPlace 原地重试逻辑
- Service 层:预检查跳过单账号模式下的限流检查
- 新增 ctxkey.SingleAccountRetry 上下文标记
2026-02-09 14:26:01 +08:00
shaw
51572b5da0 chore: update version 2026-02-09 12:00:03 +08:00
Wesley Liddick
91ca28b7e3 Merge pull request #525 from DaydreamCoding/feat/crs_sync_preview_with_select
feat(admin): 新增 CRS 同步预览和账号选择功能
2026-02-09 11:58:51 +08:00
QTom
04cedce9a1 test: 为 stubAccountRepo 添加 ListCRSAccountIDs 方法实现 2026-02-09 11:40:37 +08:00
QTom
5e0d789440 feat(admin): 新增 CRS 同步预览和账号选择功能
- 后端新增 PreviewFromCRS 接口,允许用户先预览 CRS 中的账号
- 后端支持在同步时选择特定账号,不选中的账号将被跳过
- 前端重构 SyncFromCrsModal 为三步向导:输入凭据 → 预览账号 → 执行同步
- 改进表单无障碍性:添加 for/id 关联和 required 属性
- 修复 Back 按钮返回时的状态清理
- 新增 buildSelectedSet 和 shouldCreateAccount 的单元测试
- 完整的向后兼容性:旧客户端不发送 selected_account_ids 时行为不变
2026-02-09 10:39:09 +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
Wesley Liddick
149e4267cd Merge pull request #523 from touwaeriol/feat/antigravity-improvements
feat: Antigravity improvements and scope-to-model rate limiting refactor
2026-02-09 09:38:55 +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
erio
9a479d1b55 fix: resolve CI failures from scope removal refactor
- Fix gofmt alignment in ops_realtime_models.go
- Remove SetAntigravityQuotaScopeLimit mock from api_contract_test.go
- Add UpdateSortOrders mock to mockGroupRepoForGateway
2026-02-09 08:27:14 +08:00
erio
fc095bf054 refactor: replace scope-level rate limiting with model-level rate limiting
Merge functional changes from develop branch:
- Remove AntigravityQuotaScope system (claude/gemini_text/gemini_image)
- Replace with per-model rate limiting using resolveAntigravityModelKey
- Remove model load statistics (IncrModelCallCount/GetModelLoadBatch)
- Simplify account selection to unified priority→load→LRU algorithm
- Remove SetAntigravityQuotaScopeLimit from AccountRepository
- Clean up scope-related UI indicators and API fields
2026-02-09 08:19:01 +08:00
erio
1af06aed96 feat: shuffle accounts within same sort group to prevent thundering herd
Add post-sort shuffle for accounts with identical (priority, loadRate,
lastUsedAt) to break deterministic ordering when concurrent requests
read the same scheduler snapshot. Applies to both Antigravity and
OpenAI scheduling paths, plus the sortAccountsByPriorityAndLastUsed
helper.

Keeps upstream CallCount/ModelLoadInfo scheduling intact; shuffle is
additive and only randomises within equivalent-rank groups.
2026-02-09 07:33:17 +08:00
erio
9236936a55 feat: route AccountTypeUpstream to ForwardUpstream in Forward() entry
Without this routing guard, ForwardUpstream is never called because
Forward() always proceeds with the standard OAuth/cookie flow.
2026-02-09 07:27:10 +08:00
erio
125152460f fix: use upstream retryDelay for rate limit duration instead of fixed default
- In handleSmartRetry, use the actual upstream retryDelay to set model
  rate limit duration instead of always using the 30s default
- Return info.RetryDelay from shouldTriggerAntigravitySmartRetry when
  shouldRateLimitModel=true, so callers know the actual delay
- Extract getDefaultRateLimitDuration() and resolveResetTime() helpers
  to reduce duplication in handleUpstreamError 429 handling
- Improve debug logging with upstream_retry_delay and response body
2026-02-09 07:11:29 +08:00
erio
6d90fb0bc3 feat: detect client disconnect during streaming and continue draining upstream for billing 2026-02-09 07:06:26 +08:00
erio
b889d5017b refactor: replace Trie-based digest session store with flat cache 2026-02-09 07:02:12 +08:00
erio
72b08f9cc5 fix: ensure sticky session failover triggers cache billing exemption 2026-02-09 06:57:07 +08:00
erio
681950dadd feat: add linear delay between Antigravity account failover switches 2026-02-09 06:56:29 +08:00
erio
a67d9337b8 feat: integrate CheckErrorPolicy into Gemini error handling paths 2026-02-09 06:55:45 +08:00
erio
2f1182e8a9 feat: unified error policy for Antigravity + enable custom error codes for Gemini accounts 2026-02-09 06:54:42 +08:00
erio
cbb4d854ab fix: check type assertion in test to satisfy errcheck linter 2026-02-09 06:47:50 +08:00
erio
35598d5648 fix: parse Gemini native request format in ParseGatewayRequest for correct session hash generation
ParseGatewayRequest only parsed Anthropic format (system/messages),
ignoring Gemini native format (systemInstruction/contents). This caused
GenerateSessionHash to produce identical hashes for all Gemini sessions.

Add protocol parameter to ParseGatewayRequest to branch between
Anthropic and Gemini parsing. Update GenerateSessionHash message
traversal to extract text from both formats.
2026-02-09 06:47:22 +08:00
erio
5c76b9e45a fix: prevent sessionHash collision for different users with same messages
Mix SessionContext (ClientIP, UserAgent, APIKeyID) into
GenerateSessionHash 3rd-level fallback to differentiate requests
from different users sending identical content.

Also switch hashContent from SHA256-truncated to XXHash64 for
better performance, and optimize Trie Lua script to match from
longest prefix first.
2026-02-09 06:46:32 +08:00
erio
0b8fea4cb4 fix: clean thoughtSignature for all clients, not just CLI
Previously, thoughtSignature cleanup only applied to Gemini CLI
requests (detected via x-gemini-api-privileged-user-id header or
tmp dir pattern). This caused 400 errors for non-CLI clients when
session cache expired and they sent stale signatures.

Remove the isGeminiCLIRequest guard so all clients benefit from
proactive thoughtSignature cleanup on session binding miss.
2026-02-09 06:45:01 +08:00
Wesley Liddick
5fa93ebdc7 Merge pull request #519 from bayma888/feature/group-sort-order
feat(admin): 新增-分组管理自由拖拽排序功能
2026-02-08 18:00:22 +08:00
bayma888
8aa0aed566 docs: add development guide for team reference
记录项目环境配置、CI 流程、常见坑点和解决方案。
2026-02-08 17:54:03 +08:00
bayma888
2eb32a0ed7 chore: update pnpm-lock.yaml for vue-draggable-plus
CI 的 pnpm install --frozen-lockfile 需要 lock 文件同步更新
2026-02-08 17:10:25 +08:00
bayma888
bac9e2bfd5 feat(admin): add drag-and-drop group sort order
- Add `sort_order` field to groups table with migration
- Add `PUT /api/v1/admin/groups/sort-order` API for batch update
- Implement drag-and-drop UI using vue-draggable-plus
- All queries now order groups by sort_order
- Add i18n support (en/zh) for sort-related UI text
- Update test stubs to satisfy new interface methods
2026-02-08 16:53:45 +08:00
shaw
e4d74ae11d feat(ui): 用户列表页显示当前并发数
优化 /admin/users 页面的并发数列,显示「当前/最大」格式,
参考 AccountCapacityCell 的设计风格。

- 后端 UserHandler 注入 ConcurrencyService,批量查询用户当前并发数
- 新增 UserConcurrencyCell 组件,支持颜色状态(空闲灰/使用中黄/满载红)
- 前端 AdminUser 类型添加 current_concurrency 字段
2026-02-08 16:44:51 +08:00
shaw
8a0a8558cf feat(ui): OpenAI OAuth 账号支持批量 RT 输入创建
新增通过手动输入 Refresh Token 创建 OpenAI OAuth 账号功能,
参考 Anthropic sessionKey 批量创建方式:

- useOpenAIOAuth 添加 validateRefreshToken 方法
- accounts.ts 添加 refreshOpenAIToken API
- AuthInputMethod 类型新增 refresh_token 选项
- 支持多行输入 RT(每行一个)批量创建账号
- 账号名称自动累加后缀 #1, #2 等
- UI 显示 RT 数量徽章和批量创建提示
- 添加中英文 i18n 翻译
2026-02-08 16:10:15 +08:00
Wesley Liddick
2185a3b674 Merge pull request #517 from touwaeriol/fix/upstream-baseurl
refactor(upstream): replace upstream account type with apikey + auto-append base_url
2026-02-08 14:03:12 +08:00
Wesley Liddick
9e3c306a5b Merge pull request #513 from touwaeriol/pr/antigravity-full-v2
feat(antigravity): comprehensive enhancements — rate limiting, scheduling & smart retry
2026-02-08 14:01:17 +08:00
shaw
b1c30df8e3 fix(ui): unify admin table toolbar layout with search and buttons in single row
Standardize filter bar layout across admin pages to place search/filters
on left and action buttons on right within the same row, improving
visual consistency and space utilization.
2026-02-08 14:00:02 +08:00
erio
69816f8691 fix: remove unused upstreamHopByHopHeaders variable to pass golangci-lint 2026-02-08 13:30:39 +08:00
shaw
b4ec65785d fix: apikey类型账号test去掉oauth-2025-04-20 2026-02-08 13:26:28 +08:00
erio
3c93644146 chore: bump version to 0.1.74.7 2026-02-08 13:14:58 +08:00
erio
fb58560d15 refactor(upstream): replace upstream account type with apikey, auto-append /antigravity
Upstream accounts now use the standard APIKey type instead of a dedicated
upstream type. GetBaseURL() and new GetGeminiBaseURL() automatically append
/antigravity for Antigravity platform APIKey accounts, eliminating the need
for separate upstream forwarding methods.

- Remove ForwardUpstream, ForwardUpstreamGemini, testUpstreamConnection
- Remove upstream branch guards in Forward/ForwardGemini/TestConnection
- Add migration 052 to convert existing upstream accounts to apikey
- Update frontend CreateAccountModal to create apikey type
- Add unit tests for GetBaseURL and GetGeminiBaseURL
2026-02-08 13:06:25 +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
erio
6ab77f5eb5 fix(upstream): passthrough response body directly instead of parsing SSE
ForwardUpstream/ForwardUpstreamGemini should pipe the upstream response
directly to the client (headers + body), not parse it as SSE stream.
2026-02-08 08:49:43 +08:00
erio
4f57d7f761 fix: add nil guard for gin.Context in header passthrough to satisfy staticcheck SA5011 2026-02-08 08:36:35 +08:00
erio
1563bd3dda feat(upstream): passthrough all client headers instead of manual header setting
Replace manual header setting (Content-Type, anthropic-version, anthropic-beta)
with full client header passthrough in ForwardUpstream/ForwardUpstreamGemini.
Only authentication headers (Authorization, x-api-key) are overridden with
upstream account credentials. Hop-by-hop headers are excluded.

Add unit tests covering header passthrough, auth override, and hop-by-hop filtering.
2026-02-08 08:33:09 +08:00
erio
df3346387f fix(frontend): upstream account edit fields and mixed_scheduling on create
- EditAccountModal: add Base URL / API Key fields for upstream type
- EditAccountModal: initialize editBaseUrl from credentials on upstream account open
- EditAccountModal: save upstream credentials (base_url, api_key) on submit
- CreateAccountModal: pass mixed_scheduling extra when creating upstream account
2026-02-08 02:08:51 +08:00
erio
77b66653ed fix(gateway): restore upstream account forwarding with dedicated methods
v0.1.74 merged upstream accounts into the OAuth path, causing requests
to hit the wrong protocol and endpoint. Add three upstream-specific
methods (testUpstreamConnection, ForwardUpstream, ForwardUpstreamGemini)
that use base_url + apiKey auth and passthrough the original body, while
reusing the existing response handling and error/retry logic.
2026-02-08 01:21:02 +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
erio
3077fd279d feat: smart retry max 1 attempt + clear sticky session on failure
- Change antigravitySmartRetryMaxAttempts from 3 to 1 to prevent
  repeated rate limiting and long waits
- Clear sticky session binding (DeleteSessionAccountID) after smart
  retry exhaustion, so subsequent requests don't hit the same
  rate-limited account
- Add flow diagrams to Forward/ForwardGemini doc comments
- Add comprehensive unit tests covering:
  - Sticky session cleared on retry failure (429, 503, network error)
  - Sticky session NOT cleared on retry success
  - Sticky session NOT cleared for non-sticky requests (empty hash)
  - Sticky session NOT cleared on long delay path (handled by handler)
  - Nil cache safety (no panic)
  - MaxAttempts constant verification
  - End-to-end retryLoop → switchError propagation with session clear
2026-02-07 19:30:58 +08:00
shaw
f3605ddc71 chore: /admin/usage页面增加一个刷新按钮 2026-02-07 19:13:43 +08:00
shaw
6aaa4aee6a fix: 收敛 Claude Code 探测拦截并补齐回归测试 2026-02-07 19:04:08 +08:00
erio
e3748da860 fix(lint): handle errcheck for strings.Builder.WriteString 2026-02-07 18:18:15 +08:00
erio
36e6fb5fc8 ci: trigger CI for new PR 2026-02-07 18:13:37 +08:00
erio
86b503f87f refactor: remove Anthropic digest chain from Messages handler
The digest chain fallback is only needed for Gemini endpoints, not
for the Anthropic Messages API path. Remove the handler integration
while keeping the reusable service/repository layer for future use.
2026-02-07 18:01:04 +08:00
erio
50a783ff01 feat: add Anthropic sticky session digest chain matching via Trie
The previous fallback (step 3) in GenerateSessionHash hashed system +
all messages together, producing a different hash each round as the
conversation grew ([a] -> [a,b] -> [a,b,c]). This made fallback sticky
sessions ineffective for multi-turn conversations.

Implement per-message Trie digest chain matching (reusing Gemini's Trie
infrastructure) so that the previous round's chain is always a prefix
of the current round's chain, enabling reliable session affinity.
2026-02-07 18:00:56 +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
shaw
da9546ba24 fix(ui): widen CreateAccountModal to fix platform selector overflow 2026-02-07 17:25:52 +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
shaw
1439eb39a9 fix(gateway): harden digest logging and align antigravity ops
- avoid panic by using safe UUID prefix truncation in Gemini digest fallback logs\n- remove unconditional Antigravity 429 full-body debug logs and honor log truncation config\n- align Antigravity quick preset mappings to opus 4.6-thinking targets only\n- restore scope rate-limit aggregation/output in ops availability stats
2026-02-07 17:12:15 +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
erio
e1a68497d6 refactor: simplify sticky session rate limit handling — switch immediately on any rate limit
Remove threshold-based waiting in both sticky session and antigravity
pre-check paths. When a model is rate-limited, immediately clear the
sticky session and switch accounts instead of waiting for short durations.
2026-02-07 17:06:49 +08:00
Wesley Liddick
c4615a1224 Merge pull request #509 from touwaeriol/pr/antigravity-full
feat(antigravity): comprehensive enhancements - model mapping, rate limiting, scheduling & ops
2026-02-07 16:44:28 +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
erio
fa28dcbf32 fix(test): update test calls to match method receivers on handleSmartRetry and antigravityRetryLoop 2026-02-07 16:05:09 +08:00
erio
2656320d04 fix(antigravity): fetch default mapping from API and sync Redis on rate limit
1. Frontend: replace hardcoded antigravityDefaultMappings with async
   fetch from GET /admin/accounts/antigravity/default-model-mapping,
   eliminating the duplicate data source that caused frontend/backend
   mapping inconsistency.

2. Backend: convert handleSmartRetry and antigravityRetryLoop from
   standalone functions to AntigravityGatewayService methods, enabling
   Redis cache sync (updateAccountModelRateLimitInCache) after both
   rate-limit write paths — long-delay branch and retry-exhausted branch.
2026-02-07 15:59:27 +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
shaw
5d4327eb14 fix: 前端codex教程里模型ID升级为gpt-5.3-codex 2026-02-07 14:53:53 +08:00
erio
b4f6c4f9d5 style: fix gofmt formatting in gateway_service.go
Remove extra blank line that caused golangci-lint gofmt check to fail.
2026-02-07 14:51:20 +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
erio
14c6c9321a refactor: remove unused IsAntigravityModelSupported function and its tests 2026-02-07 14:42:28 +08:00
erio
386126b1b2 test(antigravity): add missing unit tests for upstream and custom model_mapping
- Add GetAccessToken upstream branch tests (success/failure/empty/nil)
- Add mapAntigravityModel wildcard-target-equals-request edge case tests
- Add upstream account smart retry test case
- Add GeminiMessagesCompatService custom model_mapping and empty model tests
2026-02-07 14:39:25 +08:00
erio
de0927289e fix(antigravity): support upstream accounts and custom model_mapping in scheduling
- GetAccessToken: add upstream branch to read api_key from credentials
- shouldTriggerAntigravitySmartRetry: relax check from IsOAuth to Platform-based
- isModelSupportedByAccount/WithContext: replace IsAntigravityModelSupported
  whitelist with mapAntigravityModel for unified scheduling/forwarding logic
- mapAntigravityModel: fix edge case where wildcard target equals request model
- Update tests for new behavior and add custom model_mapping test cases
2026-02-07 14:32:08 +08:00
erio
edb0937024 fix: restore non-failover error passthrough from 7b156489 2026-02-07 14:24:55 +08:00
erio
43a4840daf fix: restore error passthrough service improvements from 7b156489 2026-02-07 14:16:19 +08:00
erio
5e98445b22 feat(antigravity): comprehensive enhancements - model mapping, rate limiting, scheduling & ops
Key changes:
- Upgrade model mapping: Opus 4.5 → Opus 4.6-thinking with precise matching
- Unified rate limiting: scope-level → model-level with Redis snapshot sync
- Load-balanced scheduling by call count with smart retry mechanism
- Force cache billing support
- Model identity injection in prompts with leak prevention
- Thinking mode auto-handling (max_tokens/budget_tokens fix)
- Frontend: whitelist mode toggle, model mapping validation, status indicators
- Gemini session fallback with Redis Trie O(L) matching
- Ops: enhanced concurrency monitoring, account availability, retry logic
- Migration scripts: 049-051 for model mapping unification
2026-02-07 12:31:10 +08:00
Wesley Liddick
e617b45ba3 Merge pull request #508 from touwaeriol/pr/format-time-seconds
feat(frontend): show seconds in rate limit time display
2026-02-07 12:20:29 +08:00
Wesley Liddick
20283bb55b Merge pull request #507 from touwaeriol/pr/fix-429-fallback-default
fix(antigravity): reduce 429 fallback cooldown from 5min to 30s
2026-02-07 12:19:14 +08:00
Wesley Liddick
515dbf2c78 Merge pull request #506 from touwaeriol/pr/fix-max-tokens-budget
fix(antigravity): auto-fix max_tokens <= budget_tokens causing 400 error
2026-02-07 12:18:11 +08:00
Wesley Liddick
2887e280d6 Merge pull request #505 from touwaeriol/pr/gitattributes-lf
chore: add .gitattributes to enforce LF line endings
2026-02-07 12:17:43 +08:00
erio
8826705e71 feat(frontend): show seconds in rate limit time display
Change formatTime() to include seconds (HH:MM:SS) instead of only
hours and minutes (HH:MM). This gives users more precise information
about when rate limits will reset.
2026-02-07 11:59:27 +08:00
erio
8917afab2a fix(antigravity): reduce 429 fallback cooldown from 5min to 30s
The default fallback cooldown when rate limit reset time cannot be
parsed was 5 minutes, which is too aggressive and causes accounts
to be unnecessarily locked out. Reduce to 30 seconds for faster
recovery. Config override still works (unit remains minutes).
2026-02-07 11:54:00 +08:00
erio
49233ec26a fix(antigravity): auto-fix max_tokens <= budget_tokens causing 400 error
When extended thinking is enabled, Claude API requires max_tokens >
thinking.budget_tokens. If misconfigured, this auto-adjusts max_tokens
to budget_tokens + 1000 instead of returning a 400 error.

- Add ensureMaxTokensGreaterThanBudget helper function
- Extract Gemini25FlashThinkingBudgetLimit constant (24576)
- Log adjustment for debugging
2026-02-07 11:49:03 +08:00
erio
1e1cbbee80 chore: add .gitattributes to enforce LF line endings
Ensures consistent line endings for SQL migration files, Go source,
shell scripts, YAML configs, and Dockerfiles. Fixes checksum mismatches
on Windows where CRLF line endings cause migration hash differences.
2026-02-07 11:47:03 +08:00
shaw
39a5b17d31 fix: 账号测试根据类型使用不同的 beta header
- OAuth 账号:使用完整的 DefaultBetaHeader 和 Claude Code 客户端 headers
- API Key 账号:使用 APIKeyBetaHeader(不含 oauth beta)
2026-02-07 11:33:06 +08:00
yangjianbo
782a54a8a1 chore(version): 更新版本号至 0.1.70.1 2026-02-07 11:17:46 +08:00
shaw
35a55e10aa fix: 前端快捷添加模型id新增gpt5.3系列 2026-02-07 11:13:51 +08:00
shaw
9e80ed0fa8 fix(frontend): 优化代理管理页面工具栏布局
- 将筛选器和操作按钮合并到同一行显示
- 筛选器在左侧,操作按钮在右侧
- 添加响应式支持,窄屏时自动换行并简化按钮文字
2026-02-07 11:09:34 +08:00
shaw
5299f3dcf6 fix: ix: antigravity 添加 aude-opus-4-6-thinking 模型支持 2026-02-07 10:38:10 +08:00
shaw
7b1564898b fix: make error passthrough effective for non-failover upstream errors 2026-02-07 10:25:56 +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
shaw
76d242e024 refactor(frontend): 复用 TokenUsageTrend 组件优化用户 Dashboard 图表
用户 Dashboard 的 Token 使用趋势图表现在显示 Input/Output/Cache 三种类型,
并在 Tooltip 中显示 Actual 和 Standard 价格,与管理员页面保持一致。
2026-02-06 20:18:38 +08:00
shaw
260c152166 fix(frontend): 修复重启后健康检查接口路径错误
将 /api/health 改为 /health,与后端实际注册的路由一致
2026-02-06 19:53:39 +08:00
shaw
9f4c1ef9f9 fix(ops): 添加 token 相关字段白名单避免误脱敏
在敏感字段检测中添加白名单,排除 API 参数和用量统计字段:
- max_tokens, max_completion_tokens, max_output_tokens
- completion_tokens, prompt_tokens, total_tokens
- input_tokens, output_tokens
- cache_creation_input_tokens, cache_read_input_tokens

这些字段名虽然包含 "token" 但只是数值参数,不应被脱敏处理。
2026-02-06 19:47:14 +08:00
shaw
bd7fdb5e6c refactor(frontend): 调整账号页面错误透传规则按钮位置
将错误透传规则按钮从自动刷新按钮前面移动到后面
2026-02-06 16:38:06 +08:00
Wesley Liddick
a381910e86 Merge pull request #489 from LLLLLLiulei/feat/import-export-bundle
feat: implement account & proxy import/export with migration-ready JSON bundles
2026-02-06 16:29:52 +08:00
shaw
d182ef0391 fix(gateway): 移除 PR #316 引入的工具名转换逻辑
移除响应阶段的工具名/schema/description 转换逻辑,修复第三方工具调用时
工具名被错误转换的问题(如 Task → task)。

移除内容:
- 工具名相关正则变量(toolPrefixRe, toolNameBoundaryRe 等)
- openCodeToolOverrides 和 claudeToolNameOverrides 映射表
- 工具名转换函数(normalizeToolNameForClaude, normalizeToolNameForOpenCode 等)
- 响应体工具名替换函数(replaceToolNamesInText, replaceToolNamesInResponseBody 等)
- 参数名转换函数(normalizeParamNameForOpenCode, rewriteParamKeysInValue)
- 工具描述清理函数(sanitizeToolDescription)
- 输入 schema 转换函数(normalizeToolInputSchema)
- 模型 ID 正则替换函数(replaceModelIDInText)

保留内容:
- 系统提示词清理(sanitizeSystemText)
- Claude Code 指纹 headers 处理
- 模型 ID 映射(通过 JSON 对象操作)
2026-02-06 16:09:58 +08:00
LLLLLLiulei
7319122e92 merge upstream/main 2026-02-06 11:33:45 +08:00
Wesley Liddick
4809fa4f19 Merge pull request #497 from mt21625457/main
fix(兼容): 将 Kimi cached_tokens 映射到 Claude 标准 cache_read_input_tokens
2026-02-06 11:20:42 +08:00
yangjianbo
792bef615c Merge branch 'main' into test 2026-02-06 09:59:15 +08:00
yangjianbo
ee01f80dc1 test(backend): 修复 usage 类型断言未检查 2026-02-06 09:54:29 +08:00
yangjianbo
98671a73f4 Merge branch 'main' of https://github.com/mt21625457/aicodex2api
# Conflicts:
#	backend/internal/service/gateway_cached_tokens_test.go
2026-02-06 09:35:46 +08:00
yangjianbo
f33a950103 fix(兼容): 将 Kimi cached_tokens 映射到 Claude 标准 cache_read_input_tokens
Kimi 等 Claude 兼容 API 返回缓存信息使用 OpenAI 风格的 cached_tokens 字段,
而非 Claude 标准的 cache_read_input_tokens,导致客户端收不到缓存命中信息且
内部计费缓存折扣为 0。

新增 reconcileCachedTokens 辅助函数,在 cache_read_input_tokens == 0 且
cached_tokens > 0 时自动填充,覆盖流式(message_start/message_delta)和
非流式两种响应路径。对 Claude 原生上游无影响。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 09:27:42 +08:00
程序猿MT
132bf34b69 Merge branch 'Wei-Shaw:main' into main 2026-02-06 08:53:52 +08:00
shaw
01b08e1e43 chore: 前端增加opus4.6模型映射 2026-02-06 08:50:45 +08:00
yangjianbo
000a943cce Merge branch 'main' into test 2026-02-06 08:43:42 +08:00
yangjianbo
c6a456c7c7 fix(兼容): 将 Kimi cached_tokens 映射到 Claude 标准 cache_read_input_tokens
Kimi 等 Claude 兼容 API 返回缓存信息使用 OpenAI 风格的 cached_tokens 字段,
而非 Claude 标准的 cache_read_input_tokens,导致客户端收不到缓存命中信息且
内部计费缓存折扣为 0。

新增 reconcileCachedTokens 辅助函数,在 cache_read_input_tokens == 0 且
cached_tokens > 0 时自动填充,覆盖流式(message_start/message_delta)和
非流式两种响应路径。对 Claude 原生上游无影响。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 08:42:55 +08:00
Wesley Liddick
cc2329d4fd Merge pull request #496 from mt21625457/main
feat(模型): 添加 gpt-5.3 Codex 映射与价格配置
2026-02-06 08:37:24 +08:00
Wesley Liddick
84d0433cc3 Merge pull request #493 from iBenzene/fix/json-extra-save-error
fix: 修复了 codex 更新用量窗口异常的 bug
2026-02-06 08:35:24 +08:00
yangjianbo
f82e346f02 Merge branch 'main' into test 2026-02-06 08:12:40 +08:00
yangjianbo
a113dd4def feat: vesion -> 0.1.70 2026-02-06 07:55:30 +08:00
yangjianbo
98f793155f build(工具链): 升级 Go 到 1.25.7 2026-02-06 07:41:23 +08:00
yangjianbo
a38bd413ab fix(计费): gpt-5.3-codex 定价回退到 gpt-5.2-codex 2026-02-06 07:40:38 +08:00
yangjianbo
9e1535e203 feat(模型): 添加 gpt-5.3 Codex 映射与价格配置 2026-02-06 07:14:46 +08:00
yangjianbo
d8e405511e Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-06 06:56:23 +08:00
iBenzene
037a409919 fix: 修复了 codex 更新用量窗口异常的 bug 2026-02-06 01:06:22 +08:00
Wesley Liddick
571d1479a4 Merge pull request #490 from IanShaw027/fix/gemini-oauth-registered-user
fix(gemini): 修复已注册用户 OAuth 授权问题并增强错误提示
2026-02-05 22:37:20 +08:00
shaw
ae1934f7db fix: 修复管理页面活跃会话数始终显示为0的问题
问题原因:Redis Pipeline 执行 Lua 脚本时出现 NOSCRIPT 错误,
因为 redis.NewScript 使用 EVALSHA 执行脚本,当 Redis 重启或
脚本未被缓存时,Pipeline 模式无法自动回退到 EVAL。

解决方案:在 NewSessionLimitCache 初始化时预加载所有 Lua 脚本
到 Redis,确保后续 Pipeline 执行时脚本已被缓存。
2026-02-05 22:36:17 +08:00
shaw
39e05a2dad feat: 新增全局错误透传规则功能
支持管理员配置上游错误如何返回给客户端:
- 新增 ErrorPassthroughRule 数据模型和 Ent Schema
- 实现规则的 CRUD API(/admin/error-passthrough-rules)
- 支持按错误码、关键词匹配,支持 any/all 匹配模式
- 支持按平台过滤(anthropic/openai/gemini/antigravity)
- 支持透传或自定义响应状态码和错误消息
- 实现两级缓存(Redis + 本地内存)和多实例同步
- 集成到 gateway_handler 的错误处理流程
- 新增前端管理界面组件
- 新增单元测试覆盖核心匹配逻辑

优化:
- 移除 refreshLocalCache 中的冗余排序(数据库已排序)
- 后端 Validate() 增加匹配条件非空校验
2026-02-05 21:52:54 +08:00
ianshaw
7b46bbb628 fix(lint): 修复错误消息大写问题以符合 Go 惯例 2026-02-05 20:47:15 +08:00
ianshaw
d2527e36eb feat(gemini): 增强 API 授权错误处理,自动提取并显示激活 URL
当 Gemini for Google Cloud API 未启用时(SERVICE_DISABLED 错误),
系统现在会:
- 自动检测 403 PERMISSION_DENIED 错误
- 从错误响应中提取 API 激活 URL
- 向用户显示清晰的错误消息和可点击的激活链接
- 提供操作指引(启用后等待几分钟)

新增文件:
- internal/pkg/googleapi/error.go: Google API 错误解析器
- internal/pkg/googleapi/error_test.go: 完整的测试覆盖
- GEMINI_API_ERROR_HANDLING.md: 实现文档

修改文件:
- internal/repository/geminicli_codeassist_client.go:
  在 LoadCodeAssist 和 OnboardUser 中增强错误处理

这大大改善了用户体验,用户不再需要手动从错误日志中查找激活 URL。
2026-02-05 20:17:53 +08:00
LLLLLLiulei
029994a83b fix: remove unused listAllAccounts 2026-02-05 19:13:00 +08:00
LLLLLLiulei
37047919ab fix: harden import/export flow 2026-02-05 18:59:30 +08:00
LLLLLLiulei
0b45d48e85 perf: batch fetch proxies for account export 2026-02-05 18:40:49 +08:00
LLLLLLiulei
0c660f8335 feat: refine proxy export and toolbar layout 2026-02-05 18:35:00 +08:00
LLLLLLiulei
ce9a247a9d feat: add proxy import flow 2026-02-05 18:23:49 +08:00
LLLLLLiulei
b4bd46d067 feat: add data import/export bundle 2026-02-05 17:46:08 +08:00
shaw
1d8b686446 chore: 移除无关的md文档 2026-02-05 16:17:11 +08:00
shaw
2b192f7dca feat: 支持用户专属分组倍率配置 2026-02-05 16:05:42 +08:00
IanShaw027
979114db45 fix(gemini): 修复已注册用户 OAuth 授权时错误调用 onboardUser 的问题
问题:Google One Ultra 等已注册用户在 OAuth 授权时,如果 LoadCodeAssist
返回了 currentTier/paidTier 但没有返回 cloudaicompanionProject,之前的
逻辑会继续调用 onboardUser,导致 INVALID_ARGUMENT 错误。

修复:对齐 Gemini CLI 的处理逻辑:
- 当检测到用户已注册(有 currentTier/paidTier)时,不再调用 onboardUser
- 先尝试从 Cloud Resource Manager 获取可用项目
- 如果仍无法获取,返回友好的错误提示,引导用户手动填写 Project ID

这个修复解决了 Google One 订阅用户无法正常授权的问题。
2026-02-05 13:57:02 +08:00
shaw
6d0152c8e2 chore: 移除多余的文档/配置示例 2026-02-05 13:39:31 +08:00
Wesley Liddick
dabed96af4 Merge pull request #486 from s-Joshua-s/feat/usage-filter-by-apikey
feat(gateway): filter /v1/usage stats by API Key instead of UserID
2026-02-05 13:37:31 +08:00
Wesley Liddick
36becd972a Merge pull request #485 from LemonZuo/mod
feat: add support for HTTP/2 Cleartext (h2c) connections
2026-02-05 13:36:16 +08:00
Lemon
7498035d24 Merge branch 'main' into mod 2026-02-05 12:49:43 +08:00
Lemon
39a0359dd5 feat: enhance HTTP/2 Cleartext (h2c) configuration options 2026-02-05 12:48:05 +08:00
shaw
49a3c43741 feat(auth): 实现 Refresh Token 机制
- 新增 Access Token + Refresh Token 双令牌认证
- 支持 Token 自动刷新和轮转
- 添加登出和撤销所有会话接口
- 前端实现无感刷新和主动刷新定时器
2026-02-05 12:42:54 +08:00
JIA-ss
fa3ea5ee4d feat(gateway): filter /v1/usage stats by API Key instead of UserID
Previously the /v1/usage endpoint aggregated usage stats (today/total
tokens, cost, RPM/TPM) across all API Keys belonging to the user.
This made it impossible to distinguish usage from different API Keys
(e.g. balance vs subscription keys).

Now the usage stats are filtered by the current request's API Key ID,
so each key only sees its own usage data. The balance/remaining fields
are unaffected and still reflect the user-level wallet balance.

Changes:
- Add GetAPIKeyDashboardStats to repository interface and implementation
- Add getPerformanceStatsByAPIKey helper (also fixes TPM to include
  cache_creation_tokens and cache_read_tokens)
- Add GetAPIKeyDashboardStats to UsageService
- Update Usage handler to call GetAPIKeyDashboardStats(apiKey.ID)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:45:54 +08:00
shaw
05af95dade fix(gateway): 修复工具名转换破坏 Anthropic 特殊工具的问题
未知工具名不再进行 PascalCase/snake_case 转换,保持原样透传。
修复 text_editor_20250728 等 Anthropic 特殊工具被错误转换的问题。
2026-02-05 09:53:20 +08:00
Wesley Liddick
ae680d79ed Merge pull request #481 from beyondkmp/fix/remove-unsupported-openai-fields
fix: remove unsupported safety_identifier and previous_response_id fields from upstream requests
2026-02-05 08:35:12 +08:00
Lemon
97a5c1ac1d feat: add support for HTTP/2 Cleartext (h2c) connections 2026-02-04 21:40:25 +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
shaw
8f39754812 fix(gateway): 修复模型前缀映射逻辑错误
问题:normalizeClaudeModelForAnthropic 函数错误地将长模型ID截断为短ID,
导致 APIKey 账号的模型名被错误修改。

修复:
- 删除错误的 normalizeClaudeModelForAnthropic 函数和 anthropicPrefixMappings 变量
- 直接使用 claude.NormalizeModelID(正确的短ID->长ID扩展)
- APIKey 账号无显式映射时透传原始模型名
2026-02-04 17:50:05 +08:00
Wesley Liddick
ac4371fa98 Merge pull request #482 from IanShaw027/fix/gateway-compatibility
fix(gemini): 优化 Gemini 接口认证兼容性,支持 Authorization: Bearer
2026-02-04 16:50:52 +08:00
柴叁
9985c4a344 fix(gemini): 优化 Gemini 接口认证兼容性,支持 Authorization: Bearer
调整 API key 提取优先级,让 /v1beta 接口同时支持 x-goog-api-key 和
Authorization: Bearer 两种认证方式,解决 OpenClaw 等使用 Bearer 认证
的客户端无法直接访问 Gemini 接口的问题。
2026-02-04 16:26:36 +08:00
Payne Fu
fecfaae8dc fix: remove unsupported safety_identifier and previous_response_id fields from upstream requests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:56:01 +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
Wesley Liddick
804b6f2282 Merge pull request #468 from s-Joshua-s/fix/thinking-block-modification-error
fix(api): 修复 thinking 块被意外修改导致的 400 错误
2026-02-03 22:21:06 +08:00
Wesley Liddick
cb58daf38d Merge pull request #476 from touwaeriol/fix/gemini-thoughtsignature-v2
fix(gemini): 导出 DummyThoughtSignature 常量并修复跨账号签名验证
2026-02-03 21:56:34 +08:00
Wesley Liddick
a9398d210b Merge pull request #475 from touwaeriol/fix/openai-oauth-instructions-v2
fix(openai): 统一 OAuth instructions 处理逻辑,修复 Codex CLI 400 错误
2026-02-03 21:56:13 +08:00
shaw
df1c2383da chore: fix gofmt formatting 2026-02-03 21:52:49 +08:00
Wesley Liddick
ff8b1b4ae3 Merge pull request #467 from slovx2/main
Antigravity 相关BUG修复及调度优化
2026-02-03 21:51:04 +08:00
Wesley Liddick
4cce21b125 Merge branch 'main' into main 2026-02-03 21:43:41 +08:00
liuxiongfeng
6baf810885 fix(gemini): 导出 DummyThoughtSignature 常量并修复跨账号签名验证
- 将 dummyThoughtSignature 改为导出的 DummyThoughtSignature 常量,供跨包使用
- 修改 gemini_native_signature_cleaner.go 将删除签名改为替换为 dummy 签名
  这样可以跳过 Gemini 3 的签名验证,解决粘性会话切换账号时的验证失败问题
- 更新相关测试文件

Fixes: 粘性会话切换账号时 thoughtSignature 验证失败导致 400 错误
2026-02-03 21:34:55 +08:00
liuxiongfeng
9a48b2e942 fix(openai): 统一 OAuth instructions 处理逻辑,修复 Codex CLI 400 错误
- 修改 applyCodexOAuthTransform 函数签名,增加 isCodexCLI 参数
- 移除 && !isCodexCLI 条件,对所有 OAuth 请求统一处理
- 新增 applyInstructions/applyCodexCLIInstructions/applyOpenCodeInstructions 辅助函数
- 新增 isInstructionsEmpty 函数检查 instructions 字段是否为空
- 添加 Codex CLI 和非 Codex CLI 场景的测试用例

逻辑说明:
- Codex CLI + 有 instructions: 保持不变
- Codex CLI + 无 instructions: 补充 opencode 指令
- 非 Codex CLI: 使用 opencode 指令覆盖
2026-02-03 21:22:33 +08:00
Wesley Liddick
c0c9c984d1 Merge pull request #471 from bayma888/feature/api-key-quota-expiration
feat(api-key): 添加API密钥独立配额和过期时间功能
2026-02-03 21:11:17 +08:00
yangjianbo
31fe017888 Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-02-03 21:00:11 +08:00
bayma888
3fed478e4d fix(lint): format gateway_service.go with gofmt 2026-02-03 20:55:17 +08:00
bayma888
0afc5d0b1a fix(test): update API contract tests for new quota fields
Add quota, quota_used, expires_at fields to expected JSON responses
in POST /api/v1/keys and GET /api/v1/keys test cases.
2026-02-03 20:54:25 +08:00
Wesley Liddick
ba5a0d47eb Merge pull request #460 from s-Joshua-s/fix/proxy-probe-fallback
fix(proxy): 增加代理探测的多 URL 回退机制
2026-02-03 20:49:59 +08:00
bayma888
be7bc658fc fix(test): add IncrementQuotaUsed to all APIKeyRepository test stubs
- Add missing IncrementQuotaUsed method to stubApiKeyRepo in api_contract_test.go
- Fix gofmt formatting issues in api_key_service.go, dto/types.go, api_key_handler.go
2026-02-03 20:49:58 +08:00
Wesley Liddick
c89bbf5130 Merge pull request #458 from bayma888/feature/admin-user-balance-history
feat(admin): 管理员可查看每个用户充值和并发变动记录、点击余额可直接查看、优化弹框UI
2026-02-03 20:37:30 +08:00
bayma888
e59e3a9f00 fix(test): add IncrementQuotaUsed to all APIKeyRepository test stubs
Add the missing IncrementQuotaUsed method to:
- fakeAPIKeyRepo (api_key_auth_google_test.go)
- stubApiKeyRepo (api_key_auth_test.go)
- apiKeyRepoStub (api_key_service_delete_test.go)
- authRepoStub (api_key_service_cache_test.go)
2026-02-03 20:00:43 +08:00
bayma888
6146be1474 feat(api-key): add independent quota and expiration support
This feature allows API Keys to have their own quota limits and expiration
times, independent of the user's balance.

Backend:
- Add quota, quota_used, expires_at fields to api_key schema
- Implement IsExpired() and IsQuotaExhausted() checks in middleware
- Add ResetQuota and ClearExpiration API endpoints
- Integrate quota billing in gateway handlers (OpenAI, Anthropic, Gemini)
- Include quota/expiration fields in auth cache for performance
- Expiration check returns 403, quota exhausted returns 429

Frontend:
- Add quota and expiration inputs to key create/edit dialog
- Add quick-select buttons for expiration (+7, +30, +90 days)
- Add reset quota confirmation dialog
- Add expires_at column to keys list
- Add i18n translations for new features (en/zh)

Migration:
- Add 045_add_api_key_quota.sql for new columns
2026-02-03 19:49:31 +08:00
bayma888
730d2a9ad2 fix(test): add missing stub methods to stubRedeemCodeRepo in api_contract_test
Add ListByUserPaginated and SumPositiveBalanceByUser methods
2026-02-03 19:36:17 +08:00
bayma888
d008941cb3 fix(test): add missing stub methods for RedeemCodeRepository and AdminService
Add ListByUserPaginated and SumPositiveBalanceByUser to redeemRepoStub
Add GetUserBalanceHistory to stubAdminService

Fixes CI test compilation errors
2026-02-03 19:29:39 +08:00
JIA-ss
df7a3e65ee fix(proxy): 增加代理探测的多 URL 回退机制
某些 AI API 专用代理只允许访问特定域名(如 anthropic.com、openai.com),
会拦截对 ip-api.com 的请求。本次修改增加了多 URL 回退机制:

1. 优先使用 ip-api.com(可获取详细地理信息:城市、地区、国家)
2. 若失败则回退到 httpbin.org/ip(仅获取 IP 地址,速度快)

主要变更:
- 新增 probeURLs 列表支持多个探测 URL
- 重构 ProbeProxy() 实现回退逻辑
- 新增 parseHTTPBin() 解析 httpbin.org 响应
- 优化错误信息,JSON 解析失败时显示响应体前 200 字符

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-03 17:12:27 +08:00
song
0707f3d963 chore: gofmt 2026-02-03 17:03:54 +08:00
song
976d6fb03f chore: update antigravity example url 2026-02-03 17:00:46 +08:00
song
f1aafbc06f chore: gofmt 2026-02-03 16:55:13 +08:00
song
7cb5444dbb fix: update tests for group fallback 2026-02-03 16:48:52 +08:00
song
3bede6e65f merge upstream main 2026-02-03 16:21:58 +08:00
JIA-ss
ad90bb4645 fix(api): 修复 thinking 块被意外修改导致的 400 错误
问题描述:
使用扩展思考功能时,偶现以下错误:
"thinking or redacted_thinking blocks in the latest assistant message cannot be modified"

根因分析:
当代理服务修改请求体中的某些字段时(如 metadata.user_id、model),
使用 map[string]any 解析整个 JSON 后重新序列化,导致:
1. 字段顺序改变(Go map 序列化按字母排序)
2. 数字格式变化(如 1.0 → 1)
3. Unicode 转义变化

Claude API 对 thinking 块进行字节级验证,任何变化都会触发错误。

修复内容:
1. identity_service.go - RewriteUserID/RewriteUserIDWithMasking
   使用 json.RawMessage 保留其他字段的原始字节

2. gateway_service.go - replaceModelInBody
   使用 json.RawMessage 保留其他字段的原始字节

3. gateway_service.go - normalizeClaudeOAuthRequestBody
   保留 messages 的原始字节,跳过包含 thinking 块的消息修改

4. gateway_service.go - isThinkingBlockSignatureError
   添加 "cannot be modified" 错误检测,触发自动重试

5. antigravity_gateway_service.go - isSignatureRelatedError
   添加 "cannot be modified" 错误检测

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:15:37 +08:00
song
2220fd18ca merge upstream main 2026-02-03 15:36:17 +08:00
Wesley Liddick
bb3df5785a Merge pull request #322 from xlx0852/main
fix: 混合渠道警告确认框和过滤 prompt_cache_retention 参数
2026-02-03 15:13:10 +08:00
Wesley Liddick
6e54eda41f Merge pull request #464 from touwaeriol/pr/antigravity-scope-ratelimit
feat(antigravity): 支持按配额域(scope)级别限流
2026-02-03 15:02:15 +08:00
Wesley Liddick
df4c0adf0b Merge pull request #463 from DuckyProject/feat/usage-records-codex-reasoning-effort
feat(usage): add reasoning effort column
2026-02-03 14:57:45 +08:00
Wesley Liddick
7cbe4afdb8 Merge pull request #462 from touwaeriol/pr/antigravity-gemini-mapping
feat(antigravity): map all gemini-2.5 to gemini-3 series
2026-02-03 14:55:45 +08:00
Wesley Liddick
16f150caae Merge pull request #459 from IanShaw027/fix/gemini-error
fix(gemini): 为 Gemini 工具调用添加 thoughtSignature 避免 INVALID_ARGUMENT 错误
2026-02-03 14:48:50 +08:00
Wesley Liddick
7229b41fc7 Merge pull request #420 from shuike/feat-invitation-code
feat: 增加邀请码注册功能
2026-02-03 14:44:15 +08:00
ianshaw
2cd5037878 chore: 触发 CI 重新运行 2026-02-03 14:43:59 +08:00
ducky
53ee6383db feat(usage): add reasoning effort column 2026-02-03 14:36:29 +08:00
Wesley Liddick
a09478f374 Merge pull request #316 from cyhhao/fix/claude-oauth-compat
fix(网关): 完善 Claude OAuth/Claude Code 兼容
2026-02-03 14:26:19 +08:00
liuxiongfeng
d3c1d77a35 fix(frontend): import missing formatTime function in AccountStatusIndicator 2026-02-03 14:25:30 +08:00
liuxiongfeng
8824400c3e feat(accounts): 账号列表显示 Antigravity scope 级别限流状态
- 后端 DTO 新增 scope_rate_limits 字段,从 extra 提取限流信息
- 前端状态列显示 scope 级限流徽章(Claude/Gemini/Image)
- 清除速率限制时同时清除账号级和 scope 级限流(已有实现)

Cherry-picked from slovx2/sub2api: 66f49b67
2026-02-03 14:25:30 +08:00
liuxiongfeng
6e8eff9bb9 feat(ops): 运维界面展示 Antigravity 账号 scope 级别限流统计
在运维监控的并发/排队卡片中,为 Antigravity 平台账号显示各 scope
(claude/gemini_text/gemini_image) 的限流数量统计,便于管理员了解
哪些 scope 正在被限流。

Cherry-picked from slovx2/sub2api: 08d6dc52
2026-02-03 14:25:30 +08:00
liuxiongfeng
f5884d1608 fix: jsonb_set 嵌套路径无法创建多层 key 的问题
PostgreSQL jsonb_set 在 create_if_missing=true 时无法一次性创建多层嵌套路径。
例如设置 {antigravity_quota_scopes,gemini_image} 时,如果 antigravity_quota_scopes 不存在,
jsonb_set 不会自动创建外层 key,导致更新静默失败(affected=1 但数据未变)。

修复方案:嵌套两次 jsonb_set,先确保外层 key 存在,再设置内层值。
同时在设置限流时更新 last_used_at,使刚触发 429 的账号调度优先级降低。

Cherry-picked from slovx2/sub2api: 4b57e80e
2026-02-03 14:25:30 +08:00
liuxiongfeng
56949a58bc feat(antigravity): 默认开启按配额域限流,避免整个账号被锁定
将 GATEWAY_ANTIGRAVITY_429_SCOPE_LIMIT 的默认值从关闭改为开启。
当 Gemini 模型触发 429 限流时,只会限制对应的配额域(gemini_text),
而 Claude 和 gemini_image 仍可继续使用,提高账号利用率。
2026-02-03 14:25:30 +08:00
liuxiongfeng
7d256879c5 feat(antigravity): map all gemini-2.5 to gemini-3 series
Antigravity 上游不再支持 gemini-2.5 系列,统一映射到 gemini-3:
- gemini-2.5-flash → gemini-3-flash
- gemini-2.5-flash-lite → gemini-3-flash
- gemini-2.5-flash-thinking → gemini-3-flash
- gemini-2.5-flash-image → gemini-3-pro-image
- gemini-2.5-pro → gemini-3-pro-high
- gemini-2.5-pro-preview → gemini-3-pro-high
- gemini-2.5-pro-exp → gemini-3-pro-high
2026-02-03 14:23:47 +08:00
liuxiongfeng
f9512fda58 test: update gemini-2.5-pro mapping test case
Update test expectation to reflect new mapping:
gemini-2.5-pro -> gemini-3-pro-high
2026-02-03 14:23:47 +08:00
liuxiongfeng
beb63cb152 feat(antigravity): map gemini-2.5-pro to gemini-3-pro-high
Add prefix mapping rules for gemini-2.5-pro variants:
- gemini-2.5-pro -> gemini-3-pro-high
- gemini-2.5-pro-preview -> gemini-3-pro-high
- gemini-2.5-pro-exp -> gemini-3-pro-high
2026-02-03 14:23:47 +08:00
song
11ff73b578 fix: align migration checksum and import formatTime 2026-02-03 14:04:11 +08:00
shuike
0ed4a404e4 fix(test): api_contract_test添加 invitation_code_enabled 字段 2026-02-03 13:38:44 +08:00
shuike
6c86501d11 feat: 增加邀请码注册功能 2026-02-03 13:38:44 +08:00
Call White
2fe8932c1d Merge pull request #3 from cyhhao/main
merge to main
2026-02-03 11:44:54 +08:00
shaw
0ab68aa9fb fix(setup): 修复 Redis TLS 配置选项位置错误
- 将 TLS Toggle 从第一步(PostgreSQL)移动到第二步(Redis)
- 添加缺失的 Toggle 组件导入

问题描述:
TLS 配置选项错误地出现在数据库配置步骤中,而不是 Redis 配置步骤
2026-02-03 11:24:04 +08:00
Wesley Liddick
2f92b06869 Merge pull request #457 from touwaeriol/pr/group-copy-accounts
feat(groups): 添加从其他分组复制账号功能
2026-02-03 08:45:13 +08:00
ianshaw
03e94f9f53 fix(gemini): 为 Gemini 工具调用添加 thoughtSignature 避免 INVALID_ARGUMENT 错误 2026-02-03 06:01:29 +08:00
bayma888
606e29d390 feat(admin): add user balance/concurrency history modal
- Add new API endpoint GET /admin/users/:id/balance-history with pagination and type filter
- Add SumPositiveBalanceByUser for calculating total recharged amount
- Create UserBalanceHistoryModal component with:
  - User info header (email, username, created_at, current balance, notes, total recharged)
  - Type filter dropdown (all/balance/admin_balance/concurrency/admin_concurrency/subscription)
  - Quick deposit/withdraw buttons
  - Paginated history list with icons and colored values
- Add instant tooltip on balance column for better UX
- Add z-index prop to BaseDialog for modal stacking control
- Update i18n translations (zh/en)
2026-02-03 00:16:10 +08:00
song
3ecadf4aad chore: apply stashed changes 2026-02-02 22:20:08 +08:00
song
0170d19fa7 merge upstream main 2026-02-02 22:13:50 +08:00
liuxiongfeng
ce1d2904c7 test: 为测试 stub 添加缺失的 GroupRepository 接口方法
新增 BindAccountsToGroup 和 GetAccountIDsByGroupIDs 方法的 stub 实现,
确保测试文件中的 mock 类型满足 GroupRepository 接口要求。
2026-02-02 22:06:37 +08:00
Wesley Liddick
ea41f830fd Merge pull request #456 from touwaeriol/pr/gemini-long-context-billing
feat(billing): 添加 Gemini 200K 长上下文双倍计费功能
2026-02-02 21:57:25 +08:00
liuxiongfeng
e1a4a7b8c0 feat(groups): 添加从其他分组复制账号功能
- 创建分组时可选择从已有分组复制账号
- 编辑分组时支持同步账号(全量替换操作)
- 仅允许选择相同平台的源分组
- 添加完整的数据校验:去重、自引用检查、平台一致性检查
- 前端支持多选源分组,带提示说明操作行为
2026-02-02 21:47:47 +08:00
liuxiongfeng
b381e8ee73 refactor(billing): 简化 CalculateCostWithLongContext 逻辑
将 token 直接拆分为范围内和范围外两部分,分别调用 CalculateCost:
- 范围内:正常计费 (rateMultiplier)
- 范围外:双倍计费 (rateMultiplier × extraMultiplier)

代码更直观,便于理解和维护
2026-02-02 21:47:02 +08:00
liuxiongfeng
45e1429ae8 feat(billing): 添加 Gemini 200K 长上下文双倍计费功能
- 新增 CalculateCostWithLongContext 方法支持阈值双倍计费
- 新增 RecordUsageWithLongContext 方法专用于 Gemini 计费
- Gemini 超过 200K token 的部分按 2 倍费率计算
- 其他平台(Claude/OpenAI)完全不受影响
2026-02-02 21:47:02 +08:00
shaw
7b1d63a786 fix(types): 添加缺失的 ignore_invalid_api_key_errors 类型定义
OpsAdvancedSettings 接口缺少 ignore_invalid_api_key_errors 字段,
导致 TypeScript 编译报错。
2026-02-02 21:01:32 +08:00
Wesley Liddick
e204b4d81f Merge pull request #452 from s-Joshua-s/feat/enhance-usage-endpoint
feat(gateway): 增强 /v1/usage 端点返回完整用量统计
2026-02-02 20:35:00 +08:00
Wesley Liddick
325ed747d8 Merge pull request #455 from ZeroClover/feat/ops-ignore-invalid-api-key-errors
feat(ops): 支持过滤无效 API Key 错误,不写入错误日志
2026-02-02 20:28:00 +08:00
Wesley Liddick
cbf3dba28d Merge pull request #454 from ZeroClover/feat-exclude-user-inactive-errors
feat(ops): 将 USER_INACTIVE 错误排除在 SLA 统计之外
2026-02-02 20:19:48 +08:00
Wesley Liddick
4329f72abf Merge pull request #450 from bayma888/feature/show-admin-adjustment-notes
feat: 向用户显示管理员调整余额的备注
2026-02-02 20:19:23 +08:00
Zero Clover
ad1cdba338 feat(ops): 支持过滤无效 API Key 错误,不写入错误日志
新增 IgnoreInvalidApiKeyErrors 开关,启用后 INVALID_API_KEY 和
API_KEY_REQUIRED 错误将被完全跳过,不写入 Ops 错误日志。
这些错误由用户错误配置导致,与服务质量无关。
2026-02-02 20:16:17 +08:00
Wesley Liddick
016c3915d7 Merge pull request #449 from bayma888/feature/user-search-support-notes
feat: 支持在用户列表搜索中使用备注字段和模糊查询、支持用户名备注等搜索
2026-02-02 20:16:03 +08:00
shaw
79fa18132b fix(gateway): 修复 OAuth token 刷新后调度器缓存不一致问题
Token 刷新成功后,调度器缓存中的 Account 对象仍包含旧的 credentials,
导致在 Outbox 异步更新之前(最多 1 秒窗口)请求使用过期 token,
返回 403 错误(OAuth token has been revoked)。

修复方案:在 token 刷新成功后同步更新调度器缓存,确保调度获取的
Account 对象立即包含最新的 access_token 和 _token_version。

此修复覆盖所有 OAuth 平台:OpenAI、Claude、Gemini、Antigravity。
2026-02-02 20:05:37 +08:00
Zero Clover
673caf41a0 feat(ops): 将 USER_INACTIVE 错误排除在 SLA 统计之外
将账户停用 (USER_INACTIVE) 导致的请求失败视为业务限制类错误,不计入 SLA 和错误率统计。

账户停用是预期内的业务结果,不应被视为系统错误或服务质量问题。此改动使错误分类更加准确,避免将预期的业务限制误报为系统故障。

修改内容:
- 在 classifyOpsIsBusinessLimited 函数中添加 USER_INACTIVE 错误码
- 该类错误不再触发错误率告警

Fixes Wei-Shaw/sub2api#453
2026-02-02 18:50:54 +08:00
JIA-ss
c441638fc0 feat(gateway): 增强 /v1/usage 端点返回完整用量统计
为 CC Switch 集成增强 /v1/usage 网关端点,在保持原有 4 字段
(isValid, planName, remaining, unit) 向后兼容的基础上,新增:

- usage 对象:今日/累计的请求数、token 用量、费用,以及 RPM/TPM
- subscription 对象(订阅模式):日/周/月用量和限额、过期时间
- balance 字段(余额模式):当前钱包余额

用量数据获取采用 best-effort 策略,失败不影响基础响应。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 18:30:06 +08:00
小北
ae18397ca6 feat: 向用户显示管理员调整余额的备注
- 为RedeemCode DTO添加notes字段(仅用于admin_balance/admin_concurrency类型)
- 更新mapper使其有条件地包含备注信息
- 在用户兑换历史UI中显示备注
- 备注以斜体显示,悬停时显示完整内容

用户现在可以看到管理员调整其余额的原因说明。

Changes:
- backend/internal/handler/dto/types.go: RedeemCode添加notes字段
- backend/internal/handler/dto/mappers.go: 条件性填充notes
- frontend/src/api/redeem.ts: TypeScript接口添加notes
- frontend/src/views/user/RedeemView.vue: UI显示备注信息
2026-02-02 17:44:50 +08:00
小北
426ce616c0 feat: 支持在用户搜索中使用备注字段
- 在用户仓库的搜索过滤器中添加备注字段
- 管理员现在可以通过备注/标记搜索用户
- 使用不区分大小写的搜索(ContainsFold)

Changes:
- backend/internal/repository/user_repo.go: 添加 NotesContainsFold 到搜索条件
2026-02-02 17:41:27 +08:00
shaw
5cda979209 feat(deploy): 优化 Docker 部署体验,新增一键部署脚本
## 新增功能

- 新增 docker-compose.local.yml:使用本地目录存储数据,便于迁移和备份
- 新增 docker-deploy.sh:一键部署脚本,自动生成安全密钥(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)
- 新增 deploy/.gitignore:忽略运行时数据目录

## 优化改进

- docker-compose.local.yml 包含 PGDATA 环境变量修复,解决 PostgreSQL 18 Alpine 数据丢失问题
- 脚本自动设置 .env 文件权限为 600,增强安全性
- 脚本显示生成的凭证,方便用户记录

## 文档更新

- 更新 README.md(英文版):新增"快速开始"章节,添加部署版本对比表
- 更新 README_CN.md(中文版):同步英文版更新
- 更新 deploy/README.md:详细说明两种部署方式和迁移方法

## 使用方式

一键部署:
```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
docker-compose -f docker-compose.local.yml up -d
```

轻松迁移:
```bash
tar czf sub2api-complete.tar.gz deploy/
# 传输到新服务器后直接解压启动即可
```
2026-02-02 16:17:07 +08:00
Wesley Liddick
cc7e67b01a Merge pull request #445 from touwaeriol/fix/gemini-cache-token-billing
fix(billing): 修复 Gemini 接口缓存 token 统计
2026-02-02 15:22:46 +08:00
Wesley Liddick
6999a9c011 Merge pull request #444 from touwaeriol/fix/gemini-apikey-passthrough
feat(gateway): Gemini API Key 账户跳过模型映射检查,直接透传
2026-02-02 15:17:05 +08:00
shaw
bbdc8663d3 feat: 重新设计公告系统为Header铃铛通知
- 新增 AnnouncementBell 组件,支持 Modal 弹窗和 Markdown 渲染
- 移除 Dashboard 横幅和独立公告页面
- 铃铛位置在 Header 文档按钮左侧,显示未读红点
- 支持点击查看详情、标记已读、全部已读等操作
- 完善国际化,移除所有硬编码中文
- 修复 AnnouncementTargetingEditor watch 循环问题
2026-02-02 15:15:39 +08:00
liuxiongfeng
4bfeeecb05 fix(billing): 修复 Gemini 接口缓存 token 统计
extractGeminiUsage 函数未提取 cachedContentTokenCount,
导致计费时缓存读取 token 始终为 0。

修复:
- 提取 usageMetadata.cachedContentTokenCount
- 设置 CacheReadInputTokens 字段
- InputTokens 减去缓存 token(与 response_transformer 逻辑一致)
2026-02-02 14:01:17 +08:00
liuxiongfeng
bbc7b4aeed feat(gateway): Gemini API Key 账户跳过模型映射检查,直接透传
Gemini API Key 账户通常代理上游服务,模型支持由上游判断,
本地不需要预先配置模型映射。
2026-02-02 13:40:29 +08:00
Wesley Liddick
d3062b2e46 Merge pull request #434 from DuckyProject/feat/announcement-system-pr-upstream
feat(announcements): admin/user announcement system
2026-02-02 10:50:26 +08:00
Wesley Liddick
b7777fb46c Merge pull request #436 from iBenzene/feat/redis-tls-support
feat: add support for using TLS to connect to Redis
2026-02-02 10:02:25 +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
iBenzene
35f39ca291 chore: 修复了 redis.go 中代码风格(golangci-lint)的问题 2026-01-31 19:06:19 +08:00
iBenzene
f2e206700c feat: add support for using TLS to connect to Redis 2026-01-31 03:58:01 +08:00
cyhhao
adb77af1d9 fix: satisfy golangci-lint (nil checks, remove unused helpers) 2026-01-31 02:07:57 +08:00
cyhhao
3a34746668 refactor: stop rewriting tool descriptions; keep only system sentence rewrite 2026-01-31 02:01:51 +08:00
cyhhao
fe17058700 refactor: limit OpenCode keyword replacement to tool descriptions 2026-01-31 01:40:38 +08:00
ducky
9bee0a2071 chore: gofmt for golangci-lint 2026-01-30 17:28:53 +08:00
ducky
b7f69844e1 feat(announcements): add admin/user announcement system
Implements announcements end-to-end (admin CRUD + read status, user list + mark read) with OR-of-AND targeting. Also breaks the ent<->service import cycle by moving schema-facing constants/targeting into a new domain package.
2026-01-30 16:45:04 +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
cyhhao
602bf9c017 Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-30 13:21:25 +08:00
Wesley Liddick
c3d1891ccd Merge pull request #427 from touwaeriol/pr/upgrade-antigravity-ua
chore: upgrade Antigravity User-Agent to 1.15.8
2026-01-30 09:17:17 +08:00
shaw
4d8f2db924 fix: 更新所有CI workflow的Go版本验证至1.25.6 2026-01-30 08:57:37 +08:00
shaw
6599b366dc fix: 升级Go版本至1.25.6修复标准库安全漏洞
修复GO-2026-4341和GO-2026-4340两个标准库漏洞
2026-01-30 08:53:53 +08:00
liuxiongfeng
ba16ace697 chore: upgrade Antigravity User-Agent to 1.15.8 2026-01-30 08:14:52 +08:00
song
7ade9baa15 fix(gateway): 过滤 Gemini 请求中 parts 为空的消息
Gemini API 不接受 contents 数组中 parts 为空的消息,会返回 400 INVALID_ARGUMENT 错误。
添加 filterEmptyPartsFromGeminiRequest 函数在转发前过滤这类消息。

影响范围:ForwardGemini (antigravity) 和 ForwardNative (gemini)
2026-01-29 21:09:33 +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
cyhhao
fa454b1b99 fix: align Claude Code system banner with opencode latest 2026-01-29 15:37:07 +08:00
cyhhao
8375094c69 fix(oauth): match Claude CLI accept header and beta set 2026-01-29 15:31:29 +08:00
cyhhao
91079d3f15 chore(debug): emit Claude mimic fingerprint on credential-scope error 2026-01-29 15:17:46 +08:00
cyhhao
63412a9fcc chore(debug): log Claude mimic fingerprint 2026-01-29 03:13:14 +08:00
cyhhao
d98648f03b fix: rewrite OpenCode identity sentence to Claude Code 2026-01-29 03:03:40 +08:00
cyhhao
c37fe91672 fix(oauth): update Claude CLI fingerprint headers 2026-01-29 02:52:26 +08:00
cyhhao
4d40fb6b60 fix(oauth): merge anthropic-beta and force Claude Code headers in mimic mode 2026-01-29 02:36:28 +08:00
cyhhao
be3b788b8f fix: also prefix next system block with Claude Code banner 2026-01-29 02:03:54 +08:00
cyhhao
723e54013a fix(oauth): mimic Claude Code metadata and beta headers 2026-01-29 01:49:51 +08:00
cyhhao
4d566f68b6 chore: gofmt 2026-01-29 01:34:58 +08:00
cyhhao
31f817d189 fix: add newline separation for Claude Code system prompt 2026-01-29 01:28:43 +08:00
cyhhao
59231668c5 Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-29 01:16:36 +08:00
shaw
cadca752c4 修复SSE流式响应中usage数据被覆盖的问题 2026-01-28 18:36:21 +08:00
Wesley Liddick
edf215e6fd Merge pull request #409 from DuckyProject/feat/purchase-subscription-iframe
feat(purchase): 增加购买订阅 iframe 页面与配置
2026-01-28 17:28:47 +08:00
shaw
e12dd079fd 修复调度器空缓存导致的竞态条件bug
当新分组创建后立即绑定账号时,调度器会错误地将空快照视为有效缓存命中,
导致返回没有可调度的账号。现在空快照会触发数据库回退查询。
2026-01-28 17:26:32 +08:00
ducky
04a509d45e feat(purchase): 增加购买订阅 iframe 页面与配置
- 新增 /purchase 页面(iframe + 新窗口兜底)

- 管理员系统设置可配置开关与URL

- 非 simple mode 才在侧边栏展示入口
2026-01-28 13:54:32 +08:00
Wesley Liddick
269a659200 Merge pull request #406 from geminiwen/main
fix(openai-oauth): 改进错误处理和代理支持
2026-01-28 13:53:44 +08:00
Wesley Liddick
2c31bf46b5 Merge pull request #401 from slovx2/heihuzi_main
feat(gemini): 为 Gemini 原生平台添加图片计费支持
2026-01-28 13:51:14 +08:00
song
5b787334c8 antigravity: 转发优先 daily 2026-01-28 11:17:39 +08:00
song
f761afb1ef antigravity: 区分切换后重试次数 2026-01-28 00:01:03 +08:00
Gemini Wen
8f6639f825 fix(response): add nil check for c.Request in error logging
Prevents panic when ErrorFrom is called in test contexts where
gin.CreateTestContext doesn't set up an HTTP request.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:26:44 +08:00
Gemini Wen
fc17d9d7df chore: bump version to 0.1.61 and fix tests
- Update VERSION from 0.1.46 to 0.1.61
- Remove ForceHTTP2 tests for OpenAI OAuth client (ForceHTTP2 was removed)
- Update createOpenAIReqClient test to use new single-arg signature

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:22:45 +08:00
Gemini Wen
ab092e88a8 fix(openai-oauth): 改进错误处理和代理支持
- 使用 ApplicationError 返回详细错误信息到前端
- 添加 User-Agent: codex-cli/0.91.0
- 移除 ForceHTTP2 以兼容 HTTP 代理
- 修复代理获取失败时静默忽略的问题
- 500 错误时记录完整错误日志

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:13:01 +08:00
song
877c17251d feat(group): 添加 MCP XML 注入开关
- Group 新增 mcp_xml_inject 字段,控制 Antigravity 平台的 MCP XML 协议注入
- 默认启用,可在分组设置中关闭
- 修复 GetByKeyForAuth 遗漏查询 mcp_xml_inject 字段导致认证缓存值始终为 false 的问题
2026-01-27 13:09:56 +08:00
cyhhao
ffe43f6098 Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-27 11:09:11 +08:00
song
66f49b67d6 feat(accounts): 账号列表显示 Antigravity scope 级别限流状态
- 后端 DTO 新增 scope_rate_limits 字段,从 extra 提取限流信息
- 前端状态列显示 scope 级限流徽章(Claude/Gemini/Image)
- 清除速率限制时同时清除账号级和 scope 级限流(已有实现)
2026-01-27 11:04:41 +08:00
song
08d6dc5227 feat(ops): 运维界面展示 Antigravity 账号 scope 级别限流统计
在运维监控的并发/排队卡片中,为 Antigravity 平台账号显示各 scope
(claude/gemini_text/gemini_image) 的限流数量统计,便于管理员了解
哪些 scope 正在被限流。
2026-01-27 09:34:10 +08:00
shaw
56a1e29cdd fix(gateway): 修复 SSE 流式响应 usage 统计错误
message_delta 应完全覆盖 message_start 的 usage 数据,
而非仅在值为 0 时才更新。
2026-01-27 09:16:34 +08:00
song
7cea6b6fc9 feat(gemini): 为 Gemini 原生平台添加图片计费支持
对齐 Antigravity 平台的图片计费逻辑:
- 添加 extractImageSize() 方法提取图片尺寸
- Forward() 和 ForwardNative() 返回 ImageCount/ImageSize
- 支持分组自定义图片价格和倍率
2026-01-27 00:33:48 +08:00
song
4b57e80e6a fix: jsonb_set 嵌套路径无法创建多层 key 的问题
PostgreSQL jsonb_set 在 create_if_missing=true 时无法一次性创建多层嵌套路径。
例如设置 {antigravity_quota_scopes,gemini_image} 时,如果 antigravity_quota_scopes 不存在,
jsonb_set 不会自动创建外层 key,导致更新静默失败(affected=1 但数据未变)。

修复方案:嵌套两次 jsonb_set,先确保外层 key 存在,再设置内层值。

影响范围:
- SetAntigravityQuotaScopeLimit: Antigravity 平台按模型 scope 限流
- SetModelRateLimit: Anthropic 平台 Sonnet 模型限流
2026-01-26 23:40:48 +08:00
song
0059a232a6 feat(gemini): 为 Gemini 原生平台添加图片计费支持
对齐 Antigravity 平台的图片计费逻辑:
- 添加 extractImageSize() 方法提取图片尺寸
- Forward() 和 ForwardNative() 返回 ImageCount/ImageSize
- 支持分组自定义图片价格和倍率
2026-01-26 20:51:40 +08:00
shaw
45676fdc8d fix(ci): 转义 Telegram 消息中的 Markdown 特殊字符
修复发布通知发送失败的问题,原因是 tag message 中包含未闭合的
Markdown 格式标记(如 user_id 中的 _ 被解析为斜体开始)导致
Telegram API 返回解析错误。

添加 sed 命令转义 _、*、` 和 [ 字符,避免被 Telegram Markdown
解析器错误处理。
2026-01-26 11:07:08 +08:00
cyhhao
a161fcc89b Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-26 10:44:38 +08:00
Wesley Liddick
e32c5f534f Merge pull request #386 from IanShaw027/fix/openai-usage-limit-reset-time
fix(ratelimit): 修复 OpenAI usage_limit_reached 错误的重置时间解析
2026-01-26 10:22:42 +08:00
shaw
426d691c95 fix(urlvalidator): 移除 ValidateURLFormat 返回值的末尾斜杠
修复 API Key 账号 base_url 末尾带斜杠时导致的双斜杠问题
2026-01-26 10:21:41 +08:00
shaw
e9a4c8ab97 docs: 修改演示站点域名 2026-01-26 10:04:44 +08:00
ianshaw
a55cfebd09 fix(ratelimit): 修复 OpenAI usage_limit_reached 错误的重置时间解析
- 问题:OpenAI 的 usage_limit_reached 错误(需 37 小时重置)被错误地设置为 5 分钟
- 原因:handle429 只检查 Anthropic 响应头,没有解析 OpenAI 响应体中的 resets_in_seconds
- 修复:新增 parseOpenAIRateLimitResetTime 函数解析 OpenAI 响应体
- 影响:避免调度器不断尝试已达配额上限的账户
2026-01-26 09:57:44 +08:00
Wesley Liddick
34cc02f8c7 Merge pull request #393 from IanShaw027/fix/gemini-thought-signature-preserve
fix(gemini): 修复 thoughtSignature 跨账号验证错误
2026-01-26 09:23:46 +08:00
Wesley Liddick
624d9fddb7 Merge pull request #391 from geminiwen/main
fix(subscription): 修复订阅调整逻辑,已过期订阅从当前时间计算
2026-01-26 09:23:29 +08:00
Wesley Liddick
47fbe43324 Merge pull request #385 from DDZS987/fix/oauth-token-refresh-missing-project-id-retry
fix(oauth): 修复 OAuth 令牌刷新时 missing_project_id 误报问题
2026-01-26 09:22:48 +08:00
shaw
1245f07a2d feat(auth): 实现 TOTP 双因素认证功能
新增功能:
- 支持 Google Authenticator 等应用进行 TOTP 二次验证
- 用户可在个人设置中启用/禁用 2FA
- 登录时支持 TOTP 验证流程
- 管理后台可全局开关 TOTP 功能

安全增强:
- TOTP 密钥使用 AES-256-GCM 加密存储
- 添加 TOTP_ENCRYPTION_KEY 配置项,必须手动配置才能启用功能
- 防止服务重启导致加密密钥变更使用户无法登录
- 验证失败次数限制,防止暴力破解

配置说明:
- Docker 部署:在 .env 中设置 TOTP_ENCRYPTION_KEY
- 非 Docker 部署:在 config.yaml 中设置 totp.encryption_key
- 生成密钥命令:openssl rand -hex 32
2026-01-26 09:19:53 +08:00
ianshaw
839975b0cf feat(gemini): 支持 Gemini CLI 粘性会话与跨账号 thoughtSignature 清理
## 问题背景

1. Gemini CLI 没有明确的会话标识(如 Claude Code 的 metadata.user_id)
2. thoughtSignature 与具体上游账号强绑定,跨账号使用会导致 400 错误
3. 粘性会话切换账号或 cache 丢失时,旧签名会导致请求失败

## 解决方案

### 1. Gemini CLI 会话标识提取

- 从 `x-gemini-api-privileged-user-id` header 和请求体中的 tmp 目录哈希生成会话标识
- 组合策略:SHA256(privileged-user-id + ":" + tmp_dir_hash)
- 正则提取:`/\.gemini/tmp/([A-Fa-f0-9]{64})`

### 2. 跨账号 thoughtSignature 清理

实现三种场景的智能清理:

1. **Cache 命中 + 账号切换**
   - 粘性会话绑定的账号与当前选择的账号不同时清理

2. **同一请求内 failover 切换**
   - 通过 sessionBoundAccountID 跟踪,检测重试时的账号切换

3. **Gemini CLI + Cache 未命中 + 含签名**
   - 预防性清理,避免 cache 丢失后首次转发就 400
   - 仅对 Gemini CLI 请求且请求体包含 thoughtSignature 时触发

## 修改内容

### backend/internal/handler/gemini_v1beta_handler.go
- 添加 `extractGeminiCLISessionHash` 函数提取 Gemini CLI 会话标识
- 添加 `isGeminiCLIRequest` 函数识别 Gemini CLI 请求
- 实现账号切换检测与 thoughtSignature 清理逻辑
- 添加 `geminiCLITmpDirRegex` 正则表达式

### backend/internal/service/gateway_service.go
- 添加 `GetCachedSessionAccountID` 方法查询粘性会话绑定的账号 ID

### backend/internal/service/gemini_native_signature_cleaner.go (新增)
- 实现 `CleanGeminiNativeThoughtSignatures` 函数
- 递归清理 JSON 中的所有 thoughtSignature 字段
- 支持任意 JSON 顶层类型(object/array)

### backend/internal/handler/gemini_cli_session_test.go (新增)
- 测试 Gemini CLI 会话哈希提取逻辑
- 测试 tmp 目录正则匹配
- 覆盖有/无 privileged-user-id 的场景

## 影响范围

- 修复 Gemini CLI 多轮对话时账号切换导致的 400 错误
- 提高粘性会话的稳定性和容错能力
- 不影响其他客户端(Claude Code 等)的会话标识生成

## 测试

- 单元测试:go test -tags=unit ./internal/handler -run TestExtractGeminiCLISessionHash
- 单元测试:go test -tags=unit ./internal/handler -run TestGeminiCLITmpDirRegex
- 编译验证:go build ./cmd/server
2026-01-26 04:40:38 +08:00
ianshaw
8c1233393f fix(antigravity): 修复 Gemini 模型 thoughtSignature 被错误覆盖的问题
## 问题描述

在使用 Gemini 模型(gemini-3-flash-preview)时,出现 400 错误:
"Unable to submit request because Thought signature is not valid"

## 根本原因

在 `request_transformer.go` 的 `buildParts()` 函数中:
- 对于 `tool_use` 和 `thinking` 块,当 `allowDummyThought=true`(Gemini 模型)时
- 代码会无条件将客户端传入的真实 `thoughtSignature` 覆盖成 dummy 值
- 导致 Gemini API 验证签名失败(签名与上下文不匹配)

## 修复方案

修改 signature 处理逻辑:
1. **优先透传真实 signature**:如果客户端提供了有效的 signature,保留它
2. **缺失时才使用 dummy**:只有在 signature 缺失且是 Gemini 模型时,才使用 dummy signature
3. **Claude 模型特殊处理**:将 dummy signature 视为缺失,避免透传到需要真实签名的链路

## 修改内容

### request_transformer.go
- `thinking` 块(第 367-386 行):优先透传真实 signature
- `tool_use` 块(第 411-418 行):优先透传真实 signature

### request_transformer_test.go
- 修改测试用例名称,反映新的行为
- 新增测试用例验证"缺失时才使用 dummy"的逻辑

## 影响范围

- 修复 Gemini 模型在多轮对话中使用 tool_use 时的签名验证错误
- 不影响 Claude 模型的现有行为
- 提高跨账号切换时的稳定性

相关问题:#[issue_number]
2026-01-26 03:06:45 +08:00
Gemini Wen
9cdb0568cc fix(subscription): 修复订阅调整逻辑,已过期订阅从当前时间计算
- 已过期订阅延长时,从当前时间开始增加天数
- 已过期订阅不允许负向调整(缩短)
- 未过期订阅保持原逻辑,从原过期时间增减

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 18:12:15 +08:00
shaw
74e05b83ea fix(ratelimit): 修复 OpenAI 账号限流倒计时计算错误
- 解析 x-codex-* 响应头获取正确的重置时间
- 7d 限制用尽时使用 codex_7d_reset_after_seconds
- 提取 Normalize() 方法统一窗口规范化逻辑
2026-01-25 13:32:08 +08:00
Ubuntu
4ded9e7d49 fix(oauth): 为初始 OAuth 授权添加 LoadCodeAssist 重试机制
问题:
- 初始授权时 LoadCodeAssist 没有重试机制,失败后直接跳过
- 导致账号创建时就可能缺失 project_id
- 之后每次刷新都因为 missing_project_id 报错

修复:
- 统一使用 loadProjectIDWithRetry 方法(最多4次尝试)
- 初始授权和token刷新使用相同的重试策略
- 保留原注释说明部分账户可能没有 project_id

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 23:41:36 +08:00
Ubuntu
716272a1e2 fix(oauth): 彻底修复 project_id 丢失问题
根本原因:
- BuildAccountCredentials 只在 project_id 非空时才添加该字段
- LoadCodeAssist 失败时返回空字符串 → 新 credentials 不包含 project_id 键
- 普通合并逻辑只保留新 credentials 中不存在的键,无法覆盖空值

解决方案:
1. 在合并后特殊处理 project_id:如果新值为空但旧值非空,保留旧值
2. LoadCodeAssist 失败不再返回错误,只记录警告
3. Token 刷新成功(access_token 已更新)就不应标记账户为 error

改进效果:
- 即使 LoadCodeAssist 连续失败,已有的 project_id 也不会丢失
- 避免因临时网络问题将账户误标记为不可用
- 允许在下次刷新时自动重试获取 project_id

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 23:04:48 +08:00
shaw
9cc8352593 feat(auth): 密码重置邮件队列化与限流优化
- 邮件发送改为异步队列处理,避免并发导致发送失败
- 新增 Email 维度限流(30秒冷却期),防止邮件轰炸
- Token 验证使用常量时间比较,防止时序攻击
- 重构代码消除冗余,提取公共验证逻辑
2026-01-24 22:55:28 +08:00
shaw
43a1031e38 fix(test): 修复订阅相关测试失败问题
1. 使用未来日期(2099年)作为测试订阅的过期时间,避免
   normalizeSubscriptionStatus 将测试数据标记为过期
2. 修复 List 方法调用参数不足的问题(新增 sortBy/sortOrder 参数)
2026-01-24 21:10:02 +08:00
Wesley Liddick
a5547b2f30 Merge pull request #380 from DDZS987/fix/oauth-token-refresh-missing-project-id-retry
fix(oauth): 修复 OAuth 令牌刷新时 missing_project_id 误报问题
2026-01-24 20:29:43 +08:00
shaw
b0aa23540b feat(subscription): 订阅过期状态自动更新与服务端排序
- 新增 SubscriptionExpiryService 定时任务,每分钟更新过期订阅状态
- 订阅列表支持服务端排序(按过期时间、状态、创建时间)
- 实时显示正确的过期状态,无需等待定时任务
- 允许对已过期订阅进行续期操作
- DataTable 组件支持 serverSideSort 模式
2026-01-24 20:26:01 +08:00
yangjianbo
bece1b5201 perf(服务端): 启用 h2c 并保留 HTTP/1.1 回退 2026-01-24 20:01:03 +08:00
Ubuntu
ffaa6c4a17 fix(oauth): 修复 OAuth 令牌刷新时 missing_project_id 误报问题
问题描述:
在网络波动环境下,LoadCodeAssist 临时失败会被错误地标记为
"missing_project_id" 不可重试错误,导致账户被禁用。但实际上
账户配置正确,手动刷新后即可恢复。

解决方案:
1. 为 LoadCodeAssist 增加重试机制(最多4次,指数退避)
2. 区分真正的配置缺失和临时网络故障:
   - 如果之前有 project_id,本次获取失败只保留旧值,不报错
   - 只有从未获取过 project_id 且本次也失败时,才标记为缺失
3. 优化错误判断逻辑,避免误报

改进效果:
- 提高在复杂网络环境(如家宽代理)下的鲁棒性
- 减少因临时网络波动导致的服务中断
- 保持真正配置错误的检测能力

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 17:44:56 +08:00
Wesley Liddick
fbf72f0ec4 Merge pull request #377 from lynoot/fix/non-streaming-chunk-aggregation
fix(gateway): aggregate all text chunks in non-streaming Gemini responses
2026-01-24 08:49:10 +08:00
song
e316a923d4 fix(ops): count failover kinds with suffix 2026-01-24 01:14:44 +08:00
song
fd0370c07a Add invalid-request fallback routing 2026-01-23 22:24:46 +08:00
lynoot
909b8a8f9c fix(gateway): aggregate all text chunks in non-streaming Gemini responses
Previously, collectGeminiSSE() only returned the last chunk received
from the upstream streaming response when converting to non-streaming.
This caused incomplete responses where only the final text fragment
was returned to clients.

For example, a request asking to "count from 1 to 10" would only
return "\n" (the last chunk) instead of "1\n2\n3\n...\n10\n".

This was especially problematic for JSON structured output where
the opening brace "{" from the first chunk was lost, resulting
in invalid JSON like: colors": ["red", "blue"]}

The fix:
- Collect all text parts from each SSE chunk into a slice
- Merge all collected text parts into the final response
- Reuse the same pattern as handleGeminiStreamToNonStreaming
  in antigravity_gateway_service.go

Fixes: non-streaming responses returning incomplete text
Fixes: structured output (JSON schema) returning invalid JSON
2026-01-23 13:54:09 +00:00
song
316f2fee21 feat(ops): add account switch metrics and trend 2026-01-23 19:39:48 +08:00
shaw
4a0fe3b143 feat(gateway): 增加 SUGGESTION MODE 请求拦截
扩展现有的预热请求拦截功能,新增对 SUGGESTION MODE 请求的拦截:
- 检测 messages 最后一条 user 消息是否以 [SUGGESTION MODE: 开头
- 拦截后返回空内容响应,节省 token 消耗
- 重构检测逻辑,合并为单一函数,只解析一次 JSON
2026-01-23 16:57:25 +08:00
shaw
a1292fac81 feat(oauth): 支持Anthropic的Team账号使用sk授权 2026-01-23 16:30:12 +08:00
shaw
7f98be4f91 fix(oauth): 更新 Anthropic 账号 OAuth 参数,同步最新客户端 2026-01-23 16:00:42 +08:00
shaw
fd73b8875d feat(frontend): 优化账号限流状态显示,直接展示倒计时 2026-01-23 15:48:25 +08:00
shaw
f9ab1daa3c feat: 保存并显示OAuth账号邮箱地址 2026-01-23 15:17:47 +08:00
shaw
d27b847442 fix(test): 修复测试中引用不存在的函数名
测试文件引用了 IsTokenVersionStale 函数,但实际函数名为 CheckTokenVersion,导致 CI 构建失败
2026-01-23 10:58:30 +08:00
song
3002c7a17f Clamp Claude maxOutputTokens to 64000 2026-01-23 10:44:21 +08:00
shaw
dac6bc2228 fix(token-cache): 版本过时时使用最新token而非旧token
上次修复(2665230)只阻止了写入缓存,但仍返回旧token导致403。
现在版本过时时直接使用DB中的最新token返回。

- 重构 IsTokenVersionStale 为 CheckTokenVersion,返回最新account
- 消除重复DB查询,复用版本检查时已获取的account
2026-01-23 10:29:52 +08:00
Wesley Liddick
4bd3dbf2ce Merge pull request #354 from DuckyProject/fix/frontend-table
feat(frontend): 账号表格默认排序/持久化 + 自动刷新 + 更多菜单外部关闭
2026-01-22 21:17:48 +08:00
Wesley Liddick
226df1c23a Merge pull request #358 from 0xff26b9a8/main
fix(antigravity): 修复非流式 Claude To Antigravity 响应内容为空的问题
2026-01-22 21:16:54 +08:00
shaw
2665230a09 fix(token-cache): 修复异步刷新与请求线程的缓存竞态条件
- 新增 _token_version 版本号机制,防止过期 token 污染缓存
- TokenRefreshService 刷新成功后写入版本号并清除缓存
- TokenProvider 写入缓存前检查版本,过时则跳过
- ClearError 时同步清除 token 缓存
2026-01-22 21:09:28 +08:00
0xff26b9a8
4f0c2b794c style: gofmt antigravity_gateway_service.go 2026-01-22 14:38:55 +08:00
0xff26b9a8
e756064c19 fix(antigravity): 修复非流式 Claude To Antigravity 响应内容为空的问题
- 修复 TransformGeminiToClaude 的 JSON 解析逻辑,当 V1InternalResponse
  解析成功但 candidates 为空时,尝试直接解析为 GeminiResponse 格式
- 修复 handleClaudeStreamToNonStreaming 收集流式响应的逻辑,累积所有
  chunks 的内容而不是只保留最后一个(最后一个 chunk 通常 text 为空)
- 新增 mergeCollectedPartsToResponse 函数,合并所有类型的 parts
  (text、thinking、functionCall、inlineData),保持原始顺序
- 连续的普通 text parts 合并为一个,thinking/functionCall/inlineData 保持原样
2026-01-22 14:17:59 +08:00
Wesley Liddick
17dfb0af01 Merge pull request #346 from 0xff26b9a8/main
refactor(antigravity): 提取并同步 Schema 清理逻辑至 schema_cleaner.go
2026-01-22 08:46:11 +08:00
ducky
ff74f517df feat(frontend): 账号表格默认排序/持久化 + 自动刷新 + 更多菜单外部关闭 2026-01-21 22:43:25 +08:00
song
207e09500a feat(antigravity): 支持按模型类型配置重试次数
新增环境变量:
- GATEWAY_ANTIGRAVITY_MAX_RETRIES_CLAUDE
- GATEWAY_ANTIGRAVITY_MAX_RETRIES_GEMINI_TEXT
- GATEWAY_ANTIGRAVITY_MAX_RETRIES_GEMINI_IMAGE

未设置时回退到平台级 GATEWAY_ANTIGRAVITY_MAX_RETRIES
2026-01-21 20:48:36 +08:00
song
52c745bc62 feat: 为 Antigravity 平台启用拦截预热请求功能 2026-01-21 20:40:45 +08:00
0xff26b9a8
498c6cfae9 fix: 修复 schema 清理逻辑 2026-01-21 12:08:16 +08:00
0xff26b9a8
71f8b9e473 refactor(antigravity): 提取并同步 Schema 清理逻辑至 schema_cleaner.go
主要变更:
1. 重构代码结构:
   - 将 CleanJSONSchema 及其相关辅助函数从 request_transformer.go 提取到独立的 schema_cleaner.go 文件中,实现逻辑解耦。

2. 逻辑优化与修正:
   - 参考 Antigravity-Manager (json_schema.rs) 的实现逻辑,修正了 Schema 清洗策略。
2026-01-21 12:08:16 +08:00
song
3a31fa4768 fix: 429 限流时更新账号 last_used_at
在设置限流标记时同时更新 last_used_at,使得刚触发 429 的账号
在后续调度中优先级降低,让其他账号有更多被选中的机会。
2026-01-21 11:50:38 +08:00
0xff26b9a8
477a9a180f fix: 修复 schema 清理逻辑 2026-01-21 10:58:39 +08:00
0xff26b9a8
da48df06d2 refactor(antigravity): 提取并同步 Schema 清理逻辑至 schema_cleaner.go
主要变更:
1. 重构代码结构:
   - 将 CleanJSONSchema 及其相关辅助函数从 request_transformer.go 提取到独立的 schema_cleaner.go 文件中,实现逻辑解耦。

2. 逻辑优化与修正:
   - 参考 Antigravity-Manager (json_schema.rs) 的实现逻辑,修正了 Schema 清洗策略。
2026-01-20 23:41:53 +08:00
cyhhao
65e69738cc Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-20 22:46:23 +08:00
song
549c134bb8 chore: gofmt antigravity gateway service 2026-01-20 19:16:43 +08:00
song
d206721fc1 feat: make antigravity max retries configurable 2026-01-20 19:12:19 +08:00
Wesley Liddick
39fad63ccf Merge pull request #345 from whoismonay/main
mod(frontend): 管理员订阅/兑换码分组选择展示备注
2026-01-20 16:22:53 +08:00
Wesley Liddick
5602d02b1b Merge pull request #343 from mt21625457/main
fix(调度): 完善粘性会话清理与账号调度刷新 和 启用 OpenAI OAuth HTTP/2 并修复清理任务 lint
2026-01-20 16:05:53 +08:00
shaw
81989eed1c test: add promo_code_enabled to API contract test 2026-01-20 16:02:49 +08:00
shaw
192efb84a0 feat(promo-code): complete promo code feature implementation
- Add promo_code_enabled field to SystemSettings and PublicSettings DTOs
- Add promo code validation in registration flow
- Add admin settings UI for promo code configuration
- Add i18n translations for promo code feature
2026-01-20 15:56:26 +08:00
shaw
8672347f93 fix(settings): add missing promo_code_enabled field in public settings API
The field was defined in DTO but not mapped in handler response.
2026-01-20 15:49:57 +08:00
yangjianbo
5e5d4a513b feat: 移动镜像脚本位置 2026-01-20 15:11:27 +08:00
墨颜
88b6358472 build(frontend): vite 加载开发环境变量
- 使用 loadEnv 读取 VITE_DEV_PROXY_TARGET/VITE_DEV_PORT
- 注入 public settings 与 dev proxy 使用同源后端地址
2026-01-20 15:04:18 +08:00
墨颜
dd8d5e2c42 mod(frontend): 订阅分组下拉显示备注
- 订阅管理/分配订阅:下拉项展示分组备注
- 兑换码/订阅类型:下拉项展示分组备注
- 复用 GroupOptionItem/GroupBadge 保持一致体验
2026-01-20 15:02:48 +08:00
shaw
d91e2328fb test(tlsfingerprint): add multi-profile fingerprint verification test
Add TestAllProfiles to verify TLS fingerprint configurations from
config.yaml against tls.peet.ws. Tests check JA4 cipher hash (stable
part) to validate fingerprint spoofing works correctly.
2026-01-20 14:53:15 +08:00
song
64795a03e3 新增账号凭证邮箱查询接口 2026-01-20 14:17:10 +08:00
yangjianbo
2a16735495 fix(测试): 修复 SelectAccountWithLoadAwareness 调用缺少参数
为 gateway_multiplatform_test.go 中的 SelectAccountWithLoadAwareness
调用添加缺少的第6个参数 metadataUserID,修复 CI 测试编译错误。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 14:16:46 +08:00
yangjianbo
292f25f9ca Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-01-20 14:02:08 +08:00
yangjianbo
c92e37775a Merge branch 'dev' 2026-01-20 13:57:08 +08:00
cyhhao
c8e2f614fa Merge branch 'main' of github.com:Wei-Shaw/sub2api 2026-01-20 13:53:32 +08:00
yangjianbo
f6ed3d1456 Merge branch 'test' into dev 2026-01-20 11:59:13 +08:00
yangjianbo
84686753e8 Merge branch 'test' of https://github.com/mt21625457/aicodex2api into test 2026-01-20 11:51:44 +08:00
yangjianbo
91f01309da fix(调度): 完善粘性会话清理与账号调度刷新
- Update/BulkUpdate 按不可调度字段触发缓存刷新
- GatewayCache 支持多前缀会话键清理
- 模型路由与混合调度优化粘性会话处理
- 补充调度与缓存相关测试覆盖
2026-01-20 11:40:55 +08:00
song
86d63f919d feat(antigravity): 支持秒级 fallback 冷却时间 2026-01-20 11:38:40 +08:00
yangjianbo
57a1fc9d33 style(仓储): 格式化账号仓储
- gofmt 修正 lint 格式提示
2026-01-20 11:30:36 +08:00
shaw
c95a864975 docs: add TLS fingerprint tool link 2026-01-20 11:30:10 +08:00
yangjianbo
7a83db6180 fix(调度): 完善粘性会话清理与账号调度刷新
- Update/BulkUpdate 按不可调度字段触发缓存刷新
- GatewayCache 支持多前缀会话键清理
- 模型路由与混合调度优化粘性会话处理
- 补充调度与缓存相关测试覆盖
2026-01-20 11:19:32 +08:00
song
c43aa22cdb feat(antigravity): 支持按映射模型计费 2026-01-20 11:02:08 +08:00
Wesley Liddick
a8513da7ff Merge pull request #335 from geminiwen/main
feat(subscription): 支持调整订阅时长(延长/缩短)
2026-01-20 08:52:19 +08:00
song
d1a6303e49 fix(antigravity): 修复 Claude 非流式响应丢失 2026-01-20 00:52:27 +08:00
Gemini Wen
53534d3956 style(admin): 统一列设置按钮位置到刷新按钮右侧
将订阅管理和账号管理页面的列设置按钮移动到刷新按钮右侧,
与用户管理页面保持一致的布局。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 00:18:51 +08:00
Gemini Wen
cc07a0e295 feat(subscription): 支持调整订阅时长(延长/缩短)
- 将"延长订阅"功能改为"调整订阅",支持正数延长、负数缩短
- 后端验证:调整天数范围 -36500 到 36500,缩短后剩余天数必须 > 0
- 前端同步更新界面文案和验证逻辑

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 00:11:30 +08:00
Wesley Liddick
e7bc62500b Merge pull request #333 from whoismonay/main
fix: 普通用户接口移除管理员敏感字段透传
2026-01-19 21:35:51 +08:00
墨颜
c8fb9ef3a5 style(dto): 修复 gofmt 格式问题
- 修复 mappers.go 中 Notes 字段的对齐格式
- 修复 types.go 中 BulkAssignResult 结构体字段的 JSON tag 对齐

修复 golangci-lint 检查中的 gofmt 格式错误
2026-01-19 21:26:30 +08:00
Wesley Liddick
eb5e6214bc Merge pull request #332 from geminiwen/main
fix: 修复手动刷新令牌后缓存未清除导致403错误的问题
2026-01-19 20:53:06 +08:00
shaw
568d6ee10e fix: 修复测试缺少新增设置字段 2026-01-19 20:52:05 +08:00
墨颜
6aef1af76e fix(redeem): 用户兑换历史不返回备注
- 用户侧 RedeemCode DTO 移除 notes 字段,避免泄露内部备注\n- 新增 AdminRedeemCode,并调整管理员兑换码接口继续返回 notes\n- 增加 /api/v1/redeem/history 契约测试,确保用户侧响应不包含 notes
2026-01-19 20:09:35 +08:00
Gemini Wen
a54852e129 fix: 补充API契约测试中缺失的hide_ccs_import_button字段
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 20:06:36 +08:00
Gemini Wen
668118def1 fix: 修复遗漏的测试文件更新和lint错误
- 更新api_contract_test.go以匹配NewAccountHandler新增的tokenCacheInvalidator参数
- 修复errcheck lint错误,显式忽略c.Error()返回值

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 19:58:09 +08:00
yangjianbo
73e6b160f8 feat(认证): 启用 OpenAI OAuth HTTP/2 并修复清理任务 lint
为共享 req 客户端增加 HTTP/2 选项与缓存隔离
OpenAI OAuth 超时提升到 120s,并按协议控制强制
新增客户端池与 OAuth 客户端单测覆盖
修复 usage cleanup 相关 errcheck/ineffassign/staticcheck 并统一格式

测试: make test
2026-01-19 19:50:57 +08:00
Gemini Wen
6fec141de6 fix: 修复手动刷新令牌后缓存未清除导致403错误的问题
手动刷新令牌后,新token保存到数据库但Redis缓存未清除,
导致下游请求仍然使用旧的失效token,上游API返回403错误。

修复方案:在AccountHandler中注入TokenCacheInvalidator,
刷新令牌成功后调用InvalidateToken清除缓存。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 19:40:43 +08:00
墨颜
31cde6c555 fix(subscriptions): 用户订阅不返回分配信息
- 用户侧 UserSubscription DTO 移除 assigned_by/assigned_at/notes/assigned_by_user 等管理员字段\n- 新增 AdminUserSubscription,并调整管理员订阅接口与批量分配结果使用\n- 增加 /api/v1/subscriptions 契约测试,确保用户侧响应不包含上述字段
2026-01-19 19:35:13 +08:00
shaw
b1a980f344 feat: 添加隐藏CCS导入按钮的设置选项
在管理后台设置页面新增开关,允许管理员隐藏API Keys页面的"导入CCS"按钮
2026-01-19 19:25:16 +08:00
墨颜
00d9fbd220 fix(user): 普通用户接口不返回备注
- 用户侧 dto.User 移除 notes 字段,避免泄露管理员备注\n- 新增 dto.AdminUser 并调整 /admin/users 系列接口使用\n- 前端拆分 User/AdminUser,管理端用户页面使用 AdminUser\n- 更新契约测试:/api/v1/auth/me 响应不包含 notes
2026-01-19 19:23:51 +08:00
墨颜
4f4c9679bf fix(groups): 用户分组不下发内部路由信息
- 普通用户 Group DTO 移除 model_routing/account_count/account_groups,避免泄露内部路由与账号信息\n- 新增 AdminGroup DTO,并仅在管理员分组接口返回完整字段\n- 前端拆分 Group/AdminGroup,管理端页面与 API 使用 AdminGroup\n- 增加 /api/v1/groups/available 契约测试,防止回归
2026-01-19 18:58:42 +08:00
shaw
3dab71729d feat: usage接口支持TLS指纹和缓存User-Agent 2026-01-19 17:06:16 +08:00
墨颜
2f6f758670 fix(usage): 用户使用记录不下发账号计费倍率
- 将 usage log DTO 拆分为用户/管理员两类
- 用户接口不返回 account_rate_multiplier/ip_address/account
- 管理员接口保留管理员字段
- 补充契约测试防止回归
2026-01-19 17:05:42 +08:00
Call White
c0347cde85 Merge pull request #2 from cyhhao/fix/claude-oauth-compat
Fix/claude oauth compat
2026-01-19 16:56:49 +08:00
shaw
090c8981dd fix: 更新Claude OAuth授权配置以匹配最新规范
- 更新TokenURL和RedirectURI为platform.claude.com
- 更新scope定义,区分浏览器URL和内部API调用
- 修正state/code_verifier生成算法使用base64url编码
- 修正授权URL参数顺序并添加code=true
- 更新token交换请求头匹配官方实现
- 清理未使用的类型和函数
2026-01-19 16:40:06 +08:00
cyhhao
2f2e76f9c6 fix(gateway): gate streaming tool rewrites behind mimic 2026-01-19 16:20:24 +08:00
cyhhao
dd7f21244b merge: resolve conflicts with main 2026-01-19 15:35:31 +08:00
cyhhao
49be9d08f3 fix(网关): OAuth 请求统一 user_id 与指纹 2026-01-19 15:02:00 +08:00
cyhhao
bba5b3c037 fix(网关): OAuth 请求统一 user_id 与指纹 2026-01-19 15:01:32 +08:00
cyhhao
26298c4a5f fix(openai): emit OpenAI-compatible SSE error events 2026-01-19 13:53:39 +08:00
shaw
fbb572948d fix: 修复会话数量查询使用错误的超时配置 2026-01-19 11:45:04 +08:00
shaw
a652b513d3 fix: handle 400 error for disabled organization 2026-01-19 10:54:40 +08:00
shaw
ccfeaeb22d feat: 新增会话ID伪装功能,优化日志系统
- 新增 session_id_masking_enabled 配置,启用后将在15分钟内固定
  metadata.user_id 中的 session ID
- TLS fingerprint 模块日志从自定义 debugLog 迁移到 slog
- main.go 添加 slog 初始化,根据 gin mode 设置日志级别
- 前端创建/编辑账号模态框添加会话ID伪装开关
- 多语言支持(中英文)
2026-01-19 10:22:13 +08:00
shaw
4c12799a95 fix: 补充测试桩缺失的接口方法 2026-01-19 09:28:11 +08:00
Wesley Liddick
0f8d42c577 Merge pull request #327 from mt21625457/main
feat(usage): 添加清理任务与统计过滤
2026-01-19 09:18:00 +08:00
Wesley Liddick
03c7578713 Merge pull request #325 from slovx2/main
fix(antigravity): 修复Antigravity 频繁429的问题,以及一系列优化,配置增强
2026-01-19 09:17:15 +08:00
shaw
de6797c560 fix: 修复5小时窗口费用不重置的问题
- 新增 GetCurrentWindowStartTime() 方法,当窗口过期时自动使用新的预测窗口开始时间
- UpdateSessionWindow 更新窗口时间后触发 outbox 事件同步调度器缓存
- 统一所有窗口费用查询入口使用新方法
2026-01-19 09:13:15 +08:00
cyhhao
eb7d830296 fix(网关): 修复流式 tool 输入参数转换 2026-01-19 03:57:33 +08:00
cyhhao
02db4c7671 fix(网关): 修复流式 tool 输入参数转换 2026-01-19 03:53:08 +08:00
cyhhao
a05b8b56e3 fix(网关): SSE 缓冲 input_json_delta 反向转换 2026-01-19 03:46:31 +08:00
cyhhao
eca3898410 fix(网关): SSE 缓冲 input_json_delta 反向转换 2026-01-19 03:46:09 +08:00
shaw
46ae08ecb7 fix: 补充测试桩缺失的接口方法 2026-01-18 22:23:03 +08:00
shaw
2028cc29b7 fix: 修复多个管理后台问题
- 分页接口 page_size 最大限制从 100 改为 1000
- 通过 Redis Pub/Sub 实现跨实例认证缓存失效
- 允许订阅类型分组编辑计费倍率
- 账号计费倍率支持 3 位小数
2026-01-18 22:13:47 +08:00
shaw
f6360e0bf3 fix: 移除未使用的 extractSessionUUID 函数
修复 golangci-lint unused 检查报错
2026-01-18 20:15:02 +08:00
shaw
9abda1bc59 feat(tls): 新增 TLS 指纹模拟功能 2026-01-18 20:08:40 +08:00
yangjianbo
2a94cc76a6 fix(软删除): 增强错误处理,确保软删除操作中的错误类型正确 2026-01-18 16:51:26 +08:00
yangjianbo
150b315a7b fix(软删除): 修复软删除钩子的客户端调用逻辑,确保正确处理变更 2026-01-18 16:46:22 +08:00
shaw
a07174c191 fix: 修复会话限制功能并在创建账号时支持配额控制 2026-01-18 16:41:15 +08:00
yangjianbo
fb839ae6ca fix(软删除): 修复删除钩子调用链并跳过无Docker测试
软删除钩子改用 next.Mutate 处理更新,避免 mutation 类型不匹配
集成测试检测 Docker 可用性,无 Docker 自动跳过
2026-01-18 16:10:54 +08:00
yangjianbo
bdc426a774 Merge branch 'main' into dev 2026-01-18 15:55:58 +08:00
Wesley Liddick
32fff3798c Merge pull request #326 from geminiwen/main
feat(admin): 添加账号管理和订阅管理的列设置功能
2026-01-18 14:41:02 +08:00
Wesley Liddick
2b02c6635d Merge pull request #323 from IanShaw027/fix/ops-error-classification-consistency
fix(ops): 统一 request-errors 接口与 SLA 计算的错误分类逻辑
2026-01-18 14:32:04 +08:00
yangjianbo
771baa66ee feat(界面): 优化分页跳转与页大小显示
分页组件支持隐藏每页条数选择器并新增跳转页输入
清理任务列表启用跳转页并固定每页 5 条
补充中英文分页文案
2026-01-18 14:31:22 +08:00
Wesley Liddick
a82029b0cf Merge pull request #318 from IanShaw027/main
fix(openai): OpenCode 兼容性增强 - 工具过滤和粘性会话修复
2026-01-18 14:30:53 +08:00
Wesley Liddick
0c2a901af4 Merge pull request #317 from IanShaw027/fix/gemini-issue
fix(gemini,group): 更新 Gemini 模型配置并补齐 SIMPLE 默认分组
2026-01-18 14:30:42 +08:00
yangjianbo
bd18f4b8ef feat(清理任务): 引入Ent存储并补充日志与测试
新增 usage_cleanup_task Ent schema 与仓储实现,支持清理任务排序分页
补充清理任务全链路日志、仪表盘重算触发及 UI 过滤调整
完善 repository/service 单测并引入 sqlite 测试依赖
2026-01-18 14:18:28 +08:00
yangjianbo
bf7b79f2f0 fix(数据库): 优化任务状态更新查询,使用别名提高可读性 2026-01-18 11:58:53 +08:00
Gemini Wen
45e8598d32 feat(admin): 添加账号管理和订阅管理的列设置功能
- 账号管理新增代理列显示和列设置下拉菜单
- 订阅管理新增列设置,支持用户列在邮箱/用户名间切换
- 列设置持久化到 localStorage
- 统一列设置图标样式

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 11:57:07 +08:00
yangjianbo
8391d480c9 fix(数据库): 补充分组列回填前置保护 2026-01-18 11:52:59 +08:00
yangjianbo
d17f853a5f fix(数据库): 补充允许分组列兼容迁移 2026-01-18 11:45:43 +08:00
yangjianbo
ef5a41057f feat(usage): 添加清理任务与统计过滤 2026-01-18 10:52:18 +08:00
song
c115c9e048 fix: address lint errors 2026-01-18 01:22:40 +08:00
song
6941315432 feat: add antigravity web search support 2026-01-18 01:09:40 +08:00
song
8b071cc665 fix(antigravity): restore signature retry and base order 2026-01-17 22:50:50 +08:00
song
959f6c538a fix(antigravity): remove thinking sanitation 2026-01-17 22:21:48 +08:00
song
217b3b59c0 fix(antigravity): drop MarkUnavailable 2026-01-17 21:59:32 +08:00
song
ec916a3197 fix(antigravity): remove signature retry 2026-01-17 21:56:57 +08:00
song
22eb72e0f9 fix(antigravity): restore url fallback behavior 2026-01-17 21:50:09 +08:00
song
07ba64c666 fix(antigravity): handle url-level 429 without failover 2026-01-17 21:37:32 +08:00
song
f22bc59fe3 fix(antigravity): route signature retry through url fallback 2026-01-17 21:15:33 +08:00
song
0ce8666cc0 Revert "Revert "fix(antigravity): Claude 模型透传 tool_use 的 signature""
This reverts commit 5427a9e422.
2026-01-17 21:09:59 +08:00
song
5427a9e422 Revert "fix(antigravity): Claude 模型透传 tool_use 的 signature"
This reverts commit 81b865b89d.
2026-01-17 20:41:06 +08:00
cyhhao
6901b64fce merge: sync upstream changes 2026-01-17 18:30:16 +08:00
song
5e9f5efbe3 chore: log antigravity signature retry 429 2026-01-17 18:22:53 +08:00
song
a7a0017aa8 chore: gofmt antigravity gateway service 2026-01-17 18:22:43 +08:00
cyhhao
32c47b1509 fix(gateway): satisfy golangci-lint checks 2026-01-17 18:16:34 +08:00
song
9078b17a41 test: add antigravity rate limit coverage 2026-01-17 18:15:45 +08:00
song
14a3694a9a chore: set antigravity fallback cooldown default to 1 2026-01-17 18:03:45 +08:00
song
b9b4db3df5 Merge upstream/main 2026-01-17 18:00:07 +08:00
ianshaw
bc1d7edc58 fix(ops): 统一 request-errors 和 SLA 的错误分类逻辑
修复 request-errors 接口与 Dashboard Overview SLA 计算不一致的问题:
- errors 视图现在只排除业务限制错误(余额不足、并发限制等)
- 上游 429/529 错误现在包含在 errors 视图中,与 SLA 计算保持一致
- excluded 视图现在只显示业务限制错误

这确保了 request-errors 接口和 Dashboard 的 error_count_sla 使用相同的过滤逻辑。
2026-01-17 17:57:40 +08:00
Call White
39e430018b Merge pull request #1 from cyhhao/fix/responses-stream-cancel
fix(gateway): avoid invalid SSE error on canceled stream
2026-01-17 17:15:35 +08:00
nick8802754751
6549a40cf4 refactor: 使用 ConfirmDialog 组件替代原生 confirm() 对话框
- EditAccountModal 和 CreateAccountModal 使用 ConfirmDialog 组件
- 保存 pending payload 供确认后重试
- 添加 mixedChannelWarningTitle 翻译

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-01-17 16:16:47 +08:00
nick8802754751
4e75d8fda9 fix: 添加混合渠道警告确认框和过滤 prompt_cache_retention 参数
- 前端: EditAccountModal 和 CreateAccountModal 添加 409 mixed_channel_warning 处理
- 前端: 弹出确认框让用户确认混合渠道风险
- 后端: 过滤 OpenAI 请求中的 prompt_cache_retention 参数(上游不支持)
- 添加中英文翻译

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-01-17 16:06:44 +08:00
song
5a6f60a954 fix(antigravity): 区分 URL 级别和账户配额级别的 429 限流
- "Resource has been exhausted" → URL 级别限流,立即切换 URL
- "exhausted your capacity on this model" → 账户配额限流,重试 3 次(指数退避)后标记限流
2026-01-17 11:11:18 +08:00
IanShaw027
a61cc2cb24 fix(openai): 增强 Codex 工具过滤和参数标准化
- codex_transform: 过滤无效工具,支持 Responses-style 和 ChatCompletions-style 格式
- tool_corrector: 添加 fetch 工具映射,修正 bash/edit 参数命名规范
2026-01-17 11:00:07 +08:00
song
31933c8a60 fix: 删除未使用的字段修复 lint 错误 2026-01-17 10:40:28 +08:00
song
78bccd032d refactor(antigravity): 提取公共重试循环函数减少重复代码
- 新增 antigravityRetryLoop 函数统一处理 Forward 和 ForwardGemini 的重试逻辑
- 429 日志增加 base_url 字段便于调试
- 删除重复的 shouldRetryUpstreamError 方法
2026-01-17 10:28:31 +08:00
IanShaw027
ae21db77ec fix(openai): 使用 prompt_cache_key 兜底粘性会话
opencode 请求不带 session_id/conversation_id,导致粘性会话失效。现在按 header 优先、prompt_cache_key 兜底生成 session hash,并补充单测验证优先级。
2026-01-17 02:31:16 +08:00
song
ac7503d95f fix(antigravity): 429 时也切换 URL 重试
- 429 优先切换到下一个 URL 重试
- 只有所有 URL 都返回 429 时才限流账户并返回错误
- 与 client.go 中的逻辑保持一致
2026-01-17 02:14:57 +08:00
song
69c4b17a9b feat(antigravity): 动态 URL 排序,最近成功的优先使用
- URLAvailability 新增 lastSuccess 字段追踪最近成功的 URL
- GetAvailableURLs 返回列表时优先放置 lastSuccess
- 所有 Antigravity API 调用成功后调用 MarkSuccess 更新优先级
2026-01-17 01:54:14 +08:00
IanShaw027
a7165b0f73 fix(group): SIMPLE 模式启动补齐默认分组 2026-01-17 01:53:51 +08:00
song
cc0fca35ec feat(antigravity): 同步 Antigravity-Manager 的请求逻辑
- System Prompt: 改为简短版,添加 OpenCode 过滤、MCP XML 协议注入、SYSTEM_PROMPT_END 标记
- HTTP Headers: 只保留 Content-Type/Authorization/User-Agent,移除 Accept 和 Host
- User-Agent: 改为 antigravity/1.11.9 windows/amd64
- requestType: 动态判断 (agent/web_search/image_gen)
- BaseURLs: 添加 daily sandbox 备用 URL
- Fallback: 扩展触发条件 (429/408/404/5xx)
2026-01-17 01:49:42 +08:00
cyhhao
8917a3ea8f fix(网关): 修复 golangci-lint 2026-01-17 00:27:36 +08:00
Wesley Liddick
dae0d5321f Merge pull request #315 from mt21625457/main
perf(前端): 优化页面加载性能和用户体验 和 修复静态 import 导致入口文件膨胀问题
2026-01-16 23:55:45 +08:00
shaw
34415db7ed fix: 修复 api_contract_test 缺少 SessionLimitCache 参数的问题 2026-01-16 23:53:54 +08:00
IanShaw027
28e46e0e7c fix(gemini): 更新 Gemini 模型列表配置
- 移除已弃用的 1.5 系列模型
- 调整模型优先级顺序(2.0 Flash > 2.5 Flash > 2.5 Pro > 3.0 Preview)
- 同步前后端模型配置
- 更新相关测试用例和默认模型选择逻辑
2026-01-16 23:47:42 +08:00
cyhhao
0c011b889b fix(网关): Claude Code OAuth 补齐 oauth beta 2026-01-16 23:38:09 +08:00
cyhhao
0962ba43c0 fix(网关): 补齐非 Claude Code OAuth 兼容 2026-01-16 23:38:09 +08:00
cyhhao
b8c48fb477 fix(网关): 区分 Claude Code OAuth 适配 2026-01-16 23:38:09 +08:00
cyhhao
2a7d04fec4 fix(网关): 对齐 Claude OAuth 请求适配 2026-01-16 23:36:57 +08:00
shaw
7379423325 feat: 添加5h窗口费用控制和会话数量限制
- 支持Anthropic OAuth/SetupToken账号的5h窗口费用阈值控制
- 支持账号级别的并发会话数量限制
- 使用Redis缓存窗口费用(30秒TTL)减少数据库压力
- 费用计算基于标准费用(不含账号倍率)
2026-01-16 23:36:52 +08:00
cyhhao
bd854e1750 fix(网关): Claude Code OAuth 补齐 oauth beta 2026-01-16 23:15:52 +08:00
yangjianbo
74a3c74514 perf(Caddy): 添加静态资源长期缓存配置
- 为 /assets/* 设置 1 年缓存 + immutable 标记
- 包含 logo.png 和 favicon.ico
- 移除可能干扰缓存的 Pragma/Expires 头

效果:
- 浏览器缓存命中后不再发送请求
- Cloudflare CDN 可正确缓存静态资源
- 重复访问页面秒开

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:49:05 +08:00
程序猿MT
3d6d131889 Merge branch 'Wei-Shaw:main' into main 2026-01-16 22:31:14 +08:00
yangjianbo
b0569d873a perf(路由预加载): 修复静态 import 导致入口文件膨胀问题
问题:
- 原实现使用静态 import() 映射表
- Rollup 静态分析时将所有 37 个视图组件引用打包进 index.js
- 导致首次加载时需要解析大量未使用的 import 语句

修复:
- 移除静态 import() 映射,改用纯路径字符串邻接表
- 通过 router.getRoutes() 动态获取组件的 import 函数
- 延迟初始化 routePrefetch,首次导航时才创建实例
- 更新测试文件使用 mock router

效果:
- index.js 中动态 import 引用从 37 个减少到 1 个
- 首次加载不再包含未使用的视图组件引用
- 41 个测试全部通过

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:07:39 +08:00
yangjianbo
d9433699db Merge branch 'dev' of https://github.com/mt21625457/aicodex2api into dev 2026-01-16 21:49:28 +08:00
yangjianbo
92234857f7 perf(前端): 优化页面加载性能和用户体验
- 添加路由预加载功能,使用 requestIdleCallback 在浏览器空闲时预加载
- 配置 Vite manualChunks 分离 vendor 库(vue/ui/chart/i18n/misc)
- 新增 NavigationProgress 导航进度条组件,支持防闪烁和无障碍
- 集成 Vitest 测试框架,添加 40 个单元测试和集成测试
- 支持 prefers-reduced-motion 和暗色模式

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 21:48:57 +08:00
yangjianbo
8efa361728 perf(前端): 优化页面加载性能和用户体验
- 添加路由预加载功能,使用 requestIdleCallback 在浏览器空闲时预加载
- 配置 Vite manualChunks 分离 vendor 库(vue/ui/chart/i18n/misc)
- 新增 NavigationProgress 导航进度条组件,支持防闪烁和无障碍
- 集成 Vitest 测试框架,添加 40 个单元测试和集成测试
- 支持 prefers-reduced-motion 和暗色模式

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 21:43:39 +08:00
song
1be3eacad5 feat(scheduling): 兜底层账户选择策略可配置
- gateway.scheduling.fallback_selection_mode: "last_used"(默认) 或 "random"
- last_used: 按最后使用时间排序(轮询效果)
- random: 同优先级内随机选择
2026-01-16 20:47:07 +08:00
song
34d6b0a601 feat(gateway): 账户切换次数和 Antigravity 限流时间可配置
- gateway.max_account_switches: 账户切换最大次数,默认 10
- gateway.max_account_switches_gemini: Gemini 账户切换次数,默认 3
- gateway.antigravity_fallback_cooldown_minutes: Antigravity 429 fallback 限流时间,默认 5 分钟
- Antigravity 429 不再重试,直接标记账户限流
2026-01-16 20:18:30 +08:00
yangjianbo
eb432a49ed perf(前端): 移除 Google Fonts 改用系统字体栈
- 删除 Google Fonts @import,解决国内访问阻塞问题
- 使用 system-ui 优先的系统字体栈
- 添加中文字体支持(苹方、冬青黑、微软雅黑)
- 移除 Inter 字体专用的 font-feature-settings

此改动可显著提升国内用户的页面加载速度,避免因 Google Fonts
被墙导致的渲染阻塞问题。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 19:49:33 +08:00
Wesley Liddick
04811c00cb Merge pull request #313 from mt21625457/main
fix(ci): 修复各类bug
2026-01-16 19:30:07 +08:00
Wesley Liddick
06093d4f79 Merge pull request #311 from longgexx/main
添加分组级别模型路由配置功能(Anthropic平台)
2026-01-16 19:22:12 +08:00
song
2055a60bcb fix(antigravity): 429 重试3次后限流账户
- 收到429后重试最多3次(指数退避)
- 3次都失败后调用 handleUpstreamError 限流账户
- 移除无效的 URL fallback 逻辑(当前只有一个URL)
2026-01-16 18:51:07 +08:00
song
cc892744bc fix(antigravity): 429 fallback 改为 5 分钟并限流整个账户
- fallback 时间从 1 分钟改为 5 分钟
- fallback 时直接限流整个账户而非仅限制 quota scope
2026-01-16 18:09:34 +08:00
longgexx
577ee16108 Merge branch 'main' of github.com:longgexx/sub2api 2026-01-16 17:35:44 +08:00
longgexx
392a8ac7ea 修复格式问题。 2026-01-16 17:35:17 +08:00
longgexx
226920064b Merge branch 'Wei-Shaw:main' into main 2026-01-16 17:26:54 +08:00
longgexx
19865b865f feat(group): 添加分组级别模型路由配置功能
支持为分组配置模型路由规则,可以指定特定模型模式优先使用的账号列表。

  - 新增 model_routing 字段存储路由配置(JSONB格式,支持通配符匹配)

  - 新增 model_routing_enabled 字段控制是否启用路由

  - 更新后端 handler/service/repository 支持路由配置的增删改查

  - 更新前端 GroupsView 添加路由配置界面

  - 添加数据库迁移脚本 040/041
2026-01-16 17:26:05 +08:00
yangjianbo
e3f812c2fe fix(安全): CSP 策略自动增强,无需配置文件修改即可生效
- 添加 enhanceCSPPolicy() 自动增强任何 CSP 策略
- 自动添加 nonce 占位符(如果策略中没有)
- 自动添加 Cloudflare Insights 域名
- 即使配置文件使用旧策略也能正常工作
- 添加 enhanceCSPPolicy 和 addToDirective 单元测试

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 17:20:39 +08:00
yangjianbo
c9f79dee66 feat(安全): 实现 CSP nonce 支持解决内联脚本安全问题
- 添加 GenerateNonce() 生成加密安全的随机 nonce
- SecurityHeaders 中间件为每个请求生成唯一 nonce
- CSP 策略支持 __CSP_NONCE__ 占位符动态替换
- embed_on.go 注入的内联脚本添加 nonce 属性
- 添加 Cloudflare Insights 域名到 CSP 允许列表
- 添加完整单元测试,覆盖率达到 89.8%

解决的问题:
- 内联脚本违反 CSP script-src 指令
- Cloudflare Insights beacon.min.js 加载被阻止

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 17:05:49 +08:00
yangjianbo
c659788022 fix(前端路由): 添加 chunk 加载错误自动恢复机制
- 检测动态导入模块加载失败错误
- 自动刷新页面获取最新资源
- 使用 sessionStorage 防止无限刷新循环(10秒冷却)
- 解决前端重新部署后用户缓存导致的加载失败问题

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 16:20:10 +08:00
yangjianbo
aeb987ceb1 Merge branch 'dev' 2026-01-16 15:35:38 +08:00
yangjianbo
b478982484 style(界面): 修复 AccountsView ESLint 报错
移除多余分号并整理 onMounted 代码块,确保 lint:check 通过。
2026-01-16 15:25:47 +08:00
yangjianbo
fe71ee57b3 fix(定时轮): 初始化失败返回错误并补充单测
- NewTimingWheelService 改为返回 error,避免 panic

- ProvideTimingWheelService 透传 error 并更新 wire 生成代码

- 补充定时任务调度/取消/周期任务相关单元测试
2026-01-16 15:25:33 +08:00
song
fba3d21a35 fix: 使用 Contains 匹配 missing_project_id 并修复测试 mock 2026-01-16 14:18:12 +08:00
song
455576300c fix(antigravity): 使用 Contains 匹配 missing_project_id 错误信息 2026-01-16 14:03:25 +08:00
song
821968903c feat(antigravity): 手动刷新令牌时自动恢复 missing_project_id 错误账户状态
- 当手动刷新成功获取到 project_id,且之前错误为 missing_project_id 时,自动清除错误状态
- 后台自动刷新时同样支持状态恢复
2026-01-16 13:18:00 +08:00
wfunc
452fa53c0d feat: Claude Sonnet 429 仅限模型限流 2026-01-16 13:03:04 +08:00
song
95fe1e818f fix: Antigravity 刷新 token 时检测 project_id 缺失
- 刷新 token 后调用 LoadCodeAssist 获取 project_id
- 如果获取失败,保留原有 project_id,标记账户为 error
- token 仍会正常更新,不影响凭证刷新
- 错误信息:账户缺少project id,可能无法使用Antigravity
2026-01-16 12:13:54 +08:00
song
a61042bca0 fix: Antigravity project_id 获取优化
- API URL 改为只使用 prod 端点
- 刷新 token 时每次调用 LoadCodeAssist 更新 project_id
- 移除随机生成 project_id 的兜底逻辑
2026-01-16 11:57:14 +08:00
song
b4abfae4de fix: Antigravity 测试连接使用最小 token 消耗
- buildGeminiTestRequest: 输入 "." + maxOutputTokens: 1
- buildClaudeTestRequest: 输入 "." + MaxTokens: 1
- buildGenerationConfig: 支持透传 MaxTokens 参数
2026-01-16 10:31:55 +08:00
Wesley Liddick
c02c8646a6 Merge pull request #304 from IanShaw027/feature/codex-tool-correction
feat(openai): 添加Codex工具调用自动修正功能
2026-01-16 08:50:11 +08:00
Wesley Liddick
3ff2ca8d41 Merge pull request #303 from IanShaw027/feature/ops-account-health-score
feat(ops): 运维监控功能增强与优化
2026-01-16 08:49:52 +08:00
cyhhao
65fd0d15ae fix(网关): 补齐非 Claude Code OAuth 兼容 2026-01-16 00:42:31 +08:00
IanShaw027
415840088e fix(lint): 修复剩余的errcheck错误
修复了测试文件中剩余的6处类型断言未检查错误:
- 第115-118行:choices.message.tool_calls 的类型断言链
- 第140和145行:multiple tool calls 测试的类型断言
- 第343和345行:ComplexSSEData 测试的类型断言

**修复模式:**
所有类型断言都改为使用 ok 检查:
```go
// 修复前
choices := payload["choices"].([]any)

// 修复后
choices, ok := payload["choices"].([]any)
if !ok || len(choices) == 0 {
    t.Fatal("No choices found in result")
}
```

**测试验证:**
-  TestCorrectToolCallsInSSEData - 所有子测试通过
-  TestComplexSSEData - 通过
-  TestCorrectToolParameters - 通过
-  所有类型断言都有 ok 检查
-  添加了数组长度验证

现在所有 errcheck 错误都已修复。
2026-01-16 00:14:19 +08:00
IanShaw027
c4f6c89b65 fix(lint): 修复golangci-lint检查发现的问题
修复了4个lint问题:
1. errcheck (3处): 在测试中添加类型断言的ok检查
2. govet copylocks (1处): 将mutex从ToolCorrectionStats移到CodexToolCorrector

**详细修改:**

1. **openai_tool_corrector_test.go**
   - 添加了类型断言的ok检查,避免panic
   - 在解析JSON后检查payload结构的有效性
   - 改进错误处理和测试可靠性

2. **openai_tool_corrector.go**
   - 将sync.RWMutex从ToolCorrectionStats移到CodexToolCorrector
   - 避免在GetStats()返回时复制mutex
   - 保持线程安全的同时符合Go最佳实践

**测试验证:**
- 所有单元测试通过 
- go vet 检查通过 
- 代码编译正常 
2026-01-16 00:02:22 +08:00
IanShaw027
539b41f421 feat(openai): 添加Codex工具调用自动修正功能
实现了完整的Codex工具调用拦截和自动修正系统,解决OpenCode使用Codex模型时的工具调用兼容性问题。

**核心功能:**

1. **工具名称自动映射**
   - apply_patch/applyPatch → edit
   - update_plan/updatePlan → todowrite
   - read_plan/readPlan → todoread
   - search_files/searchFiles → grep
   - list_files/listFiles → glob
   - read_file/readFile → read
   - write_file/writeFile → write
   - execute_bash/executeBash/exec_bash/execBash → bash

2. **工具参数自动修正**
   - bash: 自动移除不支持的 workdir/work_dir 参数
   - edit: 自动将 path 参数重命名为 file_path
   - 支持 JSON 字符串和对象两种参数格式

3. **流式响应集成**
   - 在 SSE 数据流中实时修正工具调用
   - 支持多种 JSON 结构(tool_calls, function_call, delta, choices等)
   - 不影响响应性能和用户体验

4. **统计和监控**
   - 记录每次工具修正的详细信息
   - 提供修正统计数据查询
   - 便于问题排查和性能优化

**实现文件:**
- `openai_tool_corrector.go`: 工具修正核心逻辑(250行)
- `openai_tool_corrector_test.go`: 完整的单元测试(380+行)
- `openai_gateway_service.go`: 流式响应集成
- `openai_gateway_service_tool_correction_test.go`: 集成测试

**测试覆盖:**
- 工具名称映射测试(18个映射规则)
- 参数修正测试(bash workdir、edit path等)
- SSE数据修正测试(多种JSON结构)
- 统计功能测试
- 所有测试通过 

**解决的问题:**
修复了 OpenCode 使用 sub2api 中转 Codex 时,因工具名称和参数不兼容导致的工具调用失败问题。
Codex 模型有时会忽略指令文件中的工具映射说明,导致调用不存在的工具(如 apply_patch)。
现在通过流式响应拦截,自动将错误的工具调用修正为 OpenCode 兼容的格式。

**参考文档:**
- OpenCode 工具规范: https://opencode.ai/docs/
- Codex Bridge 指令: backend/internal/service/prompts/codex_opencode_bridge.txt
2026-01-15 23:52:50 +08:00
IanShaw027
b2ff326ced fix(ops): 调整健康分数权重以修复CI测试
- 将业务健康和基础设施健康的权重从80/20调整为70/30
- 使基础设施故障(DB/Redis)对总分影响更明显
- 修复三个失败的测试用例:
  * DB故障: 92→88 (期望70-90)
  * Redis故障: 96→94 (期望85-95)
  * 业务降级: 82→84.5 (期望84-85)
2026-01-15 23:02:15 +08:00
IanShaw027
8b95d16220 refactor(ops): 简化自动刷新定时器逻辑
- 合并双定时器为单一倒计时定时器
- 倒计时归零时触发数据刷新
- 添加自定义时间范围的安全回退
2026-01-15 22:07:23 +08:00
cyhhao
c11f14f3a0 fix(gateway): drain upstream after client disconnect 2026-01-15 21:51:14 +08:00
cyhhao
98b65e67f2 fix(gateway): avoid injecting invalid SSE on client cancel 2026-01-15 21:42:13 +08:00
IanShaw027
a478822b8e refactor(ops): 优化文案显示
- TTFT 定义统一改为"首 Token"/"First Token"(而非"首字节"/"first byte")
- 请求时长卡片标题去掉"(毫秒)"/"(ms)"后缀
2026-01-15 21:37:35 +08:00
IanShaw027
23aa69f56f refactor(ops): 优化任务心跳和组件刷新机制
后端改动:
- 添加 ops_job_heartbeats.last_result 字段记录任务执行结果
- 优化告警评估器统计信息(规则数/事件数/邮件数)
- 统一各定时任务的心跳记录格式

前端改动:
- 重构 OpsConcurrencyCard 使用父组件统一控制刷新节奏
- 移除独立的 5 秒刷新定时器,改用 refreshToken 机制
- 修复 TypeScript 类型错误
2026-01-15 21:31:55 +08:00
Wesley Liddick
b36f3db9de Merge pull request #300 from mt21625457/main
feat(网关): 引入 OpenAI/Claude OAuth token 缓存
2026-01-15 20:10:16 +08:00
IanShaw027
e93f086485 fix(ops): 请求时长详情显示所有请求
- 移除请求时长卡片详情按钮的 min_duration_ms 参数限制
- 现在点击详情会显示所有请求,按时长倒序排列
- 不再只显示 P99 以上的请求
2026-01-15 19:57:19 +08:00
IanShaw027
930e9ee55c feat(ops): 添加自定义时间范围选择功能
功能特性:
- 在时间段选择器中增加"自定义"选项
- 点击后弹出对话框,支持选择任意时间范围
- 使用 HTML5 datetime-local 输入框,体验友好
- 自定义时显示格式化的时间范围标签(MM-DD HH:mm ~ MM-DD HH:mm)
- 默认初始化为最近1小时

技术实现:
- 扩展 TimeRange 类型支持 'custom'
- 添加 customStartTime 和 customEndTime 状态管理
- 创建 buildApiParams 辅助函数统一处理 API 参数
- 当选择自定义时,使用 start_time 和 end_time 参数替代 time_range
- 更新所有相关 API 调用支持自定义时间范围

国际化:
- 添加"自定义"、"开始时间"、"结束时间"翻译
2026-01-15 19:50:47 +08:00
IanShaw027
38961ba10e refactor(ops): 优化阈值检查系统和布局
阈值检查系统优化:
- 引入三级阈值系统(normal/warning/critical)
- 统一阈值判断逻辑,支持警告和严重两个级别
- 移除硬编码的 TTFT 颜色判断,改用阈值配置
- 新增 getThresholdColorClass 统一颜色映射

布局优化:
- 优化详细指标在卡片内的响应式布局
- 改进宽屏下的卡片布局显示
- 优化指标数值的对齐和间距
2026-01-15 19:50:31 +08:00
IanShaw027
93b5b7474b refactor(ops): 调整健康得分权重
- 业务健康权重从 70% 提升到 80%
- 基础设施健康权重从 30% 降低到 20%
- 更加关注业务指标(SLA、错误率等)对整体健康的影响
2026-01-15 19:50:02 +08:00
yangjianbo
f862ddc9ff style: 修复 gofmt 格式化问题
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 19:42:18 +08:00
程序猿MT
b59032304c Merge branch 'Wei-Shaw:main' into main 2026-01-15 19:34:11 +08:00
yangjianbo
3ba4d535e3 Merge branch 'dev' 2026-01-15 19:33:32 +08:00
cyhhao
c579439c1e fix(网关): 区分 Claude Code OAuth 适配 2026-01-15 19:17:07 +08:00
yangjianbo
5b37e9aea4 fix(OAuth缓存): 修复缓存键冲突、401强制刷新及Redis降级处理
- Gemini 缓存键统一增加 gemini: 前缀,避免与其他平台命名空间冲突
- OAuth 账号 401 错误时设置 expires_at=now 并持久化,强制下次请求刷新 token
- Redis 锁获取失败时降级为无锁刷新,仅在 token 接近过期时执行,并检查 ctx 取消状态

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 19:08:07 +08:00
cyhhao
46e5ac9672 fix(网关): 对齐 Claude OAuth 请求适配 2026-01-15 18:54:42 +08:00
yangjianbo
1820389a05 feat(网关): 引入 OpenAI/Claude OAuth token 缓存
新增 OpenAI/Claude TokenProvider 与缓存键生成
扩展 OAuth 缓存失效覆盖更多平台
统一 OAuth 缓存前缀与依赖注入
2026-01-15 18:27:06 +08:00
yangjianbo
35e3a89385 fix(忽略文件): 添加 .serena 目录到 .gitignore 2026-01-15 16:34:42 +08:00
shaw
5f890e85e7 Merge PR #296: OAuth token cache invalidation on 401 and refresh 2026-01-15 16:31:37 +08:00
Wesley Liddick
10bc7f7042 Merge pull request #297 from LLLLLLiulei/feat/ip-management-enhancements
feat: add proxy geo location
2026-01-15 16:06:36 +08:00
yangjianbo
a65fd9dee8 Merge branch 'test' 2026-01-15 15:58:47 +08:00
yangjianbo
1bb4c76deb fix(账号管理): 移除调度切换后的冗余列表刷新
切换账号调度状态后,updateSchedulableInList 已完成局部更新,
无需再调用 load() 刷新整个列表。此修改减少不必要的 API 请求,
避免 UI 闪烁。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:57:47 +08:00
LLLLLLiulei
aab44f9fc8 feat: add proxy geo location 2026-01-15 15:15:20 +08:00
yangjianbo
0a848e7578 Merge branch 'test' 2026-01-15 15:15:07 +08:00
yangjianbo
90bce60b85 feat: merge dev 2026-01-15 15:14:44 +08:00
程序猿MT
c22d51ee41 Merge branch 'Wei-Shaw:main' into main 2026-01-15 15:12:16 +08:00
yangjianbo
a458e684bc fix(认证): OAuth 401 直接标记错误状态
- OAuth 401 清理缓存并设置错误状态

- 移除 oauth_401_cooldown_minutes 配置及示例

- 更新 401 相关单测

破坏性变更: OAuth 401 不再临时不可调度,需手动恢复
2026-01-15 15:06:34 +08:00
LLLLLLiulei
87b4662993 Revert "feat: add proxy geo location"
This reverts commit 09c4f82927ddce1c9528c146a26457f53d02b034.
2026-01-15 15:01:50 +08:00
LLLLLLiulei
3a100339b9 feat: add proxy geo location 2026-01-15 15:01:50 +08:00
Wesley Liddick
47eb3c8888 Merge pull request #284 from longgexx/main
fix(admin): 修复使用记录页面趋势图筛选联动和日期选择问题
2026-01-15 13:43:00 +08:00
longgexx
4672a6fac3 merge: 合并上游 upstream/main 分支
解决冲突:
- usage_log_repo.go: 保留 groupID 和 stream 参数,同时合并上游的
  account_rate_multiplier 逻辑

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 13:09:26 +08:00
longgexx
82743704e4 fix(test): 添加测试辅助函数 truncateToDayUTC 修复编译错误
在 usage_log_repo_integration_test.go 中添加本地的 truncateToDayUTC
辅助函数,修复因主代码重命名该函数导致的测试编译错误。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 11:42:35 +08:00
Wesley Liddick
cc2d064ab4 Merge pull request #289 from IanShaw027/fix/mobile-ui
fix(mobile): 优化移动端表格、操作栏和弹窗显示
2026-01-15 11:31:16 +08:00
Wesley Liddick
27214f8657 Merge pull request #285 from IanShaw027/fix/ops-bug
feat(ops): 增强错误日志管理、告警静默和前端 UI 优化
2026-01-15 11:26:16 +08:00
Wesley Liddick
28de614dfb Merge pull request #282 from LLLLLLiulei/feat/ip-management-enhancements
feat: enhance proxy management
2026-01-15 11:23:17 +08:00
longgexx
850183c269 fix(dashboard): 修复预聚合表使用UTC时区导致今日统计不准确的问题
将 dashboard_aggregation_repo.go 和 usage_log_repo.go 中的时区处理
从 UTC 改为使用服务器配置时区(默认 Asia/Shanghai),确保"今日"
统计数据与用户预期一致。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 11:22:13 +08:00
shaw
2a5ef6d3f5 Merge PR #279: feat(计费): 账号计费倍率快照与账号口径费用统计 2026-01-15 11:11:55 +08:00
IanShaw027
1d231c6cc3 fix(mobile): 修复 UsersView 更多菜单定位并统一逻辑
**问题描述**:
- UsersView 的"更多"菜单仍然出现在页面左上角错误位置
- UsersView 使用 actionButtonRefs Map 获取按钮元素,导致定位失败
- UsersView 和 AccountsView 的菜单定位逻辑不一致,难以维护

**解决方案**:
- 修改 openActionMenu 函数签名,添加 MouseEvent 参数
- 使用 e.currentTarget 直接从事件对象获取触发元素
- 移除不必要的 actionButtonRefs Map 和 setActionButtonRef 函数
- 统一菜单宽度为 200px(与 AccountsView 一致)
- 完全复制 AccountsView 的定位逻辑,确保两者行为一致

**技术要点**:
- 移动端:菜单居中对齐按钮,优先显示在按钮下方
- 桌面端:使用鼠标位置定位,添加边界检测
- 简化代码,移除不必要的防御性检查
- 两个组件的菜单定位逻辑完全一致,便于维护
2026-01-15 10:21:35 +08:00
IanShaw027
20c71acb3b fix(mobile): 优化移动端表格、操作栏和弹窗显示
**问题描述**:
- 表格在移动端显示列过多,需要横向滚动,内容被截断
- 顶部操作栏按钮拥挤,占用过多空间
- 弹窗表单在小屏幕上布局不合理
- "更多"操作菜单定位错误,位置过高或超出屏幕
- 滚动页面时菜单不会自动关闭,与卡片分离

**解决方案**:

1. **DataTable 组件 - 移动端卡片视图**
   - 在 < 768px 时自动切换到卡片布局
   - 每个表格行渲染为独立卡片,所有字段清晰可见
   - 操作按钮在卡片底部,触摸目标足够大
   - 支持深色模式,包含加载和空状态
   - 自动应用于所有使用 DataTable 的管理页面

2. **UsersView 顶部操作栏优化**
   - 移动端:搜索框全宽 + 次要按钮显示为图标 + 创建按钮突出
   - 桌面端:保持原有布局(图标 + 文字)
   - 使用响应式 Tailwind classes

3. **UserCreateModal 弹窗优化**
   - 余额/并发数字段:移动端单列,桌面端双列
   - 弹窗边距:移动端 8px,桌面端 16px

4. **操作菜单定位修复**
   - UsersView: 移动端菜单居中对齐按钮,智能定位
   - AccountsView: 移动端菜单优先显示在按钮下方
   - 所有情况下确保菜单不超出屏幕边界
   - 添加滚动监听,滚动时自动关闭菜单

**影响范围**:
- 所有使用 DataTable 的管理页面(8 个页面)自动获得移动端卡片视图
- 用户管理和账号管理页面的操作菜单定位优化
- 创建用户弹窗的响应式布局优化

**技术要点**:
- 使用 Tailwind 响应式断点(md:, sm:)
- 触摸目标 ≥ 44px
- 完整支持深色模式
- 向后兼容,桌面端保持原有布局
2026-01-15 10:08:14 +08:00
yangjianbo
52ad7c6e9c Merge branch 'main' into dev 2026-01-15 09:30:29 +08:00
longgexx
5aaaffe4d1 fix(dashboard): 修复仪表盘今日统计使用UTC时区的问题
将仪表盘统计中的"今日"时间范围从UTC时区改为服务器配置时区,
使其与使用记录页面保持一致。

修改内容:
- GetDashboardStats: 使用 timezone.Now() 和 timezone.Today()
- GetDashboardStatsWithRange: 同上

影响的统计项:
- 今日请求 (TodayRequests)
- 今日 Token (TodayTokens)
- 今日费用 (TodayCost/TodayActualCost)
- 今日新用户 (TodayNewUsers)
- 今日活跃用户 (ActiveUsers)
2026-01-15 09:12:16 +08:00
IanShaw027
5354ba3662 fix(ops): 修复错误列表用户显示并区分上游错误和请求错误
- 修复错误列表中用户列显示 \n 的问题
- 上游错误显示账号(account),请求错误显示用户(user)
- 错误详情模态框同步调整显示逻辑
- 添加 accountId 国际化翻译
2026-01-15 00:11:44 +08:00
IanShaw027
2daf13c4c8 style(backend): 修复 ops_service.go 代码格式 2026-01-15 00:04:37 +08:00
IanShaw027
16a90f3d3a fix(types): 在 OpsErrorLog 类型中添加 user_email 字段
- 修复 TypeScript 编译错误
- 添加 user_email 字段到 OpsErrorLog 接口
2026-01-15 00:03:34 +08:00
IanShaw027
8a0ff15242 feat(i18n): 添加用户相关的国际化翻译
- 在中英文 i18n 文件中添加 errorLog.user 和 errorLog.userId
- 在中英文 i18n 文件中添加 errorDetail.user
- 支持错误日志和详情中的用户信息显示
2026-01-15 00:02:32 +08:00
IanShaw027
8c993dfd35 refactor(frontend): 将账号显示替换为用户显示
- 在错误日志表格中将账号列替换为用户列
- 在错误详情模态框中将账号信息替换为用户信息
- 显示用户邮箱而不是账号名称
- 上游错误的账号信息保留在上游错误上下文中
2026-01-14 23:59:26 +08:00
IanShaw027
2a6fb1e456 feat(ops): 添加用户信息显示和搜索功能
- 在错误日志列表和详情中显示用户邮箱
- 在 GetErrorLogByID 中关联 users 表获取用户邮箱
- 在 OpsErrorLogFilter 中添加 UserQuery 字段
- 在 buildOpsErrorLogsWhere 中添加用户邮箱搜索条件
- 在 GetErrorLogs handler 中支持 user_query 参数
2026-01-14 23:56:45 +08:00
IanShaw027
9e6cd36af4 feat(ops): 添加上游响应体字段到错误事件
- 在 OpsUpstreamErrorEvent 中添加 UpstreamResponseBody 字段
- 用于存储上游服务返回的响应内容
- 区分客户端响应和上游响应
2026-01-14 23:52:36 +08:00
IanShaw027
f25f992a30 fix(ops): 错误详情中显示账号和分组名称
- 在 GetErrorLogByID 查询中添加 LEFT JOIN 关联查询
- 关联 accounts 和 groups 表获取名称
- 填充 AccountName 和 GroupName 字段
2026-01-14 23:52:00 +08:00
IanShaw027
841d7ef2f2 fix(lint): 修复 golangci-lint 检查问题
- 格式化代码(gofmt)
- 修复空指针检查(staticcheck)
- 删除未使用的函数(unused)
2026-01-14 23:49:27 +08:00
IanShaw027
a7a49be850 refactor(ops): 使用TTFT替代Duration作为健康分数指标
- 业务健康分数:错误率 50% + TTFT 50%
- TTFT 阈值:1s → 100分,3s → 0分
- TTFT 对 AI 服务的用户体验更有意义
- 更新所有相关测试用例期望值
2026-01-14 23:47:43 +08:00
IanShaw027
d5eab7da3b refactor(ops): 优化健康分数计算逻辑和阈值
- 移除 SLA 组件(与错误率重复)
- 恢复延迟组件,阈值调整为 1s-2s
- 错误率阈值调整为 1%-10%(更宽松)
- 业务健康分数:错误率 50% + 延迟 50%
- 更新所有相关测试用例期望值
2026-01-14 23:43:12 +08:00
IanShaw027
9b10241561 test(ops): 修复健康分数测试用例期望值
- 更新 TestComputeBusinessHealth 中 SLA 95% 边界测试的期望值
- 更新 TestComputeDashboardHealthScore 中中等健康度测试的期望值
- 适配移除延迟组件后的新健康分数计算逻辑
2026-01-14 23:39:09 +08:00
IanShaw027
76448ab555 refactor(frontend): 优化ops看板骨架屏组件
- 添加 fullscreen 属性支持,适配全屏模式
- 优化骨架屏布局,更好地匹配实际看板结构
- 改进加载动画效果,提升用户体验
2026-01-14 23:26:34 +08:00
IanShaw027
9584af5cb4 fix(ops): 优化错误日志查询和详情展示
- 新增 GetErrorLogByID 接口用于获取单个错误日志详情
- 优化 GetErrorLogs 过滤逻辑,简化参数处理
- 简化前端错误详情模态框代码,提升可维护性
- 更新相关 API 接口和 i18n 翻译
2026-01-14 23:16:01 +08:00
longgexx
6fabddcb0b fix(test): 更新集成测试以匹配新的筛选参数签名
更新 usage_log_repo_integration_test.go 中的测试用例,
   使其与 GetUsageTrendWithFilters 和 GetModelStatsWithFilters
   方法的新签名保持一致。
2026-01-14 22:29:14 +08:00
longgexx
5efeabb0c6 fix(admin): 修复使用记录页面趋势图筛选联动和日期选择问题
修复两个问题:
   1. Token使用趋势图和模型分布图未响应筛选条件
   2. 上午时段选择今天刷新后日期回退到前一天

   前端修改:
   - 更新 dashboard API 类型定义,添加 model、account_id、group_id、stream 参数支持
   - 修改 UsageView 趋势图加载逻辑,传递所有筛选参数到后端
   - 修复日期格式化函数,使用本地时区避免 UTC 转换导致的日期偏移

   后端修改:
   - Handler 层:接收并解析所有筛选参数(model、account_id、group_id、stream)
   - Service 层:传递完整的筛选参数到 Repository 层
   - Repository 层:SQL 查询动态添加所有过滤条件
   - 更新接口定义和测试 mock 以保持一致性

   影响范围:
   - /admin/dashboard/trend 端点现支持完整筛选
   - /admin/dashboard/models 端点现支持完整筛选
   - 用户在后台使用记录页面选择任意筛选条件时,趋势图和模型分布图会实时响应
   - 日期选择器在任何时区下都能正确保持今天的选择
2026-01-14 22:02:56 +08:00
longgexx
806f402bba fix(admin): 修复使用记录页面趋势图筛选联动和日期选择问题
修复两个问题:
   1. Token使用趋势图和模型分布图未响应筛选条件
   2. 上午时段选择今天刷新后日期回退到前一天

   前端修改:
   - 更新 dashboard API 类型定义,添加 model、account_id、group_id、stream 参数支持
   - 修改 UsageView 趋势图加载逻辑,传递所有筛选参数到后端
   - 修复日期格式化函数,使用本地时区避免 UTC 转换导致的日期偏移

   后端修改:
   - Handler 层:接收并解析所有筛选参数(model、account_id、group_id、stream)
   - Service 层:传递完整的筛选参数到 Repository 层
   - Repository 层:SQL 查询动态添加所有过滤条件
   - 更新接口定义和所有调用点以保持一致性

   影响范围:
   - /admin/dashboard/trend 端点现支持完整筛选
   - /admin/dashboard/models 端点现支持完整筛选
   - 用户在后台使用记录页面选择任意筛选条件时,趋势图和模型分布图会实时响应
   - 日期选择器在任何时区下都能正确保持今天的选择
2026-01-14 21:52:50 +08:00
IanShaw027
5432087d96 refactor(frontend): 优化ops错误详情模态框代码格式和功能
- 重构OpsErrorDetailModal.vue代码格式,提升可读性
- 添加上游错误tab显示功能
- 完善i18n翻译(upstream_http)
- 优化其他ops组件代码格式
2026-01-14 20:49:18 +08:00
LLLLLLiulei
02cb14c7b8 test: fix proxy repo stub 2026-01-14 19:56:19 +08:00
LLLLLLiulei
9bdb45be7c feat: enhance proxy management 2026-01-14 19:45:29 +08:00
IanShaw027
514c0562e0 refactor(frontend): 清理OpsDashboardHeader中的i18n翻译
将技术术语的i18n翻译键替换为硬编码文本:
- ms (P99) - 毫秒和百分位数标识
- TTFT - Time To First Token缩写

这些是通用技术术语,不需要国际化。
2026-01-14 19:02:02 +08:00
IanShaw027
371275ec34 refactor(frontend): 清理ops组件中未使用的i18n翻译
- 移除i18n文件中未使用的翻译键(cpu, redis, qps, ttft等)
- 将技术术语改为硬编码(QPS, CPU, TPS等不需要翻译)
- 简化OpsDashboardHeader、OpsErrorDetailModal等组件的i18n调用
2026-01-14 17:04:30 +08:00
墨颜
ec24a3c361 Merge branch 'Wei-Shaw:main' into main 2026-01-14 16:42:30 +08:00
墨颜
d89e797bfc Merge branch 'main' of https://github.com/whoismonay/sub2api 2026-01-14 16:30:32 +08:00
IanShaw027
55e469c7fe fix(ops): 优化错误日志过滤和查询逻辑
后端改动:
- 添加 resolved 参数默认值处理(向后兼容,默认显示未解决错误)
- 新增 status_codes_other 查询参数支持
- 移除 service 层的高级设置过滤逻辑,简化错误日志查询流程

前端改动:
- 完善错误日志相关组件的国际化支持
- 优化 Ops 监控面板和设置对话框的用户体验
2026-01-14 16:26:33 +08:00
墨颜
fb99ceacc7 feat(计费): 支持账号计费倍率快照与统计展示
- 新增 accounts.rate_multiplier(默认 1.0,允许 0)
- 使用 usage_logs.account_rate_multiplier 记录倍率快照,避免历史回算
- 统计/导出/管理端展示账号口径费用(total_cost * account_rate_multiplier)
2026-01-14 16:12:08 +08:00
yangjianbo
daf10907e4 fix(认证): 修复 OAuth token 缓存失效与 401 处理
新增 token 缓存失效接口并在刷新后清理
401 限流支持自定义规则与可配置冷却时间
补齐缓存失效与 401 处理测试
测试: make test
2026-01-14 15:55:44 +08:00
Wesley Liddick
b3b2868f55 Merge pull request #278 from IanShaw027/fix/openai-account-schedulability-check
fix(网关): 修复账号选择中的调度器快照延迟问题
2026-01-14 15:52:35 +08:00
ianshaw
25b00abca1 fix(网关): 修复账号选择中的调度器快照延迟问题
## 问题描述
调度器快照更新存在0.5-1秒的延迟(Outbox轮询间隔),导致在账号被限流或过载后的短时间窗口内,
可能仍会被选中,造成请求失败。

## 根本原因
账号选择逻辑依赖调度器快照(listSchedulableAccounts),但快照更新有延迟:
- Outbox轮询: 每1秒检查一次变更事件
- 全量重建: 每300秒重建一次
- 时间窗口: 账号状态变更后0.5-1秒内,快照可能未更新

## 解决方案
在账号选择循环中添加IsSchedulable()实时检查,作为第二道防线:
1. 第一道防线: 调度器快照过滤(可能有延迟)
2. 第二道防线: IsSchedulable()实时检查(本次修复)

IsSchedulable()会检查:
- RateLimitResetAt: 限流重置时间
- OverloadUntil: 过载持续时间
- TempUnschedulableUntil: 临时不可调度时间
- Status: 账号状态
- Schedulable: 可调度标志

## 修改范围
### OpenAI Gateway Service
- SelectAccountForModelWithExclusions: 添加IsSchedulable()检查
- SelectAccountWithLoadAwareness: 添加IsSchedulable()检查

### Gateway Service (Claude/Gemini/Antigravity)
- 负载感知选择候选账号筛选: 添加IsSchedulable()检查
- selectAccountForModelWithPlatform: 添加IsSchedulable()检查
- selectAccountWithMixedScheduling: 添加IsSchedulable()检查

### 测试用例
- OpenAI: 添加2个测试用例验证限流账号过滤
- Gateway: 添加2个测试用例验证限流和过载账号过滤

### 其他修复
- ops_repo_preagg.go: 修复platform为NULL时的聚合问题

## 测试结果
所有单元测试通过 
2026-01-13 22:49:26 -08:00
IanShaw027
8d0767352b fix(ops): 修复ops handler逻辑 2026-01-14 14:30:41 +08:00
IanShaw027
918a253851 feat(frontend): 完善ops监控面板和组件功能 2026-01-14 14:30:18 +08:00
IanShaw027
63711067e6 refactor(ops): 完善gateway服务ops集成 2026-01-14 14:30:00 +08:00
IanShaw027
7158b38897 refactor(ops): 优化ops repository数据访问层 2026-01-14 14:29:39 +08:00
IanShaw027
7f317b9093 feat(ops): 增强ops核心服务功能和重试机制 2026-01-14 14:29:19 +08:00
IanShaw027
7c4309ea24 feat(ops): 添加ops handler和路由配置 2026-01-14 14:29:01 +08:00
IanShaw027
5013290486 feat(frontend): 优化ops监控UI组件 2026-01-14 12:41:24 +08:00
IanShaw027
8cf3e9a620 feat(frontend): 更新ops API接口和国际化文案 2026-01-14 12:41:05 +08:00
IanShaw027
060699c3b8 refactor(ops): 更新gateway服务集成ops功能 2026-01-14 12:40:49 +08:00
IanShaw027
2ca6c631ac refactor(ops): 重构ops handler和repository层 2026-01-14 12:40:34 +08:00
IanShaw027
967e25878f refactor(ops): 重构ops核心服务层代码 2026-01-14 12:40:12 +08:00
IanShaw027
182683814b refactor(ops): 移除duration相关告警指标,简化监控配置
主要改动:
- 移除 p95_latency_ms 和 p99_latency_ms 告警指标类型
- 移除配置中的 latency_p99_ms_max 阈值设置
- 简化健康分数计算(移除latency权重,重新归一化SLA和错误率)
- 移除duration相关的诊断规则和阈值检查
- 统一术语:延迟 → 请求时长
- 保留duration数据展示,但不再用于告警判断
- 聚焦TTFT作为主要的响应速度告警指标

影响范围:
- Backend: handler, service, models, tests
- Frontend: API types, i18n, components
2026-01-14 10:52:56 +08:00
shaw
99cbfa1567 fix(admin): 修复退款金额精度问题
- 显示完整余额精度,避免四舍五入导致的退款失败
- 添加"全部"按钮,一键填入完整余额
- 移除最小金额限制,支持任意正数金额
2026-01-14 10:22:31 +08:00
Wesley Liddick
3f8c8d70ad Merge pull request #274 from mt21625457/main
fix(openai): OAuth 请求强制 store=false
2026-01-14 09:53:09 +08:00
yangjianbo
9c567fad92 fix(网关): 优化 OAuth 请求中 store 参数的处理逻辑 2026-01-14 09:46:10 +08:00
IanShaw027
33f58d583d fix(ops): 修复告警状态验证和错误处理逻辑
- 增强告警事件状态验证,添加合法状态值检查
- 移除重试逻辑中的遗留字段赋值
- 修正仓库不可用时的错误类型
- 格式化测试文件代码
2026-01-14 09:39:18 +08:00
yangjianbo
0abb3a6843 Merge branch 'main' of https://github.com/mt21625457/aicodex2api 2026-01-14 09:22:37 +08:00
yangjianbo
3663951d11 fix(网关): OAuth 请求强制 store=false
避免上游 Store 必须为 false 的错误

仅在缺失或 true 时写回 store

测试: go test ./internal/service -run TestApplyCodexOAuthTransform

测试: make test-backend(golangci-lint 已单独执行)
2026-01-14 09:17:58 +08:00
IanShaw027
1e169685f4 feat(i18n): 添加ops新功能的国际化文案
- 新增告警静默相关的中英文翻译
- 补充错误分类和重试状态的文案
- 完善ops管理界面的提示信息
2026-01-14 09:04:09 +08:00
IanShaw027
f38a3e7585 feat(ui): 优化ops监控面板和组件功能
- 增强告警事件卡片的交互和静默功能
- 完善错误详情弹窗的展示和操作
- 优化错误日志表格的筛选和排序
- 新增重试和解决状态的UI支持
2026-01-14 09:03:59 +08:00
IanShaw027
b8da5d45ce feat(api): 扩展前端ops API接口
- 新增告警静默相关API调用
- 增强错误日志查询和过滤接口
- 添加重试和解决状态管理接口
2026-01-14 09:03:45 +08:00
IanShaw027
659df6e220 feat(handler): 新增ops管理接口和路由
- 添加告警静默管理接口
- 扩展错误日志查询和操作接口
- 新增重试和解决状态相关端点
- 完善错误日志记录功能
2026-01-14 09:03:35 +08:00
IanShaw027
d601768016 feat(service): 增强ops业务逻辑和告警功能
- 实现告警静默功能的业务逻辑
- 优化错误分类和重试机制
- 扩展告警评估和通知功能
- 完善错误解决和重试结果处理
2026-01-14 09:03:16 +08:00
IanShaw027
16ddc6a83b feat(repository): 扩展ops数据访问层功能
- 新增告警静默相关数据库操作
- 增强错误日志查询和统计功能
- 优化重试结果和解决状态的存储
2026-01-14 09:03:01 +08:00
IanShaw027
340dc9cadb feat(db): 添加ops告警静默和错误分类优化迁移
- 添加ops告警静默功能的数据库结构
- 优化错误分类和重试结果字段标准化
2026-01-14 09:02:45 +08:00
Wesley Liddick
55fced3942 Merge pull request #269 from mt21625457/main
fix: 修复opencode 适配openai 套餐的错误,通过sub2api完美转发 opencode
2026-01-13 17:33:07 +08:00
yangjianbo
7bbf49fd65 为类型断言补充 ok 校验并添加中文说明,避免 errcheck 报错(backend/internal/service/
openai_codex_transform_test.go:36、backend/internal/service/
    openai_codex_transform_test.go:89、backend/internal/service/
    openai_codex_transform_test.go:104)。
2026-01-13 17:22:57 +08:00
yangjianbo
eea6c2d02c fix(网关): 补齐Codex指令回退与输入过滤 2026-01-13 17:02:31 +08:00
yangjianbo
70eaa450db fix(网关): 修复工具续链校验与存储策略
完善 function_call_output 续链校验与引用匹配
续链场景强制 store=true,过滤 input 时避免副作用
补充续链判断与过滤相关单元测试

测试: go test ./...
2026-01-13 16:47:35 +08:00
Wesley Liddick
55796a118d Merge pull request #264 from IanShaw027/fix/openai-opencode-compatibility
fix(openai): 增强 OpenCode 兼容性和模型规范化
2026-01-13 16:01:37 +08:00
song
9a22d1a690 refactor: 提取 getOrCreateGeminiParts 减少重复代码
将两个 merge 函数中重复的 Gemini 响应结构访问逻辑提取为公共函数。
2026-01-13 13:25:55 +08:00
song
c9d21d53e6 fix: 修复 Antigravity 非流式响应文本丢失问题
Gemini 流式响应是增量的,需要累积所有 chunk 的文本内容。
原代码只保留最后一个有 parts 的 chunk,导致实际文本被空
text + thoughtSignature 的最终 chunk 覆盖。

添加 collectedTextParts 收集所有文本片段,返回前合并。
2026-01-13 13:04:03 +08:00
song
e1015c2759 fix: 修复 Antigravity 图片生成响应丢失问题
流式转非流式时,图片数据在中间 chunk 返回,最后一个 chunk 只有
finishReason,导致只保留最后 chunk 时图片丢失。

添加 collectedImageParts 收集所有图片 parts,并在返回前合并。
2026-01-13 12:58:05 +08:00
ianshaw
d7fa47d732 refactor(openai): 移除不必要的 seedOpenAISessionHeaders 函数 2026-01-12 20:38:46 -08:00
ianshaw
3d6e01a58f fix(openai): 增强 OpenCode 兼容性和模型规范化
## 主要改动

1. **模型规范化扩展到所有账号**
   - 将 Codex 模型规范化(如 gpt-5-nano → gpt-5.1)应用到所有 OpenAI 账号类型
   - 不再仅限于 OAuth 非 CLI 请求
   - 解决 Codex CLI 使用 ChatGPT 账号时的模型兼容性问题

2. **reasoning.effort 参数规范化**
   - 自动将 `minimal` 转换为 `none`
   - 解决 gpt-5.1 模型不支持 `minimal` 值的问题

3. **Session/Conversation ID fallback 机制**
   - 从请求体多个字段提取 session_id/conversation_id
   - 优先级:prompt_cache_key → session_id → conversation_id → previous_response_id
   - 支持 Codex CLI 的会话保持

4. **Tool Call ID fallback**
   - 当 call_id 为空时使用 id 字段作为 fallback
   - 确保 tool call 输出能正确匹配
   - 保留 item_reference 类型的 items

5. **Header 优化**
   - 添加 conversation_id 到允许的 headers
   - 移除删除 session headers 的逻辑

## 相关 Issue
- 参考 OpenCode issue #3118 关于 item_reference 的讨论
2026-01-12 20:18:53 -08:00
IanShaw027
f9713e8733 fix(codex): 添加codex CLI instructions fallback机制
## 问题
- 使用OpenAI API key时,opencode客户端可能因instructions不兼容而报错
- 依赖外部GitHub获取instructions,网络故障时会失败

## 解决方案
1. 将codex CLI标准instructions嵌入到项目中
2. 实现自动fallback机制:
   - 优先使用opencode GitHub的instructions
   - 失败时自动fallback到本地codex CLI instructions
3. 添加辅助函数用于错误检测和手动替换

## 改动
- 新增: internal/service/prompts/codex_cli_instructions.md
  - 从codex项目复制的标准instructions
  - 使用go:embed嵌入到二进制文件

- 修改: internal/service/openai_codex_transform.go
  - 添加embed支持
  - 增强getOpenCodeCodexHeader()的fallback逻辑
  - 新增GetCodexCLIInstructions()公开函数
  - 新增ReplaceWithCodexInstructions()用于手动替换
  - 新增IsInstructionError()用于错误检测

## 优势
- 零停机:GitHub不可用时仍能正常工作
- 离线可用:不依赖外部网络
- 兼容性:使用标准codex CLI instructions
- 部署简单:instructions嵌入到二进制文件
2026-01-13 11:14:32 +08:00
yangjianbo
0e44829720 Merge branch 'main' into dev 2026-01-13 10:29:12 +08:00
song
f0ece82111 feat: 在 dashboard 右上角添加文档链接 2026-01-12 17:01:57 +08:00
yangjianbo
9618cb5643 Merge branch 'main' into test 2026-01-12 15:15:03 +08:00
yangjianbo
9c02ab789d fix(rate_limiter): 更新速率限制逻辑,支持返回修复状态 2026-01-12 14:42:58 +08:00
song
11bfc807d7 merge upstream/main 2026-01-09 21:37:27 +08:00
song
c2a6ca8d3a chore: 提升 SSE 单行上限到 40MB 2026-01-09 20:57:06 +08:00
song
7b1cf2c495 chore: 调整 SSE 单行上限到 25MB 2026-01-09 20:47:13 +08:00
song
da1f3d61be feat: antigravity 配额域限流 2026-01-09 17:35:02 +08:00
song
dc3cd62125 Merge remote-tracking branch 'upstream/main' 2026-01-08 22:52:18 +08:00
song
bc404d4fc1 merge: 合并 upstream/main 使用上游版本解决冲突 2026-01-08 18:01:29 +08:00
song
a4a0c0e2cc feat(antigravity): 增强请求参数和注入 Antigravity 身份 system prompt 2026-01-08 13:07:20 +08:00
song
c7abfe67b5 Merge remote-tracking branch 'upstream/main' 2026-01-08 12:17:56 +08:00
song
4e3476a669 fix: 添加 gemini-3-flash 前缀映射支持 gemini-3-flash-preview 2026-01-06 15:09:21 +08:00
1760 changed files with 503640 additions and 54670 deletions

View File

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

22
.gitattributes vendored Normal file
View File

@@ -0,0 +1,22 @@
# 确保所有 SQL 迁移文件使用 LF 换行符
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
# YAML/YML 配置文件
*.yaml text eol=lf
*.yml text eol=lf
# Dockerfile
Dockerfile text eol=lf

View File

@@ -5,12 +5,33 @@ exceptions:
severity: high severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2023-30533)" 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" 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" owner: "security@your-domain"
- package: xlsx - package: xlsx
advisory: "GHSA-5pgg-2g8v-p4x9" advisory: "GHSA-5pgg-2g8v-p4x9"
severity: high severity: high
reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2024-22363)" 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" 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" owner: "security@your-domain"

View File

@@ -11,15 +11,16 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: backend/go.mod go-version-file: backend/go.mod
check-latest: false check-latest: false
cache: true cache: true
cache-dependency-path: backend/go.sum
- name: Verify Go version - name: Verify Go version
run: | run: |
go version | grep -q 'go1.25.5' go version | grep -q 'go1.26.2'
- name: Unit tests - name: Unit tests
working-directory: backend working-directory: backend
run: make test-unit run: make test-unit
@@ -27,21 +28,42 @@ jobs:
working-directory: backend working-directory: backend
run: make test-integration run: make test-integration
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install frontend dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Frontend typecheck and critical vitest
run: make test-frontend
golangci-lint: golangci-lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: backend/go.mod go-version-file: backend/go.mod
check-latest: false check-latest: false
cache: true cache: true
cache-dependency-path: backend/go.sum
- name: Verify Go version - name: Verify Go version
run: | run: |
go version | grep -q 'go1.25.5' go version | grep -q 'go1.26.2'
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v9 uses: golangci/golangci-lint-action@v9
with: with:
version: v2.7 version: v2.9
args: --timeout=5m args: --timeout=30m
working-directory: backend working-directory: backend

59
.github/workflows/cla.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: "CLA Assistant"
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, reopened, closed, synchronize]
permissions:
actions: write
contents: write
pull-requests: write
statuses: write
jobs:
cla-check:
if: |
github.event_name == 'issue_comment' ||
(github.event_name == 'pull_request_target' && github.event.action != 'closed')
runs-on: ubuntu-latest
steps:
- name: "CLA Assistant"
if: |
(github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') ||
github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path-to-signatures: "cla.json"
path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
branch: "cla-signatures"
allowlist: "dependabot[bot],renovate[bot],bot*"
lock-pullrequest-aftermerge: false
custom-notsigned-prcomment: |
Thank you for your contribution! Before we can merge this PR, we need $you to sign our [Contributor License Agreement (CLA)](https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md).
**To sign**, please reply with the following comment:
> I have read the CLA Document and I hereby sign the CLA
You only need to sign once — it will be valid for all your future contributions to this project.
custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA"
custom-allsigned-prcomment: "All contributors have signed the CLA. ✅"
cla-lock:
if: github.event_name == 'pull_request_target' && github.event.action == 'closed' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: "Lock merged PR"
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path-to-signatures: "cla.json"
path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
branch: "cla-signatures"
lock-pullrequest-aftermerge: true

View File

@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Update VERSION file - name: Update VERSION file
run: | run: |
@@ -45,7 +45,7 @@ jobs:
echo "Updated VERSION file to: $VERSION" echo "Updated VERSION file to: $VERSION"
- name: Upload VERSION artifact - name: Upload VERSION artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: version-file name: version-file
path: backend/cmd/server/VERSION path: backend/cmd/server/VERSION
@@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -63,7 +63,7 @@ jobs:
version: 9 version: 9
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: '20' node-version: '20'
cache: 'pnpm' cache: 'pnpm'
@@ -78,7 +78,7 @@ jobs:
working-directory: frontend working-directory: frontend
- name: Upload frontend artifact - name: Upload frontend artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: frontend-dist name: frontend-dist
path: backend/internal/web/dist/ path: backend/internal/web/dist/
@@ -89,25 +89,25 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.tag || github.ref }} ref: ${{ github.event.inputs.tag || github.ref }}
- name: Download VERSION artifact - name: Download VERSION artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: version-file name: version-file
path: backend/cmd/server/ path: backend/cmd/server/
- name: Download frontend artifact - name: Download frontend artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: frontend-dist name: frontend-dist
path: backend/internal/web/dist/ path: backend/internal/web/dist/
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version-file: backend/go.mod go-version-file: backend/go.mod
check-latest: false check-latest: false
@@ -115,7 +115,7 @@ jobs:
- name: Verify Go version - name: Verify Go version
run: | run: |
go version | grep -q 'go1.25.5' go version | grep -q 'go1.26.2'
# Docker setup for GoReleaser # Docker setup for GoReleaser
- name: Set up QEMU - name: Set up QEMU
@@ -173,7 +173,7 @@ jobs:
run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v7
with: with:
version: '~> v2' version: '~> v2'
args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }} args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }}
@@ -188,7 +188,7 @@ jobs:
# Update DockerHub description # Update DockerHub description
- name: Update DockerHub description - name: Update DockerHub description
if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }} if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }}
uses: peter-evans/dockerhub-description@v4 uses: peter-evans/dockerhub-description@v5
env: env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
with: with:
@@ -222,8 +222,9 @@ jobs:
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase
# 获取 tag message 内容 # 获取 tag message 内容并转义 Markdown 特殊字符
TAG_MESSAGE='${{ steps.tag_message.outputs.message }}' TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
TAG_MESSAGE=$(echo "$TAG_MESSAGE" | sed 's/\([_*`\[]\)/\\\1/g')
# 限制消息长度Telegram 消息限制 4096 字符,预留空间给头尾固定内容) # 限制消息长度Telegram 消息限制 4096 字符,预留空间给头尾固定内容)
if [ ${#TAG_MESSAGE} -gt 3500 ]; then if [ ${#TAG_MESSAGE} -gt 3500 ]; then
@@ -245,10 +246,10 @@ jobs:
if [ -n "$DOCKERHUB_USERNAME" ]; then if [ -n "$DOCKERHUB_USERNAME" ]; then
DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api" DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api"
MESSAGE+="# Docker Hub"$'\n' MESSAGE+="# Docker Hub"$'\n'
MESSAGE+="docker pull ${DOCKER_IMAGE}:${TAG_NAME}"$'\n' MESSAGE+="docker pull ${DOCKER_IMAGE}:${VERSION}"$'\n'
MESSAGE+="# GitHub Container Registry"$'\n' MESSAGE+="# GitHub Container Registry"$'\n'
fi fi
MESSAGE+="docker pull ${GHCR_IMAGE}:${TAG_NAME}"$'\n' MESSAGE+="docker pull ${GHCR_IMAGE}:${VERSION}"$'\n'
MESSAGE+="\`\`\`"$'\n'$'\n' MESSAGE+="\`\`\`"$'\n'$'\n'
MESSAGE+="🔗 *相关链接:*"$'\n' MESSAGE+="🔗 *相关链接:*"$'\n'
MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG_NAME})"$'\n' MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG_NAME})"$'\n'
@@ -270,3 +271,36 @@ jobs:
parse_mode: "Markdown", parse_mode: "Markdown",
disable_web_page_preview: true 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: jobs:
backend-security: backend-security:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version-file: backend/go.mod go-version-file: backend/go.mod
check-latest: false check-latest: false
cache-dependency-path: backend/go.sum cache-dependency-path: backend/go.sum
- name: Verify Go version - name: Verify Go version
run: | run: |
go version | grep -q 'go1.25.5' go version | grep -q 'go1.26.2'
- name: Run govulncheck - name: Run govulncheck
working-directory: backend working-directory: backend
run: | run: |
go install golang.org/x/vuln/cmd/govulncheck@latest go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./... govulncheck ./...
- name: Run gosec
working-directory: backend
run: |
go install github.com/securego/gosec/v2/cmd/gosec@latest
gosec -severity high -confidence high ./...
frontend-security: frontend-security:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up pnpm - name: Set up pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 9 version: 9
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: '20' node-version: '20'
cache: 'pnpm' cache: 'pnpm'

17
.gitignore vendored
View File

@@ -1,4 +1,5 @@
docs/claude-relay-service/ docs/claude-relay-service/
.codex
# =================== # ===================
# Go 后端 # Go 后端
@@ -83,6 +84,8 @@ temp/
*.log *.log
*.bak *.bak
.cache/ .cache/
.dev/
.serena/
# =================== # ===================
# 构建产物 # 构建产物
@@ -114,16 +117,22 @@ backend/.installed
# =================== # ===================
tests tests
CLAUDE.md CLAUDE.md
AGENTS.md
.claude .claude
scripts scripts
.code-review-state .code-review-state
openspec/ #openspec/
docs/
code-reviews/ code-reviews/
AGENTS.md #AGENTS.md
backend/cmd/server/server backend/cmd/server/server
deploy/docker-compose.override.yml deploy/docker-compose.override.yml
.gocache/ .gocache/
vite.config.js vite.config.js
docs/* docs/*
!docs/PAYMENT.md
!docs/PAYMENT_CN.md
!docs/ADMIN_PAYMENT_INTEGRATION_API.md
.serena/
.codex/
frontend/coverage/
aicodex
output/

View File

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

View File

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

73
CLA.md Normal file
View File

@@ -0,0 +1,73 @@
# Sub2API Individual Contributor License Agreement (v1.0)
Thank you for your interest in contributing to Sub2API ("the Project"). This Contributor License Agreement ("Agreement") documents the rights granted by contributors to the Project.
By signing this Agreement, you accept and agree to the following terms and conditions for your present and future contributions submitted to the Project.
## 1. Definitions
- **"You" (or "Your")** means the copyright owner or legal entity authorized by the copyright owner that is making this Agreement.
- **"Contribution"** means any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project for inclusion in, or documentation of, any of the products owned or managed by the Project. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Project, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
- **"Project Owner"** means Wesley Liddick, or any individual or legal entity to whom Wesley Liddick has explicitly assigned or transferred ownership of the Project in writing, and their respective successors and assigns.
## 2. Grant of Copyright License
Subject to the terms and conditions of this Agreement, You hereby grant to the Project Owner a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. This license includes, without limitation, the right to sublicense, assign, and transfer these rights to any third party, including without limitation any successor, assignee, or acquiring entity of the Project or the Project Owner, and to use Your Contributions under any license, including proprietary or commercial licenses.
## 3. Moral Rights
To the fullest extent permitted by applicable law, You irrevocably waive and agree not to assert any moral rights (including rights of attribution and integrity) that You may have in Your Contributions, and agree that the Project Owner and its licensees may use, modify, and distribute Your Contributions without attribution or other obligations arising from moral rights.
## 4. Grant of Patent License
Subject to the terms and conditions of this Agreement, You hereby grant to the Project Owner a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer Your Contributions, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Project to which such Contribution(s) was submitted.
## 5. Representations and Warranties
You represent and warrant that:
(a) You are legally entitled to grant the above licenses.
(b) If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You have received permission to make Contributions on behalf of that employer, or that Your employer has waived such rights for Your Contributions to the Project.
(c) Each of Your Contributions is Your original creation, or You have sufficient rights to submit it under the terms of this Agreement. You agree to provide, upon request, reasonable documentation or explanation of any third-party materials included in Your Contributions.
## 6. No Warranty
Your Contributions are provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support.
## 7. No Obligation
You understand that the decision to include Your Contribution in any product or project is entirely at the discretion of the Project Owner, and this Agreement does not obligate the Project Owner to use Your Contribution.
## 8. Retention of Rights
You retain ownership of the copyright in Your Contributions. This Agreement does not transfer any copyright or other intellectual property rights from You to the Project Owner. This Agreement only grants the licenses described above.
## 9. Term and Termination
This Agreement shall remain in effect indefinitely. You may terminate this Agreement prospectively by providing written notice to the Project Owner, but such termination shall not affect the licenses granted for Contributions submitted prior to the effective date of termination. The licenses granted herein for Contributions submitted prior to termination are perpetual and irrevocable.
## 10. Electronic Signature
You agree that Your electronic signature (including but not limited to typing a specific phrase in a pull request, issue, or other electronic communication) is legally binding and has the same force and effect as a handwritten signature. You consent to the use of electronic means to enter into this Agreement and acknowledge that this Agreement is enforceable as if executed in a traditional written format.
## 11. General Provisions
**Entire Agreement.** This Agreement constitutes the entire agreement between You and the Project Owner with respect to Your Contributions and supersedes all prior or contemporaneous understandings regarding such subject matter.
**Severability.** If any provision of this Agreement is held to be unenforceable or invalid, that provision will be enforced to the maximum extent possible and the remaining provisions will remain in full force and effect.
**No Waiver.** The failure of the Project Owner to enforce any provision of this Agreement shall not constitute a waiver of that provision or any other provision.
**Amendment.** This Agreement may only be modified by a written instrument signed by both parties. Modifications to this Agreement apply only to Contributions submitted after the modified Agreement is published and accepted by You. Prior Contributions remain governed by the version of the Agreement in effect at the time of submission.
**Notification.** Notices under this Agreement shall be sent to the Project Owner via a GitHub issue on the Project repository. Notices are effective upon receipt.
---
**By signing this CLA, you acknowledge that you have read and understood this Agreement and agree to be bound by its terms.**
To sign, reply in the pull request with:
> I have read the CLA Document and I hereby sign the CLA

346
DEV_GUIDE.md Normal file
View File

@@ -0,0 +1,346 @@
# sub2api 项目开发指南
> 本文档记录项目环境配置、常见坑点和注意事项,供 Claude Code 和团队成员参考。
## 一、项目基本信息
| 项目 | 说明 |
|------|------|
| **上游仓库** | Wei-Shaw/sub2api |
| **Fork 仓库** | bayma888/sub2api-bmai |
| **技术栈** | Go 后端 (Ent ORM + Gin) + Vue3 前端 (pnpm) |
| **数据库** | PostgreSQL 16 + Redis |
| **包管理** | 后端: go modules, 前端: **pnpm**(不是 npm |
## 二、本地环境配置
### PostgreSQL 16 (Windows 服务)
| 配置项 | 值 |
|--------|-----|
| 端口 | 5432 |
| psql 路径 | `C:\Program Files\PostgreSQL\16\bin\psql.exe` |
| pg_hba.conf | `C:\Program Files\PostgreSQL\16\data\pg_hba.conf` |
| 数据库凭据 | user=`sub2api`, password=`sub2api`, dbname=`sub2api` |
| 超级用户 | user=`postgres`, password=`postgres` |
### Redis
| 配置项 | 值 |
|--------|-----|
| 端口 | 6379 |
| 密码 | 无 |
### 开发工具
```bash
# golangci-lint v2.7
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7
# pnpm (前端包管理)
npm install -g pnpm
```
## 三、CI/CD 流水线
### GitHub Actions Workflows
| Workflow | 触发条件 | 检查内容 |
|----------|----------|----------|
| **backend-ci.yml** | push, pull_request | 单元测试 + 集成测试 + golangci-lint v2.7 |
| **security-scan.yml** | push, pull_request, 每周一 | govulncheck + gosec + pnpm audit |
| **release.yml** | tag `v*` | 构建发布PR 不触发) |
### CI 要求
- Go 版本必须是 **1.25.7**
- 前端使用 `pnpm install --frozen-lockfile`,必须提交 `pnpm-lock.yaml`
### 本地测试命令
```bash
# 后端单元测试
cd backend && go test -tags=unit ./...
# 后端集成测试
cd backend && go test -tags=integration ./...
# 代码质量检查
cd backend && golangci-lint run ./...
# 前端依赖安装(必须用 pnpm
cd frontend && pnpm install
```
## 四、常见坑点 & 解决方案
### 坑 1pnpm-lock.yaml 必须同步提交
**问题**`package.json` 新增依赖后CI 的 `pnpm install --frozen-lockfile` 失败。
**原因**:上游 CI 使用 pnpmlock 文件不同步会报错。
**解决**
```bash
cd frontend
pnpm install # 更新 pnpm-lock.yaml
git add pnpm-lock.yaml
git commit -m "chore: update pnpm-lock.yaml"
```
---
### 坑 2npm 和 pnpm 的 node_modules 冲突
**问题**:之前用 npm 装过 `node_modules`pnpm install 报 `EPERM` 错误。
**解决**
```bash
cd frontend
rm -rf node_modules # 或 PowerShell: Remove-Item -Recurse -Force node_modules
pnpm install
```
---
### 坑 3PowerShell 中 bcrypt hash 的 `$` 被转义
**问题**bcrypt hash 格式如 `$2a$10$xxx...`PowerShell 把 `$2a` 当变量解析,导致数据丢失。
**解决**:将 SQL 写入文件,用 `psql -f` 执行:
```bash
# 错误示范PowerShell 会吃掉 $
psql -c "INSERT INTO users ... VALUES ('$2a$10$...')"
# 正确做法
echo "INSERT INTO users ... VALUES ('\$2a\$10\$...')" > temp.sql
psql -U sub2api -h 127.0.0.1 -d sub2api -f temp.sql
```
---
### 坑 4psql 不支持中文路径
**问题**`psql -f "D:\中文路径\file.sql"` 报错找不到文件。
**解决**:复制到纯英文路径再执行:
```bash
cp "D:\中文路径\file.sql" "C:\temp.sql"
psql -f "C:\temp.sql"
```
---
### 坑 5PostgreSQL 密码重置流程
**场景**:忘记 PostgreSQL 密码。
**步骤**
1. 修改 `C:\Program Files\PostgreSQL\16\data\pg_hba.conf`
```
# 将 scram-sha-256 改为 trust
host all all 127.0.0.1/32 trust
```
2. 重启 PostgreSQL 服务
```powershell
Restart-Service postgresql-x64-16
```
3. 无密码登录并重置
```bash
psql -U postgres -h 127.0.0.1
ALTER USER sub2api WITH PASSWORD 'sub2api';
ALTER USER postgres WITH PASSWORD 'postgres';
```
4. 改回 `scram-sha-256` 并重启
---
### 坑 6Go interface 新增方法后 test stub 必须补全
**问题**:给 interface 新增方法后,编译报错 `does not implement interface (missing method XXX)`。
**原因**:所有测试文件中实现该 interface 的 stub/mock 都必须补上新方法。
**解决**
```bash
# 搜索所有实现该 interface 的 struct
cd backend
grep -r "type.*Stub.*struct" internal/
grep -r "type.*Mock.*struct" internal/
# 逐一补全新方法
```
---
### 坑 7Windows 上 psql 连 localhost 的 IPv6 问题
**问题**psql 连 `localhost` 先尝试 IPv6 (::1),可能报错后再回退 IPv4。
**建议**:直接用 `127.0.0.1` 代替 `localhost`。
---
### 坑 8Windows 没有 make 命令
**问题**CI 里用 `make test-unit`,本地 Windows 没有 make。
**解决**:直接用 Makefile 里的原始命令:
```bash
# 代替 make test-unit
go test -tags=unit ./...
# 代替 make test-integration
go test -tags=integration ./...
```
---
### 坑 9Ent Schema 修改后必须重新生成
**问题**:修改 `ent/schema/*.go` 后,代码不生效。
**解决**
```bash
cd backend
go generate ./ent # 重新生成 ent 代码
git add ent/ # 生成的文件也要提交
```
---
### 坑 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 前务必本地验证:
- [ ] `go test -tags=unit ./...` 通过
- [ ] `go test -tags=integration ./...` 通过
- [ ] `golangci-lint run ./...` 无新增问题
- [ ] `pnpm-lock.yaml` 已同步(如果改了 package.json
- [ ] 所有 test stub 补全新接口方法(如果改了 interface
- [ ] Ent 生成的代码已提交(如果改了 schema
## 五、常用命令速查
### 数据库操作
```bash
# 连接数据库
psql -U sub2api -h 127.0.0.1 -d sub2api
# 查看所有用户
psql -U postgres -h 127.0.0.1 -c "\du"
# 查看所有数据库
psql -U postgres -h 127.0.0.1 -c "\l"
# 执行 SQL 文件
psql -U sub2api -h 127.0.0.1 -d sub2api -f migration.sql
```
### Git 操作
```bash
# 同步上游
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
# 创建功能分支
git checkout -b feature/xxx
# Rebase 到最新 main
git fetch upstream
git rebase upstream/main
```
### 前端操作
```bash
# 安装依赖(必须用 pnpm
cd frontend
pnpm install
# 开发服务器
pnpm dev
# 构建
pnpm build
```
### 后端操作
```bash
# 运行服务器
cd backend
go run ./cmd/server/
# 生成 Ent 代码
go generate ./ent
# 运行测试
go test -tags=unit ./...
go test -tags=integration ./...
# Lint 检查
golangci-lint run ./...
```
## 六、项目结构速览
```
sub2api-bmai/
├── backend/
│ ├── cmd/server/ # 主程序入口
│ ├── ent/ # Ent ORM 生成代码
│ │ └── schema/ # 数据库 Schema 定义
│ ├── internal/
│ │ ├── handler/ # HTTP 处理器
│ │ ├── service/ # 业务逻辑
│ │ ├── repository/ # 数据访问层
│ │ └── server/ # 服务器配置
│ ├── migrations/ # 数据库迁移脚本
│ └── config.yaml # 配置文件
├── frontend/
│ ├── src/
│ │ ├── api/ # API 调用
│ │ ├── components/ # Vue 组件
│ │ ├── views/ # 页面视图
│ │ ├── types/ # TypeScript 类型
│ │ └── i18n/ # 国际化
│ ├── package.json # 依赖配置
│ └── pnpm-lock.yaml # pnpm 锁文件(必须提交)
└── .claude/
└── CLAUDE.md # 本文档
```
## 七、参考资源
- [上游仓库](https://github.com/Wei-Shaw/sub2api)
- [Ent 文档](https://entgo.io/docs/getting-started)
- [Vue3 文档](https://vuejs.org/)
- [pnpm 文档](https://pnpm.io/)

View File

@@ -7,8 +7,9 @@
# ============================================================================= # =============================================================================
ARG NODE_IMAGE=node:24-alpine ARG NODE_IMAGE=node:24-alpine
ARG GOLANG_IMAGE=golang:1.25.5-alpine ARG GOLANG_IMAGE=golang:1.26.2-alpine
ARG ALPINE_IMAGE=alpine:3.20 ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine
ARG GOPROXY=https://goproxy.cn,direct ARG GOPROXY=https://goproxy.cn,direct
ARG GOSUMDB=sum.golang.google.cn ARG GOSUMDB=sum.golang.google.cn
@@ -36,7 +37,7 @@ RUN pnpm run build
FROM ${GOLANG_IMAGE} AS backend-builder FROM ${GOLANG_IMAGE} AS backend-builder
# Build arguments for version info (set by CI) # Build arguments for version info (set by CI)
ARG VERSION=docker ARG VERSION=
ARG COMMIT=docker ARG COMMIT=docker
ARG DATE ARG DATE
ARG GOPROXY ARG GOPROXY
@@ -61,14 +62,24 @@ COPY backend/ ./
COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist
# Build the binary (BuildType=release for CI builds, embed frontend) # 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 \ -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 \ -o /app/sub2api \
./cmd/server ./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} FROM ${ALPINE_IMAGE}
@@ -81,9 +92,21 @@ LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
RUN apk add --no-cache \ RUN apk add --no-cache \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
curl \ su-exec \
libpq \
zstd-libs \
lz4-libs \
krb5-libs \
libldap \
libedit \
&& rm -rf /var/cache/apk/* && 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 # Create non-root user
RUN addgroup -g 1000 sub2api && \ RUN addgroup -g 1000 sub2api && \
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
@@ -91,21 +114,24 @@ RUN addgroup -g 1000 sub2api && \
# Set working directory # Set working directory
WORKDIR /app WORKDIR /app
# Copy binary from builder # Copy binary/resources with ownership to avoid extra full-layer chown copy
COPY --from=backend-builder /app/sub2api /app/sub2api 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 # 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 # Copy entrypoint script (fixes volume permissions then drops to sub2api)
USER 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 port (can be overridden by SERVER_PORT env var)
EXPOSE 8080 EXPOSE 8080
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ 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 # Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
ENTRYPOINT ["/app/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. # 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 maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform" LABEL description="Sub2API - AI API Gateway Platform"
@@ -16,8 +21,21 @@ RUN apk add --no-cache \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
curl \ curl \
su-exec \
libpq \
zstd-libs \
lz4-libs \
krb5-libs \
libldap \
libedit \
&& rm -rf /var/cache/apk/* && 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 # Create non-root user
RUN addgroup -g 1000 sub2api && \ RUN addgroup -g 1000 sub2api && \
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
@@ -30,11 +48,15 @@ COPY sub2api /app/sub2api
# Create data directory # Create data directory
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app 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 EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1 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"]

178
LICENSE
View File

@@ -1,21 +1,165 @@
MIT License GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2025 Wesley Liddick Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all This version of the GNU Lesser General Public License incorporates
copies or substantial portions of the Software. the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0. Additional Definitions.
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE As used herein, "this License" refers to version 3 of the GNU Lesser
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER General Public License, and the "GNU GPL" refers to version 3 of the GNU
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, General Public License.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -1,368 +0,0 @@
# Linux DO Connect
OAuthOpen Authorization是一个开放的网络授权标准目前最新版本为 OAuth 2.0。我们日常使用的第三方登录(如 Google 账号登录就采用了该标准。OAuth 允许用户授权第三方应用访问存储在其他服务提供商(如 Google上的信息无需在不同平台上重复填写注册信息。用户授权后平台可以直接访问用户的账户信息进行身份验证而用户无需向第三方应用提供密码。
目前系统已实现完整的 OAuth2 授权码code方式鉴权但界面等配套功能还在持续完善中。让我们一起打造一个更完善的共享方案。
## 基本介绍
这是一套标准的 OAuth2 鉴权系统,可以让开发者共享论坛的用户基本信息。
- 可获取字段:
| 参数 | 说明 |
| ----------------- | ------------------------------- |
| `id` | 用户唯一标识(不可变) |
| `username` | 论坛用户名 |
| `name` | 论坛用户昵称(可变) |
| `avatar_template` | 用户头像模板URL支持多种尺寸 |
| `active` | 账号活跃状态 |
| `trust_level` | 信任等级0-4 |
| `silenced` | 禁言状态 |
| `external_ids` | 外部ID关联信息 |
| `api_key` | API访问密钥 |
通过这些信息,公益网站/接口可以实现:
1. 基于 `id` 的服务频率限制
2. 基于 `trust_level` 的服务额度分配
3. 基于用户信息的滥用举报机制
## 相关端点
- Authorize 端点: `https://connect.linux.do/oauth2/authorize`
- Token 端点:`https://connect.linux.do/oauth2/token`
- 用户信息 端点:`https://connect.linux.do/api/user`
## 申请使用
- 访问 [Connect.Linux.Do](https://connect.linux.do/) 申请接入你的应用。
![linuxdoconnect_1](https://wiki.linux.do/_next/image?url=%2Flinuxdoconnect_1.png&w=1080&q=75)
- 点击 **`我的应用接入`** - **`申请新接入`**,填写相关信息。其中 **`回调地址`** 是你的应用接收用户信息的地址。
![linuxdoconnect_2](https://wiki.linux.do/_next/image?url=%2Flinuxdoconnect_2.png&w=1080&q=75)
- 申请成功后,你将获得 **`Client Id`** 和 **`Client Secret`**,这是你应用的唯一身份凭证。
![linuxdoconnect_3](https://wiki.linux.do/_next/image?url=%2Flinuxdoconnect_3.png&w=1080&q=75)
## 接入 Linux Do
JavaScript
```JavaScript
// 安装第三方请求库(或使用原生的 Fetch API本例中使用 axios
// npm install axios
// 通过 OAuth2 获取 Linux Do 用户信息的参考流程
const axios = require('axios');
const readline = require('readline');
// 配置信息(建议通过环境变量配置,避免使用硬编码)
const CLIENT_ID = '你的 Client ID';
const CLIENT_SECRET = '你的 Client Secret';
const REDIRECT_URI = '你的回调地址';
const AUTH_URL = 'https://connect.linux.do/oauth2/authorize';
const TOKEN_URL = 'https://connect.linux.do/oauth2/token';
const USER_INFO_URL = 'https://connect.linux.do/api/user';
// 第一步:生成授权 URL
function getAuthUrl() {
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: 'user'
});
return `${AUTH_URL}?${params.toString()}`;
}
// 第二步:获取 code 参数
function getCode() {
return new Promise((resolve) => {
// 本例中使用终端输入来模拟流程,仅供本地测试
// 请在实际应用中替换为真实的处理逻辑
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question('从回调 URL 中提取出 code粘贴到此处并按回车', (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
// 第三步:使用 code 参数获取访问令牌
async function getAccessToken(code) {
try {
const form = new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: code,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code'
}).toString();
const response = await axios.post(TOKEN_URL, form, {
// 提醒:需正确配置请求头,否则无法正常获取访问令牌
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
return response.data;
} catch (error) {
console.error(`获取访问令牌失败:${error.response ? JSON.stringify(error.response.data) : error.message}`);
throw error;
}
}
// 第四步:使用访问令牌获取用户信息
async function getUserInfo(accessToken) {
try {
const response = await axios.get(USER_INFO_URL, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return response.data;
} catch (error) {
console.error(`获取用户信息失败:${error.response ? JSON.stringify(error.response.data) : error.message}`);
throw error;
}
}
// 主流程
async function main() {
// 1. 生成授权 URL前端引导用户访问授权页
const authUrl = getAuthUrl();
console.log(`请访问此 URL 授权:${authUrl}
`);
// 2. 用户授权后,从回调 URL 获取 code 参数
const code = await getCode();
try {
// 3. 使用 code 参数获取访问令牌
const tokenData = await getAccessToken(code);
const accessToken = tokenData.access_token;
// 4. 使用访问令牌获取用户信息
if (accessToken) {
const userInfo = await getUserInfo(accessToken);
console.log(`
获取用户信息成功:${JSON.stringify(userInfo, null, 2)}`);
} else {
console.log(`
获取访问令牌失败:${JSON.stringify(tokenData)}`);
}
} catch (error) {
console.error('发生错误:', error);
}
}
```
Python
```python
# 安装第三方请求库,本例中使用 requests
# pip install requests
# 通过 OAuth2 获取 Linux Do 用户信息的参考流程
import requests
import json
# 配置信息(建议通过环境变量配置,避免使用硬编码)
CLIENT_ID = '你的 Client ID'
CLIENT_SECRET = '你的 Client Secret'
REDIRECT_URI = '你的回调地址'
AUTH_URL = 'https://connect.linux.do/oauth2/authorize'
TOKEN_URL = 'https://connect.linux.do/oauth2/token'
USER_INFO_URL = 'https://connect.linux.do/api/user'
# 第一步:生成授权 URL
def get_auth_url():
params = {
'client_id': CLIENT_ID,
'redirect_uri': REDIRECT_URI,
'response_type': 'code',
'scope': 'user'
}
auth_url = f"{AUTH_URL}?{'&'.join(f'{k}={v}' for k, v in params.items())}"
return auth_url
# 第二步:获取 code 参数
def get_code():
# 本例中使用终端输入来模拟流程,仅供本地测试
# 请在实际应用中替换为真实的处理逻辑
return input('从回调 URL 中提取出 code粘贴到此处并按回车').strip()
# 第三步:使用 code 参数获取访问令牌
def get_access_token(code):
try:
data = {
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'code': code,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code'
}
# 提醒:需正确配置请求头,否则无法正常获取访问令牌
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
response = requests.post(TOKEN_URL, data=data, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"获取访问令牌失败:{e}")
return None
# 第四步:使用访问令牌获取用户信息
def get_user_info(access_token):
try:
headers = {
'Authorization': f'Bearer {access_token}'
}
response = requests.get(USER_INFO_URL, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"获取用户信息失败:{e}")
return None
# 主流程
if __name__ == '__main__':
# 1. 生成授权 URL前端引导用户访问授权页
auth_url = get_auth_url()
print(f'请访问此 URL 授权:{auth_url}
')
# 2. 用户授权后,从回调 URL 获取 code 参数
code = get_code()
# 3. 使用 code 参数获取访问令牌
token_data = get_access_token(code)
if token_data:
access_token = token_data.get('access_token')
# 4. 使用访问令牌获取用户信息
if access_token:
user_info = get_user_info(access_token)
if user_info:
print(f"
获取用户信息成功{json.dumps(user_info, indent=2)}")
else:
print("
获取用户信息失败")
else:
print(f"
获取访问令牌失败{json.dumps(token_data, indent=2)}")
else:
print("
获取访问令牌失败")
```
PHP
```php
// 通过 OAuth2 获取 Linux Do 用户信息的参考流程
// 配置信息
$CLIENT_ID = '你的 Client ID';
$CLIENT_SECRET = '你的 Client Secret';
$REDIRECT_URI = '你的回调地址';
$AUTH_URL = 'https://connect.linux.do/oauth2/authorize';
$TOKEN_URL = 'https://connect.linux.do/oauth2/token';
$USER_INFO_URL = 'https://connect.linux.do/api/user';
// 生成授权 URL
function getAuthUrl($clientId, $redirectUri) {
global $AUTH_URL;
return $AUTH_URL . '?' . http_build_query([
'client_id' => $clientId,
'redirect_uri' => $redirectUri,
'response_type' => 'code',
'scope' => 'user'
]);
}
// 使用 code 参数获取用户信息(合并获取令牌和获取用户信息的步骤)
function getUserInfoWithCode($code, $clientId, $clientSecret, $redirectUri) {
global $TOKEN_URL, $USER_INFO_URL;
// 1. 获取访问令牌
$ch = curl_init($TOKEN_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'client_id' => $clientId,
'client_secret' => $clientSecret,
'code' => $code,
'redirect_uri' => $redirectUri,
'grant_type' => 'authorization_code'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
]);
$tokenResponse = curl_exec($ch);
curl_close($ch);
$tokenData = json_decode($tokenResponse, true);
if (!isset($tokenData['access_token'])) {
return ['error' => '获取访问令牌失败', 'details' => $tokenData];
}
// 2. 获取用户信息
$ch = curl_init($USER_INFO_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $tokenData['access_token']
]);
$userResponse = curl_exec($ch);
curl_close($ch);
return json_decode($userResponse, true);
}
// 主流程
// 1. 生成授权 URL
$authUrl = getAuthUrl($CLIENT_ID, $REDIRECT_URI);
echo "<a href='$authUrl'>使用 Linux Do 登录</a>";
// 2. 处理回调并获取用户信息
if (isset($_GET['code'])) {
$userInfo = getUserInfoWithCode(
$_GET['code'],
$CLIENT_ID,
$CLIENT_SECRET,
$REDIRECT_URI
);
if (isset($userInfo['error'])) {
echo '错误: ' . $userInfo['error'];
} else {
echo '欢迎, ' . $userInfo['name'] . '!';
// 处理用户登录逻辑...
}
}
```
## 使用说明
### 授权流程
1. 用户点击应用中的’使用 Linux Do 登录’按钮
2. 系统将用户重定向至 Linux Do 的授权页面
3. 用户完成授权后,系统自动重定向回应用并携带授权码
4. 应用使用授权码获取访问令牌
5. 使用访问令牌获取用户信息
### 安全建议
- 切勿在前端代码中暴露 Client Secret
- 对所有用户输入数据进行严格验证
- 确保使用 HTTPS 协议传输数据
- 定期更新并妥善保管 Client Secret

View File

@@ -1,4 +1,12 @@
.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-frontend-critical test-datamanagementd secret-scan
FRONTEND_CRITICAL_VITEST := \
src/views/auth/__tests__/LinuxDoCallbackView.spec.ts \
src/views/auth/__tests__/WechatCallbackView.spec.ts \
src/views/user/__tests__/PaymentView.spec.ts \
src/views/user/__tests__/PaymentResultView.spec.ts \
src/components/user/profile/__tests__/ProfileInfoCard.spec.ts \
src/views/admin/__tests__/SettingsView.spec.ts
# 一键编译前后端 # 一键编译前后端
build: build-backend build-frontend build: build-backend build-frontend
@@ -11,6 +19,10 @@ build-backend:
build-frontend: build-frontend:
@pnpm --dir frontend run build @pnpm --dir frontend run build
# 编译 datamanagementd宿主机数据管理进程
build-datamanagementd:
@cd datamanagement && go build -o datamanagementd ./cmd/datamanagementd
# 运行测试(后端 + 前端) # 运行测试(后端 + 前端)
test: test-backend test-frontend test: test-backend test-frontend
@@ -20,3 +32,13 @@ test-backend:
test-frontend: test-frontend:
@pnpm --dir frontend run lint:check @pnpm --dir frontend run lint:check
@pnpm --dir frontend run typecheck @pnpm --dir frontend run typecheck
@$(MAKE) test-frontend-critical
test-frontend-critical:
@pnpm --dir frontend exec vitest run $(FRONTEND_CRITICAL_VITEST)
test-datamanagementd:
@cd datamanagement && go test ./...
secret-scan:
@python3 tools/secret_scan.py

256
README.md
View File

@@ -2,33 +2,37 @@
<div align="center"> <div align="center">
[![Go](https://img.shields.io/badge/Go-1.25.5-00ADD8.svg)](https://golang.org/) [![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/) [![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/) [![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/) [![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/) [![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** **AI API Gateway Platform for Subscription Quota Distribution**
English | [中文](README_CN.md) English | [中文](README_CN.md) | [日本語](README_JA.md)
</div> </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 ## Demo
Try Sub2API online: **https://v2.pincc.ai/** Try Sub2API online: **[https://demo.sub2api.org/](https://demo.sub2api.org/)**
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs): Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
| Email | Password | | Email | Password |
|-------|----------| |-------|----------|
| admin@sub2api.com | admin123 | | admin@sub2api.org | admin123 |
## Overview ## 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 ## Features
@@ -38,22 +42,96 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
- **Smart Scheduling** - Intelligent account selection with sticky sessions - **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits - **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate 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 - **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>
<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>Huge thanks to BmoPlus for sponsoring this project! BmoPlus is a highly reliable AI account provider built strictly for heavy AI users and developers. They offer rock-solid, ready-to-use accounts and official top-up services for ChatGPT Plus / ChatGPT Pro (Full Warranty) / Claude Pro / Super Grok / Gemini Pro. By registering and ordering through <a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus - Premium AI Accounts & Top-ups</a>, users can unlock the mind-blowing rate of 10% of the official GPT subscription price (90% OFF)</td>
</tr>
<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>Thanks to Bestproxy for sponsoring this project! <a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> provides high-purity residential IPs with dedicated one-IP-per-account support. By combining real home networks with fingerprint isolation, it enables link environment isolation and reduces the probability of association-based risk control.</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 ## Tech Stack
| Component | Technology | | Component | Technology |
|-----------|------------| |-----------|------------|
| Backend | Go 1.25.5, Gin, Ent | | Backend | Go 1.25.7, Gin, Ent |
| Frontend | Vue 3.4+, Vite 5+, TailwindCSS | | Frontend | Vue 3.4+, Vite 5+, TailwindCSS |
| Database | PostgreSQL 15+ | | Database | PostgreSQL 15+ |
| Cache/Queue | Redis 7+ | | Cache/Queue | Redis 7+ |
--- ---
## 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.
--- ---
@@ -128,7 +206,7 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
--- ---
### Method 2: Docker Compose ### Method 2: Docker Compose (Recommended)
Deploy with Docker Compose, including PostgreSQL and Redis containers. Deploy with Docker Compose, including PostgreSQL and Redis containers.
@@ -137,87 +215,157 @@ Deploy with Docker Compose, including PostgreSQL and Redis containers.
- Docker 20.10+ - Docker 20.10+
- Docker Compose v2+ - Docker Compose v2+
#### Installation Steps #### Quick Start (One-Click Deployment)
Use the automated deployment script for easy setup:
```bash
# Create deployment directory
mkdir -p sub2api-deploy && cd sub2api-deploy
# Download and run deployment preparation script
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Start services
docker compose up -d
# View logs
docker compose logs -f sub2api
```
**What the script does:**
- 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)
- Displays generated credentials for your reference
#### Manual Deployment
If you prefer manual setup:
```bash ```bash
# 1. Clone the repository # 1. Clone the repository
git clone https://github.com/Wei-Shaw/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api cd sub2api/deploy
# 2. Enter the deploy directory # 2. Copy environment configuration
cd deploy
# 3. Copy environment configuration
cp .env.example .env cp .env.example .env
# 4. Edit configuration (set your passwords) # 3. Edit configuration (generate secure passwords)
nano .env nano .env
``` ```
**Required configuration in `.env`:** **Required configuration in `.env`:**
```bash ```bash
# PostgreSQL password (REQUIRED - change this!) # PostgreSQL password (REQUIRED)
POSTGRES_PASSWORD=your_secure_password_here POSTGRES_PASSWORD=your_secure_password_here
# JWT Secret (RECOMMENDED - keeps users logged in after restart)
JWT_SECRET=your_jwt_secret_here
# TOTP Encryption Key (RECOMMENDED - preserves 2FA after restart)
TOTP_ENCRYPTION_KEY=your_totp_key_here
# Optional: Admin account # Optional: Admin account
ADMIN_EMAIL=admin@example.com ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password ADMIN_PASSWORD=your_admin_password
# Optional: Custom port # Optional: Custom port
SERVER_PORT=8080 SERVER_PORT=8080
```
# Optional: Security configuration **Generate secure secrets:**
# Enable URL allowlist validation (false to skip allowlist checks, only basic format validation) ```bash
SECURITY_URL_ALLOWLIST_ENABLED=false # Generate JWT_SECRET
openssl rand -hex 32
# Allow insecure HTTP URLs when allowlist is disabled (default: false, requires https) # Generate TOTP_ENCRYPTION_KEY
# ⚠️ WARNING: Enabling this allows HTTP (plaintext) URLs which can expose API keys openssl rand -hex 32
# Only recommended for:
# - Development/testing environments
# - Internal networks with trusted endpoints
# - When using local test servers (http://localhost)
# PRODUCTION: Keep this false or use HTTPS URLs only
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
# Allow private IP addresses for upstream/pricing/CRS (for internal deployments) # Generate POSTGRES_PASSWORD
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=false openssl rand -hex 32
``` ```
```bash ```bash
# 4. Create data directories (for local version)
mkdir -p data postgres_data redis_data
# 5. Start all services # 5. Start all services
docker-compose up -d # Option A: Local directory version (recommended - easy migration)
docker compose -f docker-compose.local.yml up -d
# Option B: Named volumes version (simple setup)
docker compose up -d
# 6. Check status # 6. Check status
docker-compose ps docker compose -f docker-compose.local.yml ps
# 7. View logs # 7. View logs
docker-compose logs -f sub2api docker compose -f docker-compose.local.yml logs -f sub2api
``` ```
#### Deployment Versions
| Version | Data Storage | Migration | Best For |
|---------|-------------|-----------|----------|
| **docker-compose.local.yml** | Local directories | ✅ Easy (tar entire directory) | Production, frequent backups |
| **docker-compose.yml** | Named volumes | ⚠️ Requires docker commands | Simple setup |
**Recommendation:** Use `docker-compose.local.yml` (deployed by script) for easier data management.
#### Access #### Access
Open `http://YOUR_SERVER_IP:8080` in your browser. 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"
```
#### Upgrade #### Upgrade
```bash ```bash
# Pull latest image and recreate container # Pull latest image and recreate container
docker-compose pull docker compose -f docker-compose.local.yml pull
docker-compose up -d docker compose -f docker-compose.local.yml up -d
```
#### Easy Migration (Local Directory Version)
When using `docker-compose.local.yml`, migrate to a new server easily:
```bash
# On source server
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
# Transfer to new server
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
``` ```
#### Useful Commands #### Useful Commands
```bash ```bash
# Stop all services # Stop all services
docker-compose down docker compose -f docker-compose.local.yml down
# Restart # Restart
docker-compose restart docker compose -f docker-compose.local.yml restart
# View all logs # View all logs
docker-compose logs -f docker compose -f docker-compose.local.yml logs -f
# Remove all data (caution!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
``` ```
--- ---
@@ -293,6 +441,12 @@ default:
rate_multiplier: 1.0 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`: Additional security-related options are available in `config.yaml`:
- `cors.allowed_origins` for CORS allowlist - `cors.allowed_origins` for CORS allowlist
@@ -445,9 +599,33 @@ sub2api/
└── install.sh # One-click installation script └── 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 ## License
MIT License This project is licensed under the [GNU Lesser General Public License v3.0](LICENSE) (or later).
Copyright (c) 2026 Wesley Liddick
--- ---

View File

@@ -2,33 +2,36 @@
<div align="center"> <div align="center">
[![Go](https://img.shields.io/badge/Go-1.25.5-00ADD8.svg)](https://golang.org/) [![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/) [![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/) [![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/) [![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/) [![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 网关平台 - 订阅配额分发管理** **AI API 网关平台 - 订阅配额分发管理**
[English](README.md) | 中文 [English](README.md) | 中文 | [日本語](README_JA.md)
</div> </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,22 +41,96 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
- **智能调度** - 智能账号选择,支持粘性会话 - **智能调度** - 智能账号选择,支持粘性会话
- **并发控制** - 用户级和账号级并发限制 - **并发控制** - 用户级和账号级并发限制
- **速率限制** - 可配置的请求和 Token 速率限制 - **速率限制** - 可配置的请求和 Token 速率限制
- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe用户自助充值无需独立部署支付服务[配置指南](docs/PAYMENT_CN.md)
- **管理后台** - Web 界面进行监控和管理 - **管理后台** - 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>
<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>感谢 BmoPlus 赞助了本项目BmoPlus 是一家专为AI订阅重度用户打造的可靠 AI 账号代充服务商,提供稳定的 ChatGPT Plus / ChatGPT Pro(全程质保) / Claude Pro / Super Grok / Gemini Pro 的官方代充&成品账号。 通过<a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus AI成品号专卖/代充</a>注册下单的用户可享GPT 官网订阅一折 的震撼价格!</td>
</tr>
<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>感谢 Bestproxy 赞助了本项目!<a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> 是一家提供高纯度住宅IP支持一号一IP独享结合真实家庭网络与指纹隔离可实现链路环境隔离降低关联风控概率。</td>
</tr>
</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 构建 |
## 技术栈 ## 技术栈
| 组件 | 技术 | | 组件 | 技术 |
|------|------| |------|------|
| 后端 | Go 1.25.5, Gin, Ent | | 后端 | Go 1.25.7, Gin, Ent |
| 前端 | Vue 3.4+, Vite 5+, TailwindCSS | | 前端 | Vue 3.4+, Vite 5+, TailwindCSS |
| 数据库 | PostgreSQL 15+ | | 数据库 | PostgreSQL 15+ |
| 缓存/队列 | Redis 7+ | | 缓存/队列 | Redis 7+ |
--- ---
## 文档 ## Nginx 反向代理注意事项
- 依赖安全:`docs/dependency-security.md` 通过 Nginx 反向代理 Sub2API或 CRS 服务)并搭配 Codex CLI 使用时,需要在 Nginx 配置的 `http` 块中添加:
```nginx
underscores_in_headers on;
```
Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),这会导致多账号环境下的粘性会话功能失效。
--- ---
@@ -128,7 +205,7 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
--- ---
### 方式二Docker Compose ### 方式二Docker Compose(推荐)
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。 使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
@@ -137,87 +214,169 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
- Docker 20.10+ - Docker 20.10+
- Docker Compose v2+ - 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 ```bash
# 1. 克隆仓库 # 1. 克隆仓库
git clone https://github.com/Wei-Shaw/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api cd sub2api/deploy
# 2. 进入 deploy 目录 # 2. 复制环境配置文件
cd deploy
# 3. 复制环境配置文件
cp .env.example .env cp .env.example .env
# 4. 编辑配置(设置密码 # 3. 编辑配置(生成安全密码)
nano .env nano .env
``` ```
**`.env` 必须配置项:** **`.env` 必须配置项:**
```bash ```bash
# PostgreSQL 密码(必须修改! # PostgreSQL 密码(必
POSTGRES_PASSWORD=your_secure_password_here 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_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password ADMIN_PASSWORD=your_admin_password
# 可选:自定义端口 # 可选:自定义端口
SERVER_PORT=8080 SERVER_PORT=8080
```
# 可选:安全配置 **生成安全密钥:**
# 启用 URL 白名单验证false 则跳过白名单检查,仅做基本格式校验) ```bash
SECURITY_URL_ALLOWLIST_ENABLED=false # 生成 JWT_SECRET
openssl rand -hex 32
# 关闭白名单时,是否允许 http:// URL默认 false只允许 https:// # 生成 TOTP_ENCRYPTION_KEY
# ⚠️ 警告:允许 HTTP 会暴露 API 密钥(明文传输) openssl rand -hex 32
# 仅建议在以下场景使用:
# - 开发/测试环境
# - 内部可信网络
# - 本地测试服务器http://localhost
# 生产环境:保持 false 或仅使用 HTTPS URL
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
# 是否允许私有 IP 地址用于上游/定价/CRS内网部署时使用 # 生成 POSTGRES_PASSWORD
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=false openssl rand -hex 32
``` ```
```bash ```bash
# 4. 创建数据目录(本地版)
mkdir -p data postgres_data redis_data
# 5. 启动所有服务 # 5. 启动所有服务
docker-compose up -d # 选项 A本地目录版推荐 - 易于迁移)
docker compose -f docker-compose.local.yml up -d
# 选项 B命名卷版简单设置
docker compose up -d
# 6. 查看状态 # 6. 查看状态
docker-compose ps docker compose -f docker-compose.local.yml ps
# 7. 查看日志 # 7. 查看日志
docker-compose logs -f sub2api docker compose -f docker-compose.local.yml logs -f sub2api
``` ```
#### 部署版本对比
| 版本 | 数据存储 | 迁移便利性 | 适用场景 |
|------|---------|-----------|---------|
| **docker-compose.local.yml** | 本地目录 | ✅ 简单(打包整个目录) | 生产环境、频繁备份 |
| **docker-compose.yml** | 命名卷 | ⚠️ 需要 docker 命令 | 简单设置 |
**推荐:** 使用 `docker-compose.local.yml`(脚本部署)以便更轻松地管理数据。
#### 启用“数据管理”功能datamanagementd
如需启用管理后台“数据管理”,需要额外部署宿主机数据管理进程 `datamanagementd`
关键点:
- 主进程固定探测:`/tmp/sub2api-datamanagement.sock`
- 只有该 Socket 可连通时,数据管理功能才会开启
- Docker 场景需将宿主机 Socket 挂载到容器同路径
详细部署步骤见:`deploy/DATAMANAGEMENTD_CN.md`
#### 访问 #### 访问
在浏览器中打开 `http://你的服务器IP:8080` 在浏览器中打开 `http://你的服务器IP:8080`
如果管理员密码是自动生成的,在日志中查找:
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```
#### 升级 #### 升级
```bash ```bash
# 拉取最新镜像并重建容器 # 拉取最新镜像并重建容器
docker-compose pull docker compose -f docker-compose.local.yml pull
docker-compose up -d 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 ```bash
# 停止所有服务 # 停止所有服务
docker-compose down docker compose -f docker-compose.local.yml down
# 重启 # 重启
docker-compose restart docker compose -f docker-compose.local.yml restart
# 查看所有日志 # 查看所有日志
docker-compose logs -f docker compose -f docker-compose.local.yml logs -f
# 删除所有数据(谨慎!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
``` ```
--- ---
@@ -293,6 +452,33 @@ default:
rate_multiplier: 1.0 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` 还支持以下安全相关配置: `config.yaml` 还支持以下安全相关配置:
- `cors.allowed_origins` 配置 CORS 白名单 - `cors.allowed_origins` 配置 CORS 白名单
@@ -306,6 +492,14 @@ default:
- `server.trusted_proxies` 启用可信代理解析 X-Forwarded-For - `server.trusted_proxies` 启用可信代理解析 X-Forwarded-For
- `turnstile.required` 在 release 模式强制启用 Turnstile - `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 配置** **⚠️ 安全警告HTTP URL 配置**
`security.url_allowlist.enabled=false` 时,系统默认执行最小 URL 校验,**拒绝 HTTP URL**,仅允许 HTTPS。要允许 HTTP URL例如用于开发或内网测试必须显式设置 `security.url_allowlist.enabled=false` 时,系统默认执行最小 URL 校验,**拒绝 HTTP URL**,仅允许 HTTPS。要允许 HTTP URL例如用于开发或内网测试必须显式设置
@@ -351,6 +545,29 @@ Invalid base URL: invalid url scheme: http
./sub2api ./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 ```bash
@@ -443,9 +660,33 @@ sub2api/
└── install.sh # 一键安装脚本 └── 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 本项目基于 [GNU 宽通用公共许可证 v3.0](LICENSE)(或更高版本)授权。
Copyright (c) 2026 Wesley Liddick
--- ---

635
README_JA.md Normal file
View File

@@ -0,0 +1,635 @@
# 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>
<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>本プロジェクトにご支援いただいた BmoPlus に感謝いたしますBmoPlusは、AIサブスクリプションのヘビーユーザー向けに特化した信頼性の高いAIアカウントサービスプロバイダーであり、安定した ChatGPT Plus / ChatGPT Pro (完全保証) / Claude Pro / Super Grok / Gemini Pro の公式代行チャージおよび即納アカウントを提供しています。こちらの<a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus AIアカウント専門店/代行チャージ</a>経由でご登録・ご注文いただいたユーザー様は、GPTを 公式サイト価格の約1割90% OFF という驚異的な価格でご利用いただけます!</td>
</tr>
<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>Bestproxy のご支援に感謝します!<a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> は高純度の住宅IPを提供し、1アカウント1IP専有をサポートしています。実際の家庭ネットワークとフィンガープリント分離を組み合わせることで、リンク環境の分離を実現し、関連付けによるリスク管理の確率を低減します。</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>
---
## ライセンス
本プロジェクトは [GNU Lesser General Public License v3.0](LICENSE)(またはそれ以降のバージョン)の下でライセンスされています。
Copyright (c) 2026 Wesley Liddick
---
<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: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 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: enable:
- depguard - depguard
- errcheck - errcheck
- gosec
- govet - govet
- ineffassign - ineffassign
- staticcheck - staticcheck
@@ -42,6 +43,22 @@ linters:
desc: "handler must not import gorm" desc: "handler must not import gorm"
- pkg: github.com/redis/go-redis/v9 - pkg: github.com/redis/go-redis/v9
desc: "handler must not import redis" 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: errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`. # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default. # Such cases aren't reported by default.
@@ -76,20 +93,13 @@ linters:
check-escaping-errors: true check-escaping-errors: true
staticcheck: staticcheck:
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist # 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: dot-import-whitelist:
- fmt - fmt
# https://staticcheck.dev/docs/configuration/options/#initialisms # 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" ] 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 # https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
# Default: ["200", "400", "404", "500"]
http-status-code-whitelist: [ "200", "400", "404", "500" ] http-status-code-whitelist: [ "200", "400", "404", "500" ]
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks # "all" enables every SA/ST/S/QF check; only list the ones to disable.
# 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
checks: checks:
- all - all
- -ST1000 # Package comment format - -ST1000 # Package comment format
@@ -97,489 +107,19 @@ linters:
- -ST1020 # Comment on exported method format - -ST1020 # Comment on exported method format
- -ST1021 # Comment on exported type format - -ST1021 # Comment on exported type format
- -ST1022 # Comment on exported variable 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: unused:
# Mark all struct fields that have been written to as used.
# Default: true # Default: true
field-writes-are-uses: false field-writes-are-uses: true
# Treat IncDec statement (e.g. `i++` or `i--`) as both read and write operation instead of just write.
# Default: false # Default: false
post-statements-are-reads: true 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 # 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: formatters:
enable: enable:

View File

@@ -1,4 +1,4 @@
FROM golang:1.25.5-alpine FROM golang:1.25.7-alpine
WORKDIR /app WORKDIR /app
@@ -15,7 +15,7 @@ RUN go mod download
COPY . . COPY . .
# 构建应用 # 构建应用
RUN go build -o main cmd/server/main.go RUN go build -o main ./cmd/server/
# 暴露端口 # 暴露端口
EXPOSE 8080 EXPOSE 8080

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: 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: test:
go test ./... go test ./...
@@ -14,4 +21,7 @@ test-integration:
go test -tags=integration ./... go test -tags=integration ./...
test-e2e: 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)") email := flag.String("email", "", "Admin email to issue a JWT for (defaults to first active admin)")
flag.Parse() flag.Parse()
cfg, err := config.Load() cfg, err := config.LoadForBootstrap()
if err != nil { if err != nil {
log.Fatalf("failed to load config: %v", err) log.Fatalf("failed to load config: %v", err)
} }
@@ -33,7 +33,7 @@ func main() {
}() }()
userRepo := repository.NewUserRepository(client, sqlDB) userRepo := repository.NewUserRepository(client, sqlDB)
authService := service.NewAuthService(userRepo, cfg, nil, nil, nil, nil, nil) authService := service.NewAuthService(client, userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil, nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()

View File

@@ -1 +1 @@
0.1.46 0.1.118

View File

@@ -18,11 +18,14 @@ import (
_ "github.com/Wei-Shaw/sub2api/ent/runtime" _ "github.com/Wei-Shaw/sub2api/ent/runtime"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler" "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/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/setup" "github.com/Wei-Shaw/sub2api/internal/setup"
"github.com/Wei-Shaw/sub2api/internal/web" "github.com/Wei-Shaw/sub2api/internal/web"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
) )
//go:embed VERSION //go:embed VERSION
@@ -37,14 +40,24 @@ var (
) )
func init() { func init() {
// Read version from embedded VERSION file // 如果 Version 已通过 ldflags 注入(例如 -X main.Version=...),则不要覆盖。
if strings.TrimSpace(Version) != "" {
return
}
// 默认从 embedded VERSION 文件读取版本号(编译期打包进二进制)。
Version = strings.TrimSpace(embeddedVersion) Version = strings.TrimSpace(embeddedVersion)
if Version == "" { if Version == "" {
Version = "0.0.0-dev" Version = "0.0.0-dev"
} }
} }
// initLogger configures the default slog handler based on gin.Mode().
// In non-release mode, Debug level logs are enabled.
func main() { func main() {
logger.InitBootstrap()
defer logger.Sync()
// Parse command line flags // Parse command line flags
setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode") setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
showVersion := flag.Bool("version", false, "Show version information") showVersion := flag.Bool("version", false, "Show version information")
@@ -87,7 +100,7 @@ func runSetupServer() {
r := gin.New() r := gin.New()
r.Use(middleware.Recovery()) r.Use(middleware.Recovery())
r.Use(middleware.CORS(config.CORSConfig{})) 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 // Register setup routes
setup.RegisterRoutes(r) setup.RegisterRoutes(r)
@@ -103,16 +116,26 @@ func runSetupServer() {
log.Printf("Setup wizard available at http://%s", addr) log.Printf("Setup wizard available at http://%s", addr)
log.Println("Complete the setup wizard to configure Sub2API") 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) log.Fatalf("Failed to start setup server: %v", err)
} }
} }
func runMainServer() { func runMainServer() {
cfg, err := config.Load() cfg, err := config.LoadForBootstrap()
if err != nil { if err != nil {
log.Fatalf("Failed to load config: %v", err) 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 { if cfg.RunMode == config.RunModeSimple {
log.Println("⚠️ WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED") log.Println("⚠️ WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED")
} }

View File

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

View File

@@ -12,6 +12,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler" "github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/handler/admin" "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/repository"
"github.com/Wei-Shaw/sub2api/internal/server" "github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/server/middleware"
@@ -19,6 +20,7 @@ import (
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"log" "log"
"net/http" "net/http"
"sync"
"time" "time"
) )
@@ -43,9 +45,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
return nil, err return nil, err
} }
userRepository := repository.NewUserRepository(client, db) userRepository := repository.NewUserRepository(client, db)
settingRepository := repository.NewSettingRepository(client) redeemCodeRepository := repository.NewRedeemCodeRepository(client)
settingService := service.NewSettingService(settingRepository, configConfig)
redisClient := repository.ProvideRedis(configConfig) redisClient := repository.ProvideRedis(configConfig)
refreshTokenCache := repository.NewRefreshTokenCache(redisClient)
settingRepository := repository.NewSettingRepository(client)
groupRepository := repository.NewGroupRepository(client, db)
proxyRepository := repository.NewProxyRepository(client, db)
settingService := service.ProvideSettingService(settingRepository, groupRepository, proxyRepository, configConfig)
emailCache := repository.NewEmailCache(redisClient) emailCache := repository.NewEmailCache(redisClient)
emailService := service.NewEmailService(settingRepository, emailCache) emailService := service.NewEmailService(settingRepository, emailCache)
turnstileVerifier := repository.NewTurnstileVerifier() turnstileVerifier := repository.NewTurnstileVerifier()
@@ -54,108 +60,192 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
promoCodeRepository := repository.NewPromoCodeRepository(client) promoCodeRepository := repository.NewPromoCodeRepository(client)
billingCache := repository.NewBillingCache(redisClient) billingCache := repository.NewBillingCache(redisClient)
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client) userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig) apiKeyRepository := repository.NewAPIKeyRepository(client, db)
apiKeyRepository := repository.NewAPIKeyRepository(client) userRPMCache := repository.NewUserRPMCache(redisClient)
groupRepository := repository.NewGroupRepository(client, db) userGroupRateRepository := repository.NewUserGroupRateRepository(db)
billingCacheService := service.ProvideBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, userRPMCache, userGroupRateRepository, configConfig)
apiKeyCache := repository.NewAPIKeyCache(redisClient) apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, apiKeyCache, configConfig) apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig)
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService) apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator) promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
authService := service.NewAuthService(userRepository, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService) subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator) affiliateRepository := repository.NewAffiliateRepository(client, db)
authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService, promoService) affiliateService := service.NewAffiliateService(affiliateRepository, settingService, apiKeyAuthCacheInvalidator, billingCacheService)
userHandler := handler.NewUserHandler(userService) authService := service.NewAuthService(client, userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService, subscriptionService, affiliateService)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService) userService := service.NewUserService(userRepository, settingRepository, apiKeyAuthCacheInvalidator, billingCache)
usageLogRepository := repository.NewUsageLogRepository(client, db)
dashboardAggregationRepository := repository.NewDashboardAggregationRepository(db)
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
redeemCodeRepository := repository.NewRedeemCodeRepository(client)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService)
redeemCache := repository.NewRedeemCache(redisClient) redeemCache := repository.NewRedeemCache(redisClient)
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator) redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
secretEncryptor, err := repository.NewAESEncryptor(configConfig)
if err != nil {
return nil, err
}
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, authService, emailService, emailCache, affiliateService)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageLogRepository := repository.NewUsageLogRepository(client, db)
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
redeemHandler := handler.NewRedeemHandler(redeemService) redeemHandler := handler.NewRedeemHandler(redeemService)
subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService) subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService)
announcementRepository := repository.NewAnnouncementRepository(client)
announcementReadRepository := repository.NewAnnouncementReadRepository(client)
announcementService := service.NewAnnouncementService(announcementRepository, announcementReadRepository, userRepository, userSubscriptionRepository)
announcementHandler := handler.NewAnnouncementHandler(announcementService)
channelMonitorRepository := repository.NewChannelMonitorRepository(client, db)
channelMonitorService := service.ProvideChannelMonitorService(channelMonitorRepository, secretEncryptor)
channelMonitorUserHandler := handler.NewChannelMonitorUserHandler(channelMonitorService, settingService)
dashboardAggregationRepository := repository.NewDashboardAggregationRepository(db)
dashboardStatsCache := repository.NewDashboardCache(redisClient, configConfig) dashboardStatsCache := repository.NewDashboardCache(redisClient, configConfig)
timingWheelService := service.ProvideTimingWheelService()
dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig)
dashboardService := service.NewDashboardService(usageLogRepository, dashboardAggregationRepository, dashboardStatsCache, configConfig) dashboardService := service.NewDashboardService(usageLogRepository, dashboardAggregationRepository, dashboardStatsCache, configConfig)
timingWheelService, err := service.ProvideTimingWheelService()
if err != nil {
return nil, err
}
dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig)
dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService) dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService)
accountRepository := repository.NewAccountRepository(client, db) schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
proxyRepository := repository.NewProxyRepository(client, db) accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig) proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, billingCacheService, proxyExitInfoProber, apiKeyAuthCacheInvalidator) proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
adminUserHandler := admin.NewUserHandler(adminService) privacyClientFactory := providePrivacyClientFactory()
groupHandler := admin.NewGroupHandler(adminService) adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, userRPMCache, 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)
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() claudeOAuthClient := repository.NewClaudeOAuthClient()
oAuthService := service.NewOAuthService(proxyRepository, claudeOAuthClient) oAuthService := service.NewOAuthService(proxyRepository, claudeOAuthClient)
openAIOAuthClient := repository.NewOpenAIOAuthClient() openAIOAuthClient := repository.NewOpenAIOAuthClient()
openAIOAuthService := service.NewOpenAIOAuthService(proxyRepository, openAIOAuthClient) openAIOAuthService := service.NewOpenAIOAuthService(proxyRepository, openAIOAuthClient)
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig) geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient() 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) antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository) geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository)
tempUnschedCache := repository.NewTempUnschedCache(redisClient) tempUnschedCache := repository.NewTempUnschedCache(redisClient)
timeoutCounterCache := repository.NewTimeoutCounterCache(redisClient) timeoutCounterCache := repository.NewTimeoutCounterCache(redisClient)
rateLimitService := service.ProvideRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache, timeoutCounterCache, settingService) openAI403CounterCache := repository.NewOpenAI403CounterCache(redisClient)
claudeUsageFetcher := repository.NewClaudeUsageFetcher() geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
compositeTokenCacheInvalidator := service.NewCompositeTokenCacheInvalidator(geminiTokenCache)
rateLimitService := service.ProvideRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache, timeoutCounterCache, openAI403CounterCache, settingService, compositeTokenCacheInvalidator)
httpUpstream := repository.NewHTTPUpstream(configConfig)
claudeUsageFetcher := repository.NewClaudeUsageFetcher(httpUpstream)
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository) antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
usageCache := service.NewUsageCache() usageCache := service.NewUsageCache()
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache) identityCache := repository.NewIdentityCache(redisClient)
geminiTokenCache := repository.NewGeminiTokenCache(redisClient) tlsFingerprintProfileRepository := repository.NewTLSFingerprintProfileRepository(client)
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService) tlsFingerprintProfileCache := repository.NewTLSFingerprintProfileCache(redisClient)
tlsFingerprintProfileService := service.NewTLSFingerprintProfileService(tlsFingerprintProfileRepository, tlsFingerprintProfileCache)
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache, tlsFingerprintProfileService)
oAuthRefreshAPI := service.ProvideOAuthRefreshAPI(accountRepository, geminiTokenCache)
geminiTokenProvider := service.ProvideGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService, oAuthRefreshAPI)
gatewayCache := repository.NewGatewayCache(redisClient) gatewayCache := repository.NewGatewayCache(redisClient)
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
httpUpstream := repository.NewHTTPUpstream(configConfig)
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
schedulerCache := repository.NewSchedulerCache(redisClient)
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db) schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig) schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, 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) crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService) 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) oAuthHandler := admin.NewOAuthHandler(oAuthService)
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService) openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService) geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService) antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService)
proxyHandler := admin.NewProxyHandler(adminService) proxyHandler := admin.NewProxyHandler(adminService)
adminRedeemHandler := admin.NewRedeemHandler(adminService) adminRedeemHandler := admin.NewRedeemHandler(adminService, redeemService)
promoHandler := admin.NewPromoHandler(promoService) promoHandler := admin.NewPromoHandler(promoService)
opsRepository := repository.NewOpsRepository(db) opsRepository := repository.NewOpsRepository(db)
usageBillingRepository := repository.NewUsageBillingRepository(client, db)
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig) pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient) pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
billingService := service.NewBillingService(configConfig, pricingService) billingService := service.NewBillingService(configConfig, pricingService)
identityCache := repository.NewIdentityCache(redisClient)
identityService := service.NewIdentityService(identityCache) identityService := service.NewIdentityService(identityCache)
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService) deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService) claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oAuthRefreshAPI)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService) digestSessionStore := service.NewDigestSessionStore()
channelRepository := repository.NewChannelRepository(db)
channelService := service.NewChannelService(channelRepository, groupRepository, apiKeyAuthCacheInvalidator, pricingService)
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) geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService) opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService) opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
encryptionKey, err := payment.ProvideEncryptionKey(configConfig)
if err != nil {
return nil, err
}
paymentConfigService := service.ProvidePaymentConfigService(client, settingRepository, encryptionKey)
registry := payment.ProvideRegistry()
defaultLoadBalancer := payment.ProvideDefaultLoadBalancer(client, encryptionKey)
paymentService := service.NewPaymentService(client, registry, defaultLoadBalancer, redeemService, subscriptionService, paymentConfigService, userRepository, groupRepository, affiliateService)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService)
opsHandler := admin.NewOpsHandler(opsService) opsHandler := admin.NewOpsHandler(opsService)
updateCache := repository.NewUpdateCache(redisClient) updateCache := repository.NewUpdateCache(redisClient)
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig) gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
serviceBuildInfo := provideServiceBuildInfo(buildInfo) serviceBuildInfo := provideServiceBuildInfo(buildInfo)
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo) 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) adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
adminUsageHandler := admin.NewUsageHandler(usageService, apiKeyService, adminService) usageCleanupRepository := repository.NewUsageCleanupRepository(client, db)
usageCleanupService := service.ProvideUsageCleanupService(usageCleanupRepository, timingWheelService, dashboardAggregationService, configConfig)
adminUsageHandler := admin.NewUsageHandler(usageService, apiKeyService, adminService, usageCleanupService)
userAttributeDefinitionRepository := repository.NewUserAttributeDefinitionRepository(client) userAttributeDefinitionRepository := repository.NewUserAttributeDefinitionRepository(client)
userAttributeValueRepository := repository.NewUserAttributeValueRepository(client) userAttributeValueRepository := repository.NewUserAttributeValueRepository(client)
userAttributeService := service.NewUserAttributeService(userAttributeDefinitionRepository, userAttributeValueRepository) userAttributeService := service.NewUserAttributeService(userAttributeDefinitionRepository, userAttributeValueRepository)
userAttributeHandler := admin.NewUserAttributeHandler(userAttributeService) userAttributeHandler := admin.NewUserAttributeHandler(userAttributeService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler) errorPassthroughRepository := repository.NewErrorPassthroughRepository(client)
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, configConfig) errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, configConfig) errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache)
errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService)
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)
channelMonitorHandler := admin.NewChannelMonitorHandler(channelMonitorService)
channelMonitorRequestTemplateRepository := repository.NewChannelMonitorRequestTemplateRepository(client, db)
channelMonitorRequestTemplateService := service.NewChannelMonitorRequestTemplateService(channelMonitorRequestTemplateRepository)
channelMonitorRequestTemplateHandler := admin.NewChannelMonitorRequestTemplateHandler(channelMonitorRequestTemplateService)
paymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService)
affiliateHandler := admin.NewAffiliateHandler(affiliateService, adminService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, paymentHandler, affiliateHandler)
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) handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler) totpHandler := handler.NewTotpHandler(totpService)
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService)
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
availableChannelHandler := handler.NewAvailableChannelHandler(channelService, apiKeyService, settingService)
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, availableChannelHandler, idempotencyCoordinator, idempotencyCleanupService)
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService) jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService) adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig) apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
@@ -164,11 +254,15 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig) opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig) opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig) opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig)
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig) opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig, channelMonitorService)
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig) opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, configConfig) tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oAuthRefreshAPI)
accountExpiryService := service.ProvideAccountExpiryService(accountRepository) accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService) subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig)
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService)
channelMonitorRunner := service.ProvideChannelMonitorRunner(channelMonitorService, settingService)
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, channelMonitorRunner)
application := &Application{ application := &Application{
Server: httpServer, Server: httpServer,
Cleanup: v, Cleanup: v,
@@ -183,6 +277,10 @@ type Application struct {
Cleanup func() Cleanup func()
} }
func providePrivacyClientFactory() service.PrivacyClientFactory {
return repository.CreatePrivacyReqClient
}
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo { func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
return service.BuildInfo{ return service.BuildInfo{
Version: buildInfo.Version, Version: buildInfo.Version,
@@ -198,25 +296,38 @@ func provideCleanup(
opsAlertEvaluator *service.OpsAlertEvaluatorService, opsAlertEvaluator *service.OpsAlertEvaluatorService,
opsCleanup *service.OpsCleanupService, opsCleanup *service.OpsCleanupService,
opsScheduledReport *service.OpsScheduledReportService, opsScheduledReport *service.OpsScheduledReportService,
opsSystemLogSink *service.OpsSystemLogSink,
schedulerSnapshot *service.SchedulerSnapshotService, schedulerSnapshot *service.SchedulerSnapshotService,
tokenRefresh *service.TokenRefreshService, tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService, accountExpiry *service.AccountExpiryService,
subscriptionExpiry *service.SubscriptionExpiryService,
usageCleanup *service.UsageCleanupService,
idempotencyCleanup *service.IdempotencyCleanupService,
pricing *service.PricingService, pricing *service.PricingService,
emailQueue *service.EmailQueueService, emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService, billingCache *service.BillingCacheService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
subscriptionService *service.SubscriptionService,
oauth *service.OAuthService, oauth *service.OAuthService,
openaiOAuth *service.OpenAIOAuthService, openaiOAuth *service.OpenAIOAuthService,
geminiOAuth *service.GeminiOAuthService, geminiOAuth *service.GeminiOAuthService,
antigravityOAuth *service.AntigravityOAuthService, antigravityOAuth *service.AntigravityOAuthService,
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
channelMonitorRunner *service.ChannelMonitorRunner,
) func() { ) func() {
return func() { return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
cleanupSteps := []struct { type cleanupStep struct {
name string name string
fn func() error fn func() error
}{ }
parallelSteps := []cleanupStep{
{"OpsScheduledReportService", func() error { {"OpsScheduledReportService", func() error {
if opsScheduledReport != nil { if opsScheduledReport != nil {
opsScheduledReport.Stop() opsScheduledReport.Stop()
@@ -229,6 +340,12 @@ func provideCleanup(
} }
return nil return nil
}}, }},
{"OpsSystemLogSink", func() error {
if opsSystemLogSink != nil {
opsSystemLogSink.Stop()
}
return nil
}},
{"OpsAlertEvaluatorService", func() error { {"OpsAlertEvaluatorService", func() error {
if opsAlertEvaluator != nil { if opsAlertEvaluator != nil {
opsAlertEvaluator.Stop() opsAlertEvaluator.Stop()
@@ -253,6 +370,18 @@ func provideCleanup(
} }
return nil return nil
}}, }},
{"UsageCleanupService", func() error {
if usageCleanup != nil {
usageCleanup.Stop()
}
return nil
}},
{"IdempotencyCleanupService", func() error {
if idempotencyCleanup != nil {
idempotencyCleanup.Stop()
}
return nil
}},
{"TokenRefreshService", func() error { {"TokenRefreshService", func() error {
tokenRefresh.Stop() tokenRefresh.Stop()
return nil return nil
@@ -261,6 +390,16 @@ func provideCleanup(
accountExpiry.Stop() accountExpiry.Stop()
return nil return nil
}}, }},
{"SubscriptionExpiryService", func() error {
subscriptionExpiry.Stop()
return nil
}},
{"SubscriptionService", func() error {
if subscriptionService != nil {
subscriptionService.Stop()
}
return nil
}},
{"PricingService", func() error { {"PricingService", func() error {
pricing.Stop() pricing.Stop()
return nil return nil
@@ -273,6 +412,12 @@ func provideCleanup(
billingCache.Stop() billingCache.Stop()
return nil return nil
}}, }},
{"UsageRecordWorkerPool", func() error {
if usageRecordWorkerPool != nil {
usageRecordWorkerPool.Stop()
}
return nil
}},
{"OAuthService", func() error { {"OAuthService", func() error {
oauth.Stop() oauth.Stop()
return nil return nil
@@ -289,23 +434,84 @@ func provideCleanup(
antigravityOAuth.Stop() antigravityOAuth.Stop()
return nil 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
}},
{"ChannelMonitorRunner", func() error {
if channelMonitorRunner != nil {
channelMonitorRunner.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{
{"Redis", func() error { {"Redis", func() error {
if rdb == nil {
return nil
}
return rdb.Close() return rdb.Close()
}}, }},
{"Ent", func() error { {"Ent", func() error {
if entClient == nil {
return nil
}
return entClient.Close() return entClient.Close()
}}, }},
} }
for _, step := range cleanupSteps { runParallel := func(steps []cleanupStep) {
if err := step.fn(); err != nil { var wg sync.WaitGroup
log.Printf("[Cleanup] %s failed: %v", step.name, err) 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) log.Printf("[Cleanup] %s succeeded", step.name)
} }
} }
runParallel(parallelSteps)
runSequential(infraSteps)
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds") log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds")

View File

@@ -0,0 +1,85 @@
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, 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
nil, // channelMonitorRunner
)
require.NotPanics(t, func() {
cleanup()
})
}

View File

@@ -41,8 +41,12 @@ type Account struct {
ProxyID *int64 `json:"proxy_id,omitempty"` ProxyID *int64 `json:"proxy_id,omitempty"`
// Concurrency holds the value of the "concurrency" field. // Concurrency holds the value of the "concurrency" field.
Concurrency int `json:"concurrency,omitempty"` 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 holds the value of the "priority" field.
Priority int `json:"priority,omitempty"` Priority int `json:"priority,omitempty"`
// RateMultiplier holds the value of the "rate_multiplier" field.
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
// Status holds the value of the "status" field. // Status holds the value of the "status" field.
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
// ErrorMessage holds the value of the "error_message" field. // ErrorMessage holds the value of the "error_message" field.
@@ -61,6 +65,10 @@ type Account struct {
RateLimitResetAt *time.Time `json:"rate_limit_reset_at,omitempty"` RateLimitResetAt *time.Time `json:"rate_limit_reset_at,omitempty"`
// OverloadUntil holds the value of the "overload_until" field. // OverloadUntil holds the value of the "overload_until" field.
OverloadUntil *time.Time `json:"overload_until,omitempty"` 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 holds the value of the "session_window_start" field.
SessionWindowStart *time.Time `json:"session_window_start,omitempty"` SessionWindowStart *time.Time `json:"session_window_start,omitempty"`
// SessionWindowEnd holds the value of the "session_window_end" field. // SessionWindowEnd holds the value of the "session_window_end" field.
@@ -135,11 +143,13 @@ func (*Account) scanValues(columns []string) ([]any, error) {
values[i] = new([]byte) values[i] = new([]byte)
case account.FieldAutoPauseOnExpired, account.FieldSchedulable: case account.FieldAutoPauseOnExpired, account.FieldSchedulable:
values[i] = new(sql.NullBool) values[i] = new(sql.NullBool)
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority: case account.FieldRateMultiplier:
values[i] = new(sql.NullFloat64)
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
values[i] = new(sql.NullInt64) 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) 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) values[i] = new(sql.NullTime)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
@@ -235,12 +245,25 @@ func (_m *Account) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
_m.Concurrency = int(value.Int64) _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: case account.FieldPriority:
if value, ok := values[i].(*sql.NullInt64); !ok { if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field priority", values[i]) return fmt.Errorf("unexpected type %T for field priority", values[i])
} else if value.Valid { } else if value.Valid {
_m.Priority = int(value.Int64) _m.Priority = int(value.Int64)
} }
case account.FieldRateMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_multiplier", values[i])
} else if value.Valid {
_m.RateMultiplier = value.Float64
}
case account.FieldStatus: case account.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok { if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i]) return fmt.Errorf("unexpected type %T for field status", values[i])
@@ -301,6 +324,20 @@ func (_m *Account) assignValues(columns []string, values []any) error {
_m.OverloadUntil = new(time.Time) _m.OverloadUntil = new(time.Time)
*_m.OverloadUntil = value.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: case account.FieldSessionWindowStart:
if value, ok := values[i].(*sql.NullTime); !ok { if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field session_window_start", values[i]) return fmt.Errorf("unexpected type %T for field session_window_start", values[i])
@@ -417,9 +454,17 @@ func (_m *Account) String() string {
builder.WriteString("concurrency=") builder.WriteString("concurrency=")
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency)) builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
builder.WriteString(", ") 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("priority=")
builder.WriteString(fmt.Sprintf("%v", _m.Priority)) builder.WriteString(fmt.Sprintf("%v", _m.Priority))
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
builder.WriteString(", ")
builder.WriteString("status=") builder.WriteString("status=")
builder.WriteString(_m.Status) builder.WriteString(_m.Status)
builder.WriteString(", ") builder.WriteString(", ")
@@ -459,6 +504,16 @@ func (_m *Account) String() string {
builder.WriteString(v.Format(time.ANSIC)) builder.WriteString(v.Format(time.ANSIC))
} }
builder.WriteString(", ") 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 { if v := _m.SessionWindowStart; v != nil {
builder.WriteString("session_window_start=") builder.WriteString("session_window_start=")
builder.WriteString(v.Format(time.ANSIC)) builder.WriteString(v.Format(time.ANSIC))

View File

@@ -37,8 +37,12 @@ const (
FieldProxyID = "proxy_id" FieldProxyID = "proxy_id"
// FieldConcurrency holds the string denoting the concurrency field in the database. // FieldConcurrency holds the string denoting the concurrency field in the database.
FieldConcurrency = "concurrency" 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 holds the string denoting the priority field in the database.
FieldPriority = "priority" FieldPriority = "priority"
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
FieldRateMultiplier = "rate_multiplier"
// FieldStatus holds the string denoting the status field in the database. // FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status" FieldStatus = "status"
// FieldErrorMessage holds the string denoting the error_message field in the database. // FieldErrorMessage holds the string denoting the error_message field in the database.
@@ -57,6 +61,10 @@ const (
FieldRateLimitResetAt = "rate_limit_reset_at" FieldRateLimitResetAt = "rate_limit_reset_at"
// FieldOverloadUntil holds the string denoting the overload_until field in the database. // FieldOverloadUntil holds the string denoting the overload_until field in the database.
FieldOverloadUntil = "overload_until" 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 holds the string denoting the session_window_start field in the database.
FieldSessionWindowStart = "session_window_start" FieldSessionWindowStart = "session_window_start"
// FieldSessionWindowEnd holds the string denoting the session_window_end field in the database. // FieldSessionWindowEnd holds the string denoting the session_window_end field in the database.
@@ -115,7 +123,9 @@ var Columns = []string{
FieldExtra, FieldExtra,
FieldProxyID, FieldProxyID,
FieldConcurrency, FieldConcurrency,
FieldLoadFactor,
FieldPriority, FieldPriority,
FieldRateMultiplier,
FieldStatus, FieldStatus,
FieldErrorMessage, FieldErrorMessage,
FieldLastUsedAt, FieldLastUsedAt,
@@ -125,6 +135,8 @@ var Columns = []string{
FieldRateLimitedAt, FieldRateLimitedAt,
FieldRateLimitResetAt, FieldRateLimitResetAt,
FieldOverloadUntil, FieldOverloadUntil,
FieldTempUnschedulableUntil,
FieldTempUnschedulableReason,
FieldSessionWindowStart, FieldSessionWindowStart,
FieldSessionWindowEnd, FieldSessionWindowEnd,
FieldSessionWindowStatus, FieldSessionWindowStatus,
@@ -174,6 +186,8 @@ var (
DefaultConcurrency int DefaultConcurrency int
// DefaultPriority holds the default value on creation for the "priority" field. // DefaultPriority holds the default value on creation for the "priority" field.
DefaultPriority int DefaultPriority int
// DefaultRateMultiplier holds the default value on creation for the "rate_multiplier" field.
DefaultRateMultiplier float64
// DefaultStatus holds the default value on creation for the "status" field. // DefaultStatus holds the default value on creation for the "status" field.
DefaultStatus string DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save. // StatusValidator is a validator for the "status" field. It is called by the builders before save.
@@ -239,11 +253,21 @@ func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConcurrency, opts...).ToFunc() 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. // ByPriority orders the results by the priority field.
func ByPriority(opts ...sql.OrderTermOption) OrderOption { func ByPriority(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPriority, opts...).ToFunc() return sql.OrderByField(FieldPriority, opts...).ToFunc()
} }
// ByRateMultiplier orders the results by the rate_multiplier field.
func ByRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
}
// ByStatus orders the results by the status field. // ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption { func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc() return sql.OrderByField(FieldStatus, opts...).ToFunc()
@@ -289,6 +313,16 @@ func ByOverloadUntil(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOverloadUntil, opts...).ToFunc() 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. // BySessionWindowStart orders the results by the session_window_start field.
func BySessionWindowStart(opts ...sql.OrderTermOption) OrderOption { func BySessionWindowStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSessionWindowStart, opts...).ToFunc() return sql.OrderByField(FieldSessionWindowStart, opts...).ToFunc()

View File

@@ -100,11 +100,21 @@ func Concurrency(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldConcurrency, v)) 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. // Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
func Priority(v int) predicate.Account { func Priority(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldPriority, v)) return predicate.Account(sql.FieldEQ(FieldPriority, v))
} }
// RateMultiplier applies equality check predicate on the "rate_multiplier" field. It's identical to RateMultiplierEQ.
func RateMultiplier(v float64) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldRateMultiplier, v))
}
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ. // Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
func Status(v string) predicate.Account { func Status(v string) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldStatus, v)) return predicate.Account(sql.FieldEQ(FieldStatus, v))
@@ -150,6 +160,16 @@ func OverloadUntil(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldOverloadUntil, v)) 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. // SessionWindowStart applies equality check predicate on the "session_window_start" field. It's identical to SessionWindowStartEQ.
func SessionWindowStart(v time.Time) predicate.Account { func SessionWindowStart(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v)) return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))
@@ -635,6 +655,56 @@ func ConcurrencyLTE(v int) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldConcurrency, v)) 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. // PriorityEQ applies the EQ predicate on the "priority" field.
func PriorityEQ(v int) predicate.Account { func PriorityEQ(v int) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldPriority, v)) return predicate.Account(sql.FieldEQ(FieldPriority, v))
@@ -675,6 +745,46 @@ func PriorityLTE(v int) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldPriority, v)) return predicate.Account(sql.FieldLTE(FieldPriority, v))
} }
// RateMultiplierEQ applies the EQ predicate on the "rate_multiplier" field.
func RateMultiplierEQ(v float64) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldRateMultiplier, v))
}
// RateMultiplierNEQ applies the NEQ predicate on the "rate_multiplier" field.
func RateMultiplierNEQ(v float64) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldRateMultiplier, v))
}
// RateMultiplierIn applies the In predicate on the "rate_multiplier" field.
func RateMultiplierIn(vs ...float64) predicate.Account {
return predicate.Account(sql.FieldIn(FieldRateMultiplier, vs...))
}
// RateMultiplierNotIn applies the NotIn predicate on the "rate_multiplier" field.
func RateMultiplierNotIn(vs ...float64) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldRateMultiplier, vs...))
}
// RateMultiplierGT applies the GT predicate on the "rate_multiplier" field.
func RateMultiplierGT(v float64) predicate.Account {
return predicate.Account(sql.FieldGT(FieldRateMultiplier, v))
}
// RateMultiplierGTE applies the GTE predicate on the "rate_multiplier" field.
func RateMultiplierGTE(v float64) predicate.Account {
return predicate.Account(sql.FieldGTE(FieldRateMultiplier, v))
}
// RateMultiplierLT applies the LT predicate on the "rate_multiplier" field.
func RateMultiplierLT(v float64) predicate.Account {
return predicate.Account(sql.FieldLT(FieldRateMultiplier, v))
}
// RateMultiplierLTE applies the LTE predicate on the "rate_multiplier" field.
func RateMultiplierLTE(v float64) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldRateMultiplier, v))
}
// StatusEQ applies the EQ predicate on the "status" field. // StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v string) predicate.Account { func StatusEQ(v string) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldStatus, v)) return predicate.Account(sql.FieldEQ(FieldStatus, v))
@@ -1085,6 +1195,131 @@ func OverloadUntilNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldOverloadUntil)) 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. // SessionWindowStartEQ applies the EQ predicate on the "session_window_start" field.
func SessionWindowStartEQ(v time.Time) predicate.Account { func SessionWindowStartEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v)) return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))

View File

@@ -139,6 +139,20 @@ func (_c *AccountCreate) SetNillableConcurrency(v *int) *AccountCreate {
return _c 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. // SetPriority sets the "priority" field.
func (_c *AccountCreate) SetPriority(v int) *AccountCreate { func (_c *AccountCreate) SetPriority(v int) *AccountCreate {
_c.mutation.SetPriority(v) _c.mutation.SetPriority(v)
@@ -153,6 +167,20 @@ func (_c *AccountCreate) SetNillablePriority(v *int) *AccountCreate {
return _c return _c
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (_c *AccountCreate) SetRateMultiplier(v float64) *AccountCreate {
_c.mutation.SetRateMultiplier(v)
return _c
}
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
func (_c *AccountCreate) SetNillableRateMultiplier(v *float64) *AccountCreate {
if v != nil {
_c.SetRateMultiplier(*v)
}
return _c
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (_c *AccountCreate) SetStatus(v string) *AccountCreate { func (_c *AccountCreate) SetStatus(v string) *AccountCreate {
_c.mutation.SetStatus(v) _c.mutation.SetStatus(v)
@@ -279,6 +307,34 @@ func (_c *AccountCreate) SetNillableOverloadUntil(v *time.Time) *AccountCreate {
return _c 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. // SetSessionWindowStart sets the "session_window_start" field.
func (_c *AccountCreate) SetSessionWindowStart(v time.Time) *AccountCreate { func (_c *AccountCreate) SetSessionWindowStart(v time.Time) *AccountCreate {
_c.mutation.SetSessionWindowStart(v) _c.mutation.SetSessionWindowStart(v)
@@ -429,6 +485,10 @@ func (_c *AccountCreate) defaults() error {
v := account.DefaultPriority v := account.DefaultPriority
_c.mutation.SetPriority(v) _c.mutation.SetPriority(v)
} }
if _, ok := _c.mutation.RateMultiplier(); !ok {
v := account.DefaultRateMultiplier
_c.mutation.SetRateMultiplier(v)
}
if _, ok := _c.mutation.Status(); !ok { if _, ok := _c.mutation.Status(); !ok {
v := account.DefaultStatus v := account.DefaultStatus
_c.mutation.SetStatus(v) _c.mutation.SetStatus(v)
@@ -488,6 +548,9 @@ func (_c *AccountCreate) check() error {
if _, ok := _c.mutation.Priority(); !ok { if _, ok := _c.mutation.Priority(); !ok {
return &ValidationError{Name: "priority", err: errors.New(`ent: missing required field "Account.priority"`)} return &ValidationError{Name: "priority", err: errors.New(`ent: missing required field "Account.priority"`)}
} }
if _, ok := _c.mutation.RateMultiplier(); !ok {
return &ValidationError{Name: "rate_multiplier", err: errors.New(`ent: missing required field "Account.rate_multiplier"`)}
}
if _, ok := _c.mutation.Status(); !ok { if _, ok := _c.mutation.Status(); !ok {
return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Account.status"`)} return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Account.status"`)}
} }
@@ -574,10 +637,18 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldConcurrency, field.TypeInt, value) _spec.SetField(account.FieldConcurrency, field.TypeInt, value)
_node.Concurrency = 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 { if value, ok := _c.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value) _spec.SetField(account.FieldPriority, field.TypeInt, value)
_node.Priority = value _node.Priority = value
} }
if value, ok := _c.mutation.RateMultiplier(); ok {
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
_node.RateMultiplier = value
}
if value, ok := _c.mutation.Status(); ok { if value, ok := _c.mutation.Status(); ok {
_spec.SetField(account.FieldStatus, field.TypeString, value) _spec.SetField(account.FieldStatus, field.TypeString, value)
_node.Status = value _node.Status = value
@@ -614,6 +685,14 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldOverloadUntil, field.TypeTime, value) _spec.SetField(account.FieldOverloadUntil, field.TypeTime, value)
_node.OverloadUntil = &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 { if value, ok := _c.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value) _spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
_node.SessionWindowStart = &value _node.SessionWindowStart = &value
@@ -875,6 +954,30 @@ func (u *AccountUpsert) AddConcurrency(v int) *AccountUpsert {
return u 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. // SetPriority sets the "priority" field.
func (u *AccountUpsert) SetPriority(v int) *AccountUpsert { func (u *AccountUpsert) SetPriority(v int) *AccountUpsert {
u.Set(account.FieldPriority, v) u.Set(account.FieldPriority, v)
@@ -893,6 +996,24 @@ func (u *AccountUpsert) AddPriority(v int) *AccountUpsert {
return u return u
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (u *AccountUpsert) SetRateMultiplier(v float64) *AccountUpsert {
u.Set(account.FieldRateMultiplier, v)
return u
}
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
func (u *AccountUpsert) UpdateRateMultiplier() *AccountUpsert {
u.SetExcluded(account.FieldRateMultiplier)
return u
}
// AddRateMultiplier adds v to the "rate_multiplier" field.
func (u *AccountUpsert) AddRateMultiplier(v float64) *AccountUpsert {
u.Add(account.FieldRateMultiplier, v)
return u
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (u *AccountUpsert) SetStatus(v string) *AccountUpsert { func (u *AccountUpsert) SetStatus(v string) *AccountUpsert {
u.Set(account.FieldStatus, v) u.Set(account.FieldStatus, v)
@@ -1037,6 +1158,42 @@ func (u *AccountUpsert) ClearOverloadUntil() *AccountUpsert {
return u 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. // SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsert) SetSessionWindowStart(v time.Time) *AccountUpsert { func (u *AccountUpsert) SetSessionWindowStart(v time.Time) *AccountUpsert {
u.Set(account.FieldSessionWindowStart, v) u.Set(account.FieldSessionWindowStart, v)
@@ -1304,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. // SetPriority sets the "priority" field.
func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne { func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
@@ -1325,6 +1510,27 @@ func (u *AccountUpsertOne) UpdatePriority() *AccountUpsertOne {
}) })
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (u *AccountUpsertOne) SetRateMultiplier(v float64) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetRateMultiplier(v)
})
}
// AddRateMultiplier adds v to the "rate_multiplier" field.
func (u *AccountUpsertOne) AddRateMultiplier(v float64) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.AddRateMultiplier(v)
})
}
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateRateMultiplier() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateRateMultiplier()
})
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (u *AccountUpsertOne) SetStatus(v string) *AccountUpsertOne { func (u *AccountUpsertOne) SetStatus(v string) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
@@ -1493,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. // SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsertOne) SetSessionWindowStart(v time.Time) *AccountUpsertOne { func (u *AccountUpsertOne) SetSessionWindowStart(v time.Time) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
@@ -1935,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. // SetPriority sets the "priority" field.
func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk { func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
@@ -1956,6 +2232,27 @@ func (u *AccountUpsertBulk) UpdatePriority() *AccountUpsertBulk {
}) })
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (u *AccountUpsertBulk) SetRateMultiplier(v float64) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetRateMultiplier(v)
})
}
// AddRateMultiplier adds v to the "rate_multiplier" field.
func (u *AccountUpsertBulk) AddRateMultiplier(v float64) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.AddRateMultiplier(v)
})
}
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateRateMultiplier() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateRateMultiplier()
})
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (u *AccountUpsertBulk) SetStatus(v string) *AccountUpsertBulk { func (u *AccountUpsertBulk) SetStatus(v string) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
@@ -2124,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. // SetSessionWindowStart sets the "session_window_start" field.
func (u *AccountUpsertBulk) SetSessionWindowStart(v time.Time) *AccountUpsertBulk { func (u *AccountUpsertBulk) SetSessionWindowStart(v time.Time) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {

View File

@@ -172,6 +172,33 @@ func (_u *AccountUpdate) AddConcurrency(v int) *AccountUpdate {
return _u 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. // SetPriority sets the "priority" field.
func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate { func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate {
_u.mutation.ResetPriority() _u.mutation.ResetPriority()
@@ -193,6 +220,27 @@ func (_u *AccountUpdate) AddPriority(v int) *AccountUpdate {
return _u return _u
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (_u *AccountUpdate) SetRateMultiplier(v float64) *AccountUpdate {
_u.mutation.ResetRateMultiplier()
_u.mutation.SetRateMultiplier(v)
return _u
}
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableRateMultiplier(v *float64) *AccountUpdate {
if v != nil {
_u.SetRateMultiplier(*v)
}
return _u
}
// AddRateMultiplier adds value to the "rate_multiplier" field.
func (_u *AccountUpdate) AddRateMultiplier(v float64) *AccountUpdate {
_u.mutation.AddRateMultiplier(v)
return _u
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (_u *AccountUpdate) SetStatus(v string) *AccountUpdate { func (_u *AccountUpdate) SetStatus(v string) *AccountUpdate {
_u.mutation.SetStatus(v) _u.mutation.SetStatus(v)
@@ -355,6 +403,46 @@ func (_u *AccountUpdate) ClearOverloadUntil() *AccountUpdate {
return _u 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. // SetSessionWindowStart sets the "session_window_start" field.
func (_u *AccountUpdate) SetSessionWindowStart(v time.Time) *AccountUpdate { func (_u *AccountUpdate) SetSessionWindowStart(v time.Time) *AccountUpdate {
_u.mutation.SetSessionWindowStart(v) _u.mutation.SetSessionWindowStart(v)
@@ -623,12 +711,27 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.AddedConcurrency(); ok { if value, ok := _u.mutation.AddedConcurrency(); ok {
_spec.AddField(account.FieldConcurrency, field.TypeInt, value) _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 { if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value) _spec.SetField(account.FieldPriority, field.TypeInt, value)
} }
if value, ok := _u.mutation.AddedPriority(); ok { if value, ok := _u.mutation.AddedPriority(); ok {
_spec.AddField(account.FieldPriority, field.TypeInt, value) _spec.AddField(account.FieldPriority, field.TypeInt, value)
} }
if value, ok := _u.mutation.RateMultiplier(); ok {
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(account.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Status(); ok { if value, ok := _u.mutation.Status(); ok {
_spec.SetField(account.FieldStatus, field.TypeString, value) _spec.SetField(account.FieldStatus, field.TypeString, value)
} }
@@ -674,6 +777,18 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.OverloadUntilCleared() { if _u.mutation.OverloadUntilCleared() {
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime) _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 { if value, ok := _u.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value) _spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
} }
@@ -984,6 +1099,33 @@ func (_u *AccountUpdateOne) AddConcurrency(v int) *AccountUpdateOne {
return _u 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. // SetPriority sets the "priority" field.
func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne { func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne {
_u.mutation.ResetPriority() _u.mutation.ResetPriority()
@@ -1005,6 +1147,27 @@ func (_u *AccountUpdateOne) AddPriority(v int) *AccountUpdateOne {
return _u return _u
} }
// SetRateMultiplier sets the "rate_multiplier" field.
func (_u *AccountUpdateOne) SetRateMultiplier(v float64) *AccountUpdateOne {
_u.mutation.ResetRateMultiplier()
_u.mutation.SetRateMultiplier(v)
return _u
}
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableRateMultiplier(v *float64) *AccountUpdateOne {
if v != nil {
_u.SetRateMultiplier(*v)
}
return _u
}
// AddRateMultiplier adds value to the "rate_multiplier" field.
func (_u *AccountUpdateOne) AddRateMultiplier(v float64) *AccountUpdateOne {
_u.mutation.AddRateMultiplier(v)
return _u
}
// SetStatus sets the "status" field. // SetStatus sets the "status" field.
func (_u *AccountUpdateOne) SetStatus(v string) *AccountUpdateOne { func (_u *AccountUpdateOne) SetStatus(v string) *AccountUpdateOne {
_u.mutation.SetStatus(v) _u.mutation.SetStatus(v)
@@ -1167,6 +1330,46 @@ func (_u *AccountUpdateOne) ClearOverloadUntil() *AccountUpdateOne {
return _u 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. // SetSessionWindowStart sets the "session_window_start" field.
func (_u *AccountUpdateOne) SetSessionWindowStart(v time.Time) *AccountUpdateOne { func (_u *AccountUpdateOne) SetSessionWindowStart(v time.Time) *AccountUpdateOne {
_u.mutation.SetSessionWindowStart(v) _u.mutation.SetSessionWindowStart(v)
@@ -1465,12 +1668,27 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if value, ok := _u.mutation.AddedConcurrency(); ok { if value, ok := _u.mutation.AddedConcurrency(); ok {
_spec.AddField(account.FieldConcurrency, field.TypeInt, value) _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 { if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(account.FieldPriority, field.TypeInt, value) _spec.SetField(account.FieldPriority, field.TypeInt, value)
} }
if value, ok := _u.mutation.AddedPriority(); ok { if value, ok := _u.mutation.AddedPriority(); ok {
_spec.AddField(account.FieldPriority, field.TypeInt, value) _spec.AddField(account.FieldPriority, field.TypeInt, value)
} }
if value, ok := _u.mutation.RateMultiplier(); ok {
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
_spec.AddField(account.FieldRateMultiplier, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Status(); ok { if value, ok := _u.mutation.Status(); ok {
_spec.SetField(account.FieldStatus, field.TypeString, value) _spec.SetField(account.FieldStatus, field.TypeString, value)
} }
@@ -1516,6 +1734,18 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if _u.mutation.OverloadUntilCleared() { if _u.mutation.OverloadUntilCleared() {
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime) _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 { if value, ok := _u.mutation.SessionWindowStart(); ok {
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value) _spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
} }

260
backend/ent/announcement.go Normal file
View File

@@ -0,0 +1,260 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/announcement"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// Announcement is the model entity for the Announcement schema.
type Announcement struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// 公告标题
Title string `json:"title,omitempty"`
// 公告内容(支持 Markdown
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"`
// 开始展示时间(为空表示立即生效)
StartsAt *time.Time `json:"starts_at,omitempty"`
// 结束展示时间(为空表示永久生效)
EndsAt *time.Time `json:"ends_at,omitempty"`
// 创建人用户ID管理员
CreatedBy *int64 `json:"created_by,omitempty"`
// 更新人用户ID管理员
UpdatedBy *int64 `json:"updated_by,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 AnnouncementQuery when eager-loading is set.
Edges AnnouncementEdges `json:"edges"`
selectValues sql.SelectValues
}
// AnnouncementEdges holds the relations/edges for other nodes in the graph.
type AnnouncementEdges struct {
// Reads holds the value of the reads edge.
Reads []*AnnouncementRead `json:"reads,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// ReadsOrErr returns the Reads value or an error if the edge
// was not loaded in eager-loading.
func (e AnnouncementEdges) ReadsOrErr() ([]*AnnouncementRead, error) {
if e.loadedTypes[0] {
return e.Reads, nil
}
return nil, &NotLoadedError{edge: "reads"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Announcement) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case announcement.FieldTargeting:
values[i] = new([]byte)
case announcement.FieldID, announcement.FieldCreatedBy, announcement.FieldUpdatedBy:
values[i] = new(sql.NullInt64)
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)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Announcement fields.
func (_m *Announcement) 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 announcement.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 announcement.FieldTitle:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field title", values[i])
} else if value.Valid {
_m.Title = value.String
}
case announcement.FieldContent:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field content", values[i])
} else if value.Valid {
_m.Content = value.String
}
case announcement.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 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])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Targeting); err != nil {
return fmt.Errorf("unmarshal field targeting: %w", err)
}
}
case announcement.FieldStartsAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field starts_at", values[i])
} else if value.Valid {
_m.StartsAt = new(time.Time)
*_m.StartsAt = value.Time
}
case announcement.FieldEndsAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field ends_at", values[i])
} else if value.Valid {
_m.EndsAt = new(time.Time)
*_m.EndsAt = value.Time
}
case announcement.FieldCreatedBy:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field created_by", values[i])
} else if value.Valid {
_m.CreatedBy = new(int64)
*_m.CreatedBy = value.Int64
}
case announcement.FieldUpdatedBy:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field updated_by", values[i])
} else if value.Valid {
_m.UpdatedBy = new(int64)
*_m.UpdatedBy = value.Int64
}
case announcement.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 announcement.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 Announcement.
// This includes values selected through modifiers, order, etc.
func (_m *Announcement) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryReads queries the "reads" edge of the Announcement entity.
func (_m *Announcement) QueryReads() *AnnouncementReadQuery {
return NewAnnouncementClient(_m.config).QueryReads(_m)
}
// Update returns a builder for updating this Announcement.
// Note that you need to call Announcement.Unwrap() before calling this method if this Announcement
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Announcement) Update() *AnnouncementUpdateOne {
return NewAnnouncementClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the Announcement 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 *Announcement) Unwrap() *Announcement {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: Announcement is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *Announcement) String() string {
var builder strings.Builder
builder.WriteString("Announcement(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("title=")
builder.WriteString(_m.Title)
builder.WriteString(", ")
builder.WriteString("content=")
builder.WriteString(_m.Content)
builder.WriteString(", ")
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(", ")
if v := _m.StartsAt; v != nil {
builder.WriteString("starts_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.EndsAt; v != nil {
builder.WriteString("ends_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.CreatedBy; v != nil {
builder.WriteString("created_by=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.UpdatedBy; v != nil {
builder.WriteString("updated_by=")
builder.WriteString(fmt.Sprintf("%v", *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()
}
// Announcements is a parsable slice of Announcement.
type Announcements []*Announcement

View File

@@ -0,0 +1,176 @@
// Code generated by ent, DO NOT EDIT.
package announcement
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the announcement type in the database.
Label = "announcement"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldTitle holds the string denoting the title field in the database.
FieldTitle = "title"
// FieldContent holds the string denoting the content field in the database.
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.
FieldStartsAt = "starts_at"
// FieldEndsAt holds the string denoting the ends_at field in the database.
FieldEndsAt = "ends_at"
// FieldCreatedBy holds the string denoting the created_by field in the database.
FieldCreatedBy = "created_by"
// FieldUpdatedBy holds the string denoting the updated_by field in the database.
FieldUpdatedBy = "updated_by"
// 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"
// EdgeReads holds the string denoting the reads edge name in mutations.
EdgeReads = "reads"
// Table holds the table name of the announcement in the database.
Table = "announcements"
// ReadsTable is the table that holds the reads relation/edge.
ReadsTable = "announcement_reads"
// ReadsInverseTable is the table name for the AnnouncementRead entity.
// It exists in this package in order to avoid circular dependency with the "announcementread" package.
ReadsInverseTable = "announcement_reads"
// ReadsColumn is the table column denoting the reads relation/edge.
ReadsColumn = "announcement_id"
)
// Columns holds all SQL columns for announcement fields.
var Columns = []string{
FieldID,
FieldTitle,
FieldContent,
FieldStatus,
FieldNotifyMode,
FieldTargeting,
FieldStartsAt,
FieldEndsAt,
FieldCreatedBy,
FieldUpdatedBy,
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 (
// TitleValidator is a validator for the "title" field. It is called by the builders before save.
TitleValidator func(string) error
// ContentValidator is a validator for the "content" field. It is called by the builders before save.
ContentValidator 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
// 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.
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 Announcement 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()
}
// ByTitle orders the results by the title field.
func ByTitle(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTitle, opts...).ToFunc()
}
// ByContent orders the results by the content field.
func ByContent(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldContent, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
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()
}
// ByEndsAt orders the results by the ends_at field.
func ByEndsAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEndsAt, opts...).ToFunc()
}
// ByCreatedBy orders the results by the created_by field.
func ByCreatedBy(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedBy, opts...).ToFunc()
}
// ByUpdatedBy orders the results by the updated_by field.
func ByUpdatedBy(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedBy, 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()
}
// ByReadsCount orders the results by reads count.
func ByReadsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newReadsStep(), opts...)
}
}
// ByReads orders the results by reads terms.
func ByReads(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newReadsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newReadsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ReadsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ReadsTable, ReadsColumn),
)
}

View File

@@ -0,0 +1,694 @@
// Code generated by ent, DO NOT EDIT.
package announcement
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldID, id))
}
// Title applies equality check predicate on the "title" field. It's identical to TitleEQ.
func Title(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldTitle, v))
}
// Content applies equality check predicate on the "content" field. It's identical to ContentEQ.
func Content(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldContent, v))
}
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
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))
}
// EndsAt applies equality check predicate on the "ends_at" field. It's identical to EndsAtEQ.
func EndsAt(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldEndsAt, v))
}
// CreatedBy applies equality check predicate on the "created_by" field. It's identical to CreatedByEQ.
func CreatedBy(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldCreatedBy, v))
}
// UpdatedBy applies equality check predicate on the "updated_by" field. It's identical to UpdatedByEQ.
func UpdatedBy(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldUpdatedBy, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.Announcement {
return predicate.Announcement(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.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldUpdatedAt, v))
}
// TitleEQ applies the EQ predicate on the "title" field.
func TitleEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldTitle, v))
}
// TitleNEQ applies the NEQ predicate on the "title" field.
func TitleNEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldTitle, v))
}
// TitleIn applies the In predicate on the "title" field.
func TitleIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldTitle, vs...))
}
// TitleNotIn applies the NotIn predicate on the "title" field.
func TitleNotIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldTitle, vs...))
}
// TitleGT applies the GT predicate on the "title" field.
func TitleGT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldTitle, v))
}
// TitleGTE applies the GTE predicate on the "title" field.
func TitleGTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldTitle, v))
}
// TitleLT applies the LT predicate on the "title" field.
func TitleLT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldTitle, v))
}
// TitleLTE applies the LTE predicate on the "title" field.
func TitleLTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldTitle, v))
}
// TitleContains applies the Contains predicate on the "title" field.
func TitleContains(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContains(FieldTitle, v))
}
// TitleHasPrefix applies the HasPrefix predicate on the "title" field.
func TitleHasPrefix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasPrefix(FieldTitle, v))
}
// TitleHasSuffix applies the HasSuffix predicate on the "title" field.
func TitleHasSuffix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasSuffix(FieldTitle, v))
}
// TitleEqualFold applies the EqualFold predicate on the "title" field.
func TitleEqualFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEqualFold(FieldTitle, v))
}
// TitleContainsFold applies the ContainsFold predicate on the "title" field.
func TitleContainsFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContainsFold(FieldTitle, v))
}
// ContentEQ applies the EQ predicate on the "content" field.
func ContentEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldContent, v))
}
// ContentNEQ applies the NEQ predicate on the "content" field.
func ContentNEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldContent, v))
}
// ContentIn applies the In predicate on the "content" field.
func ContentIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldContent, vs...))
}
// ContentNotIn applies the NotIn predicate on the "content" field.
func ContentNotIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldContent, vs...))
}
// ContentGT applies the GT predicate on the "content" field.
func ContentGT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldContent, v))
}
// ContentGTE applies the GTE predicate on the "content" field.
func ContentGTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldContent, v))
}
// ContentLT applies the LT predicate on the "content" field.
func ContentLT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldContent, v))
}
// ContentLTE applies the LTE predicate on the "content" field.
func ContentLTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldContent, v))
}
// ContentContains applies the Contains predicate on the "content" field.
func ContentContains(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContains(FieldContent, v))
}
// ContentHasPrefix applies the HasPrefix predicate on the "content" field.
func ContentHasPrefix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasPrefix(FieldContent, v))
}
// ContentHasSuffix applies the HasSuffix predicate on the "content" field.
func ContentHasSuffix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasSuffix(FieldContent, v))
}
// ContentEqualFold applies the EqualFold predicate on the "content" field.
func ContentEqualFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEqualFold(FieldContent, v))
}
// ContentContainsFold applies the ContainsFold predicate on the "content" field.
func ContentContainsFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContainsFold(FieldContent, v))
}
// StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
}
// StatusNEQ applies the NEQ predicate on the "status" field.
func StatusNEQ(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldStatus, v))
}
// StatusIn applies the In predicate on the "status" field.
func StatusIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldStatus, vs...))
}
// StatusNotIn applies the NotIn predicate on the "status" field.
func StatusNotIn(vs ...string) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldStatus, vs...))
}
// StatusGT applies the GT predicate on the "status" field.
func StatusGT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldStatus, v))
}
// StatusGTE applies the GTE predicate on the "status" field.
func StatusGTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldStatus, v))
}
// StatusLT applies the LT predicate on the "status" field.
func StatusLT(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldStatus, v))
}
// StatusLTE applies the LTE predicate on the "status" field.
func StatusLTE(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldStatus, v))
}
// StatusContains applies the Contains predicate on the "status" field.
func StatusContains(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldContains(FieldStatus, v))
}
// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
func StatusHasPrefix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasPrefix(FieldStatus, v))
}
// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
func StatusHasSuffix(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldHasSuffix(FieldStatus, v))
}
// StatusEqualFold applies the EqualFold predicate on the "status" field.
func StatusEqualFold(v string) predicate.Announcement {
return predicate.Announcement(sql.FieldEqualFold(FieldStatus, v))
}
// StatusContainsFold applies the ContainsFold predicate on the "status" field.
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))
}
// TargetingNotNil applies the NotNil predicate on the "targeting" field.
func TargetingNotNil() predicate.Announcement {
return predicate.Announcement(sql.FieldNotNull(FieldTargeting))
}
// StartsAtEQ applies the EQ predicate on the "starts_at" field.
func StartsAtEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
}
// StartsAtNEQ applies the NEQ predicate on the "starts_at" field.
func StartsAtNEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldStartsAt, v))
}
// StartsAtIn applies the In predicate on the "starts_at" field.
func StartsAtIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldStartsAt, vs...))
}
// StartsAtNotIn applies the NotIn predicate on the "starts_at" field.
func StartsAtNotIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldStartsAt, vs...))
}
// StartsAtGT applies the GT predicate on the "starts_at" field.
func StartsAtGT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldStartsAt, v))
}
// StartsAtGTE applies the GTE predicate on the "starts_at" field.
func StartsAtGTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldStartsAt, v))
}
// StartsAtLT applies the LT predicate on the "starts_at" field.
func StartsAtLT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldStartsAt, v))
}
// StartsAtLTE applies the LTE predicate on the "starts_at" field.
func StartsAtLTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldStartsAt, v))
}
// StartsAtIsNil applies the IsNil predicate on the "starts_at" field.
func StartsAtIsNil() predicate.Announcement {
return predicate.Announcement(sql.FieldIsNull(FieldStartsAt))
}
// StartsAtNotNil applies the NotNil predicate on the "starts_at" field.
func StartsAtNotNil() predicate.Announcement {
return predicate.Announcement(sql.FieldNotNull(FieldStartsAt))
}
// EndsAtEQ applies the EQ predicate on the "ends_at" field.
func EndsAtEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldEndsAt, v))
}
// EndsAtNEQ applies the NEQ predicate on the "ends_at" field.
func EndsAtNEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldEndsAt, v))
}
// EndsAtIn applies the In predicate on the "ends_at" field.
func EndsAtIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldEndsAt, vs...))
}
// EndsAtNotIn applies the NotIn predicate on the "ends_at" field.
func EndsAtNotIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldEndsAt, vs...))
}
// EndsAtGT applies the GT predicate on the "ends_at" field.
func EndsAtGT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldEndsAt, v))
}
// EndsAtGTE applies the GTE predicate on the "ends_at" field.
func EndsAtGTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldEndsAt, v))
}
// EndsAtLT applies the LT predicate on the "ends_at" field.
func EndsAtLT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldEndsAt, v))
}
// EndsAtLTE applies the LTE predicate on the "ends_at" field.
func EndsAtLTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldEndsAt, v))
}
// EndsAtIsNil applies the IsNil predicate on the "ends_at" field.
func EndsAtIsNil() predicate.Announcement {
return predicate.Announcement(sql.FieldIsNull(FieldEndsAt))
}
// EndsAtNotNil applies the NotNil predicate on the "ends_at" field.
func EndsAtNotNil() predicate.Announcement {
return predicate.Announcement(sql.FieldNotNull(FieldEndsAt))
}
// CreatedByEQ applies the EQ predicate on the "created_by" field.
func CreatedByEQ(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldCreatedBy, v))
}
// CreatedByNEQ applies the NEQ predicate on the "created_by" field.
func CreatedByNEQ(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldCreatedBy, v))
}
// CreatedByIn applies the In predicate on the "created_by" field.
func CreatedByIn(vs ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldCreatedBy, vs...))
}
// CreatedByNotIn applies the NotIn predicate on the "created_by" field.
func CreatedByNotIn(vs ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldCreatedBy, vs...))
}
// CreatedByGT applies the GT predicate on the "created_by" field.
func CreatedByGT(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldCreatedBy, v))
}
// CreatedByGTE applies the GTE predicate on the "created_by" field.
func CreatedByGTE(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldCreatedBy, v))
}
// CreatedByLT applies the LT predicate on the "created_by" field.
func CreatedByLT(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldCreatedBy, v))
}
// CreatedByLTE applies the LTE predicate on the "created_by" field.
func CreatedByLTE(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldCreatedBy, v))
}
// CreatedByIsNil applies the IsNil predicate on the "created_by" field.
func CreatedByIsNil() predicate.Announcement {
return predicate.Announcement(sql.FieldIsNull(FieldCreatedBy))
}
// CreatedByNotNil applies the NotNil predicate on the "created_by" field.
func CreatedByNotNil() predicate.Announcement {
return predicate.Announcement(sql.FieldNotNull(FieldCreatedBy))
}
// UpdatedByEQ applies the EQ predicate on the "updated_by" field.
func UpdatedByEQ(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldUpdatedBy, v))
}
// UpdatedByNEQ applies the NEQ predicate on the "updated_by" field.
func UpdatedByNEQ(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldUpdatedBy, v))
}
// UpdatedByIn applies the In predicate on the "updated_by" field.
func UpdatedByIn(vs ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldUpdatedBy, vs...))
}
// UpdatedByNotIn applies the NotIn predicate on the "updated_by" field.
func UpdatedByNotIn(vs ...int64) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldUpdatedBy, vs...))
}
// UpdatedByGT applies the GT predicate on the "updated_by" field.
func UpdatedByGT(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldUpdatedBy, v))
}
// UpdatedByGTE applies the GTE predicate on the "updated_by" field.
func UpdatedByGTE(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldUpdatedBy, v))
}
// UpdatedByLT applies the LT predicate on the "updated_by" field.
func UpdatedByLT(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldUpdatedBy, v))
}
// UpdatedByLTE applies the LTE predicate on the "updated_by" field.
func UpdatedByLTE(v int64) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldUpdatedBy, v))
}
// UpdatedByIsNil applies the IsNil predicate on the "updated_by" field.
func UpdatedByIsNil() predicate.Announcement {
return predicate.Announcement(sql.FieldIsNull(FieldUpdatedBy))
}
// UpdatedByNotNil applies the NotNil predicate on the "updated_by" field.
func UpdatedByNotNil() predicate.Announcement {
return predicate.Announcement(sql.FieldNotNull(FieldUpdatedBy))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.Announcement {
return predicate.Announcement(sql.FieldLTE(FieldUpdatedAt, v))
}
// HasReads applies the HasEdge predicate on the "reads" edge.
func HasReads() predicate.Announcement {
return predicate.Announcement(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ReadsTable, ReadsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasReadsWith applies the HasEdge predicate on the "reads" edge with a given conditions (other predicates).
func HasReadsWith(preds ...predicate.AnnouncementRead) predicate.Announcement {
return predicate.Announcement(func(s *sql.Selector) {
step := newReadsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Announcement) predicate.Announcement {
return predicate.Announcement(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.Announcement) predicate.Announcement {
return predicate.Announcement(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.Announcement) predicate.Announcement {
return predicate.Announcement(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/announcement"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AnnouncementDelete is the builder for deleting a Announcement entity.
type AnnouncementDelete struct {
config
hooks []Hook
mutation *AnnouncementMutation
}
// Where appends a list predicates to the AnnouncementDelete builder.
func (_d *AnnouncementDelete) Where(ps ...predicate.Announcement) *AnnouncementDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AnnouncementDelete) 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 *AnnouncementDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *AnnouncementDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(announcement.Table, sqlgraph.NewFieldSpec(announcement.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
}
// AnnouncementDeleteOne is the builder for deleting a single Announcement entity.
type AnnouncementDeleteOne struct {
_d *AnnouncementDelete
}
// Where appends a list predicates to the AnnouncementDelete builder.
func (_d *AnnouncementDeleteOne) Where(ps ...predicate.Announcement) *AnnouncementDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *AnnouncementDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{announcement.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AnnouncementDeleteOne) 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"
"database/sql/driver"
"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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AnnouncementQuery is the builder for querying Announcement entities.
type AnnouncementQuery struct {
config
ctx *QueryContext
order []announcement.OrderOption
inters []Interceptor
predicates []predicate.Announcement
withReads *AnnouncementReadQuery
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 AnnouncementQuery builder.
func (_q *AnnouncementQuery) Where(ps ...predicate.Announcement) *AnnouncementQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *AnnouncementQuery) Limit(limit int) *AnnouncementQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *AnnouncementQuery) Offset(offset int) *AnnouncementQuery {
_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 *AnnouncementQuery) Unique(unique bool) *AnnouncementQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *AnnouncementQuery) Order(o ...announcement.OrderOption) *AnnouncementQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryReads chains the current query on the "reads" edge.
func (_q *AnnouncementQuery) QueryReads() *AnnouncementReadQuery {
query := (&AnnouncementReadClient{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(announcement.Table, announcement.FieldID, selector),
sqlgraph.To(announcementread.Table, announcementread.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, announcement.ReadsTable, announcement.ReadsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Announcement entity from the query.
// Returns a *NotFoundError when no Announcement was found.
func (_q *AnnouncementQuery) First(ctx context.Context) (*Announcement, 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{announcement.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *AnnouncementQuery) FirstX(ctx context.Context) *Announcement {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first Announcement ID from the query.
// Returns a *NotFoundError when no Announcement ID was found.
func (_q *AnnouncementQuery) 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{announcement.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *AnnouncementQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single Announcement entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one Announcement entity is found.
// Returns a *NotFoundError when no Announcement entities are found.
func (_q *AnnouncementQuery) Only(ctx context.Context) (*Announcement, 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{announcement.Label}
default:
return nil, &NotSingularError{announcement.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *AnnouncementQuery) OnlyX(ctx context.Context) *Announcement {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only Announcement ID in the query.
// Returns a *NotSingularError when more than one Announcement ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *AnnouncementQuery) 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{announcement.Label}
default:
err = &NotSingularError{announcement.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *AnnouncementQuery) 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 Announcements.
func (_q *AnnouncementQuery) All(ctx context.Context) ([]*Announcement, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*Announcement, *AnnouncementQuery]()
return withInterceptors[[]*Announcement](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *AnnouncementQuery) AllX(ctx context.Context) []*Announcement {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of Announcement IDs.
func (_q *AnnouncementQuery) 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(announcement.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *AnnouncementQuery) 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 *AnnouncementQuery) 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[*AnnouncementQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *AnnouncementQuery) 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 *AnnouncementQuery) 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 *AnnouncementQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AnnouncementQuery 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 *AnnouncementQuery) Clone() *AnnouncementQuery {
if _q == nil {
return nil
}
return &AnnouncementQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]announcement.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Announcement{}, _q.predicates...),
withReads: _q.withReads.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithReads tells the query-builder to eager-load the nodes that are connected to
// the "reads" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AnnouncementQuery) WithReads(opts ...func(*AnnouncementReadQuery)) *AnnouncementQuery {
query := (&AnnouncementReadClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withReads = 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 {
// Title string `json:"title,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.Announcement.Query().
// GroupBy(announcement.FieldTitle).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *AnnouncementQuery) GroupBy(field string, fields ...string) *AnnouncementGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &AnnouncementGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = announcement.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 {
// Title string `json:"title,omitempty"`
// }
//
// client.Announcement.Query().
// Select(announcement.FieldTitle).
// Scan(ctx, &v)
func (_q *AnnouncementQuery) Select(fields ...string) *AnnouncementSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &AnnouncementSelect{AnnouncementQuery: _q}
sbuild.label = announcement.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AnnouncementSelect configured with the given aggregations.
func (_q *AnnouncementQuery) Aggregate(fns ...AggregateFunc) *AnnouncementSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *AnnouncementQuery) 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 !announcement.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 *AnnouncementQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Announcement, error) {
var (
nodes = []*Announcement{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withReads != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Announcement).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &Announcement{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.withReads; query != nil {
if err := _q.loadReads(ctx, query, nodes,
func(n *Announcement) { n.Edges.Reads = []*AnnouncementRead{} },
func(n *Announcement, e *AnnouncementRead) { n.Edges.Reads = append(n.Edges.Reads, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *AnnouncementQuery) loadReads(ctx context.Context, query *AnnouncementReadQuery, nodes []*Announcement, init func(*Announcement), assign func(*Announcement, *AnnouncementRead)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*Announcement)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(announcementread.FieldAnnouncementID)
}
query.Where(predicate.AnnouncementRead(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(announcement.ReadsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.AnnouncementID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "announcement_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *AnnouncementQuery) 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 *AnnouncementQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.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, announcement.FieldID)
for i := range fields {
if fields[i] != announcement.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 *AnnouncementQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(announcement.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = announcement.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 *AnnouncementQuery) ForUpdate(opts ...sql.LockOption) *AnnouncementQuery {
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 *AnnouncementQuery) ForShare(opts ...sql.LockOption) *AnnouncementQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// AnnouncementGroupBy is the group-by builder for Announcement entities.
type AnnouncementGroupBy struct {
selector
build *AnnouncementQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *AnnouncementGroupBy) Aggregate(fns ...AggregateFunc) *AnnouncementGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *AnnouncementGroupBy) 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[*AnnouncementQuery, *AnnouncementGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *AnnouncementGroupBy) sqlScan(ctx context.Context, root *AnnouncementQuery, 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)
}
// AnnouncementSelect is the builder for selecting fields of Announcement entities.
type AnnouncementSelect struct {
*AnnouncementQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *AnnouncementSelect) Aggregate(fns ...AggregateFunc) *AnnouncementSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *AnnouncementSelect) 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[*AnnouncementQuery, *AnnouncementSelect](ctx, _s.AnnouncementQuery, _s, _s.inters, v)
}
func (_s *AnnouncementSelect) sqlScan(ctx context.Context, root *AnnouncementQuery, 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,868 @@
// 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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
// AnnouncementUpdate is the builder for updating Announcement entities.
type AnnouncementUpdate struct {
config
hooks []Hook
mutation *AnnouncementMutation
}
// Where appends a list predicates to the AnnouncementUpdate builder.
func (_u *AnnouncementUpdate) Where(ps ...predicate.Announcement) *AnnouncementUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetTitle sets the "title" field.
func (_u *AnnouncementUpdate) SetTitle(v string) *AnnouncementUpdate {
_u.mutation.SetTitle(v)
return _u
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableTitle(v *string) *AnnouncementUpdate {
if v != nil {
_u.SetTitle(*v)
}
return _u
}
// SetContent sets the "content" field.
func (_u *AnnouncementUpdate) SetContent(v string) *AnnouncementUpdate {
_u.mutation.SetContent(v)
return _u
}
// SetNillableContent sets the "content" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableContent(v *string) *AnnouncementUpdate {
if v != nil {
_u.SetContent(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *AnnouncementUpdate) SetStatus(v string) *AnnouncementUpdate {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableStatus(v *string) *AnnouncementUpdate {
if v != nil {
_u.SetStatus(*v)
}
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)
return _u
}
// SetNillableTargeting sets the "targeting" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableTargeting(v *domain.AnnouncementTargeting) *AnnouncementUpdate {
if v != nil {
_u.SetTargeting(*v)
}
return _u
}
// ClearTargeting clears the value of the "targeting" field.
func (_u *AnnouncementUpdate) ClearTargeting() *AnnouncementUpdate {
_u.mutation.ClearTargeting()
return _u
}
// SetStartsAt sets the "starts_at" field.
func (_u *AnnouncementUpdate) SetStartsAt(v time.Time) *AnnouncementUpdate {
_u.mutation.SetStartsAt(v)
return _u
}
// SetNillableStartsAt sets the "starts_at" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableStartsAt(v *time.Time) *AnnouncementUpdate {
if v != nil {
_u.SetStartsAt(*v)
}
return _u
}
// ClearStartsAt clears the value of the "starts_at" field.
func (_u *AnnouncementUpdate) ClearStartsAt() *AnnouncementUpdate {
_u.mutation.ClearStartsAt()
return _u
}
// SetEndsAt sets the "ends_at" field.
func (_u *AnnouncementUpdate) SetEndsAt(v time.Time) *AnnouncementUpdate {
_u.mutation.SetEndsAt(v)
return _u
}
// SetNillableEndsAt sets the "ends_at" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableEndsAt(v *time.Time) *AnnouncementUpdate {
if v != nil {
_u.SetEndsAt(*v)
}
return _u
}
// ClearEndsAt clears the value of the "ends_at" field.
func (_u *AnnouncementUpdate) ClearEndsAt() *AnnouncementUpdate {
_u.mutation.ClearEndsAt()
return _u
}
// SetCreatedBy sets the "created_by" field.
func (_u *AnnouncementUpdate) SetCreatedBy(v int64) *AnnouncementUpdate {
_u.mutation.ResetCreatedBy()
_u.mutation.SetCreatedBy(v)
return _u
}
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableCreatedBy(v *int64) *AnnouncementUpdate {
if v != nil {
_u.SetCreatedBy(*v)
}
return _u
}
// AddCreatedBy adds value to the "created_by" field.
func (_u *AnnouncementUpdate) AddCreatedBy(v int64) *AnnouncementUpdate {
_u.mutation.AddCreatedBy(v)
return _u
}
// ClearCreatedBy clears the value of the "created_by" field.
func (_u *AnnouncementUpdate) ClearCreatedBy() *AnnouncementUpdate {
_u.mutation.ClearCreatedBy()
return _u
}
// SetUpdatedBy sets the "updated_by" field.
func (_u *AnnouncementUpdate) SetUpdatedBy(v int64) *AnnouncementUpdate {
_u.mutation.ResetUpdatedBy()
_u.mutation.SetUpdatedBy(v)
return _u
}
// SetNillableUpdatedBy sets the "updated_by" field if the given value is not nil.
func (_u *AnnouncementUpdate) SetNillableUpdatedBy(v *int64) *AnnouncementUpdate {
if v != nil {
_u.SetUpdatedBy(*v)
}
return _u
}
// AddUpdatedBy adds value to the "updated_by" field.
func (_u *AnnouncementUpdate) AddUpdatedBy(v int64) *AnnouncementUpdate {
_u.mutation.AddUpdatedBy(v)
return _u
}
// ClearUpdatedBy clears the value of the "updated_by" field.
func (_u *AnnouncementUpdate) ClearUpdatedBy() *AnnouncementUpdate {
_u.mutation.ClearUpdatedBy()
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AnnouncementUpdate) SetUpdatedAt(v time.Time) *AnnouncementUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// AddReadIDs adds the "reads" edge to the AnnouncementRead entity by IDs.
func (_u *AnnouncementUpdate) AddReadIDs(ids ...int64) *AnnouncementUpdate {
_u.mutation.AddReadIDs(ids...)
return _u
}
// AddReads adds the "reads" edges to the AnnouncementRead entity.
func (_u *AnnouncementUpdate) AddReads(v ...*AnnouncementRead) *AnnouncementUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddReadIDs(ids...)
}
// Mutation returns the AnnouncementMutation object of the builder.
func (_u *AnnouncementUpdate) Mutation() *AnnouncementMutation {
return _u.mutation
}
// ClearReads clears all "reads" edges to the AnnouncementRead entity.
func (_u *AnnouncementUpdate) ClearReads() *AnnouncementUpdate {
_u.mutation.ClearReads()
return _u
}
// RemoveReadIDs removes the "reads" edge to AnnouncementRead entities by IDs.
func (_u *AnnouncementUpdate) RemoveReadIDs(ids ...int64) *AnnouncementUpdate {
_u.mutation.RemoveReadIDs(ids...)
return _u
}
// RemoveReads removes "reads" edges to AnnouncementRead entities.
func (_u *AnnouncementUpdate) RemoveReads(v ...*AnnouncementRead) *AnnouncementUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveReadIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *AnnouncementUpdate) 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 *AnnouncementUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *AnnouncementUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AnnouncementUpdate) 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 *AnnouncementUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := announcement.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AnnouncementUpdate) check() error {
if v, ok := _u.mutation.Title(); ok {
if err := announcement.TitleValidator(v); err != nil {
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Announcement.title": %w`, err)}
}
}
if v, ok := _u.mutation.Content(); ok {
if err := announcement.ContentValidator(v); err != nil {
return &ValidationError{Name: "content", err: fmt.Errorf(`ent: validator failed for field "Announcement.content": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := announcement.StatusValidator(v); err != nil {
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
}
func (_u *AnnouncementUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.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.Title(); ok {
_spec.SetField(announcement.FieldTitle, field.TypeString, value)
}
if value, ok := _u.mutation.Content(); ok {
_spec.SetField(announcement.FieldContent, field.TypeString, value)
}
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)
}
if _u.mutation.TargetingCleared() {
_spec.ClearField(announcement.FieldTargeting, field.TypeJSON)
}
if value, ok := _u.mutation.StartsAt(); ok {
_spec.SetField(announcement.FieldStartsAt, field.TypeTime, value)
}
if _u.mutation.StartsAtCleared() {
_spec.ClearField(announcement.FieldStartsAt, field.TypeTime)
}
if value, ok := _u.mutation.EndsAt(); ok {
_spec.SetField(announcement.FieldEndsAt, field.TypeTime, value)
}
if _u.mutation.EndsAtCleared() {
_spec.ClearField(announcement.FieldEndsAt, field.TypeTime)
}
if value, ok := _u.mutation.CreatedBy(); ok {
_spec.SetField(announcement.FieldCreatedBy, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedCreatedBy(); ok {
_spec.AddField(announcement.FieldCreatedBy, field.TypeInt64, value)
}
if _u.mutation.CreatedByCleared() {
_spec.ClearField(announcement.FieldCreatedBy, field.TypeInt64)
}
if value, ok := _u.mutation.UpdatedBy(); ok {
_spec.SetField(announcement.FieldUpdatedBy, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedUpdatedBy(); ok {
_spec.AddField(announcement.FieldUpdatedBy, field.TypeInt64, value)
}
if _u.mutation.UpdatedByCleared() {
_spec.ClearField(announcement.FieldUpdatedBy, field.TypeInt64)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(announcement.FieldUpdatedAt, field.TypeTime, value)
}
if _u.mutation.ReadsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedReadsIDs(); len(nodes) > 0 && !_u.mutation.ReadsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ReadsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{announcement.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// AnnouncementUpdateOne is the builder for updating a single Announcement entity.
type AnnouncementUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AnnouncementMutation
}
// SetTitle sets the "title" field.
func (_u *AnnouncementUpdateOne) SetTitle(v string) *AnnouncementUpdateOne {
_u.mutation.SetTitle(v)
return _u
}
// SetNillableTitle sets the "title" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableTitle(v *string) *AnnouncementUpdateOne {
if v != nil {
_u.SetTitle(*v)
}
return _u
}
// SetContent sets the "content" field.
func (_u *AnnouncementUpdateOne) SetContent(v string) *AnnouncementUpdateOne {
_u.mutation.SetContent(v)
return _u
}
// SetNillableContent sets the "content" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableContent(v *string) *AnnouncementUpdateOne {
if v != nil {
_u.SetContent(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *AnnouncementUpdateOne) SetStatus(v string) *AnnouncementUpdateOne {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableStatus(v *string) *AnnouncementUpdateOne {
if v != nil {
_u.SetStatus(*v)
}
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)
return _u
}
// SetNillableTargeting sets the "targeting" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableTargeting(v *domain.AnnouncementTargeting) *AnnouncementUpdateOne {
if v != nil {
_u.SetTargeting(*v)
}
return _u
}
// ClearTargeting clears the value of the "targeting" field.
func (_u *AnnouncementUpdateOne) ClearTargeting() *AnnouncementUpdateOne {
_u.mutation.ClearTargeting()
return _u
}
// SetStartsAt sets the "starts_at" field.
func (_u *AnnouncementUpdateOne) SetStartsAt(v time.Time) *AnnouncementUpdateOne {
_u.mutation.SetStartsAt(v)
return _u
}
// SetNillableStartsAt sets the "starts_at" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableStartsAt(v *time.Time) *AnnouncementUpdateOne {
if v != nil {
_u.SetStartsAt(*v)
}
return _u
}
// ClearStartsAt clears the value of the "starts_at" field.
func (_u *AnnouncementUpdateOne) ClearStartsAt() *AnnouncementUpdateOne {
_u.mutation.ClearStartsAt()
return _u
}
// SetEndsAt sets the "ends_at" field.
func (_u *AnnouncementUpdateOne) SetEndsAt(v time.Time) *AnnouncementUpdateOne {
_u.mutation.SetEndsAt(v)
return _u
}
// SetNillableEndsAt sets the "ends_at" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableEndsAt(v *time.Time) *AnnouncementUpdateOne {
if v != nil {
_u.SetEndsAt(*v)
}
return _u
}
// ClearEndsAt clears the value of the "ends_at" field.
func (_u *AnnouncementUpdateOne) ClearEndsAt() *AnnouncementUpdateOne {
_u.mutation.ClearEndsAt()
return _u
}
// SetCreatedBy sets the "created_by" field.
func (_u *AnnouncementUpdateOne) SetCreatedBy(v int64) *AnnouncementUpdateOne {
_u.mutation.ResetCreatedBy()
_u.mutation.SetCreatedBy(v)
return _u
}
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableCreatedBy(v *int64) *AnnouncementUpdateOne {
if v != nil {
_u.SetCreatedBy(*v)
}
return _u
}
// AddCreatedBy adds value to the "created_by" field.
func (_u *AnnouncementUpdateOne) AddCreatedBy(v int64) *AnnouncementUpdateOne {
_u.mutation.AddCreatedBy(v)
return _u
}
// ClearCreatedBy clears the value of the "created_by" field.
func (_u *AnnouncementUpdateOne) ClearCreatedBy() *AnnouncementUpdateOne {
_u.mutation.ClearCreatedBy()
return _u
}
// SetUpdatedBy sets the "updated_by" field.
func (_u *AnnouncementUpdateOne) SetUpdatedBy(v int64) *AnnouncementUpdateOne {
_u.mutation.ResetUpdatedBy()
_u.mutation.SetUpdatedBy(v)
return _u
}
// SetNillableUpdatedBy sets the "updated_by" field if the given value is not nil.
func (_u *AnnouncementUpdateOne) SetNillableUpdatedBy(v *int64) *AnnouncementUpdateOne {
if v != nil {
_u.SetUpdatedBy(*v)
}
return _u
}
// AddUpdatedBy adds value to the "updated_by" field.
func (_u *AnnouncementUpdateOne) AddUpdatedBy(v int64) *AnnouncementUpdateOne {
_u.mutation.AddUpdatedBy(v)
return _u
}
// ClearUpdatedBy clears the value of the "updated_by" field.
func (_u *AnnouncementUpdateOne) ClearUpdatedBy() *AnnouncementUpdateOne {
_u.mutation.ClearUpdatedBy()
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AnnouncementUpdateOne) SetUpdatedAt(v time.Time) *AnnouncementUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// AddReadIDs adds the "reads" edge to the AnnouncementRead entity by IDs.
func (_u *AnnouncementUpdateOne) AddReadIDs(ids ...int64) *AnnouncementUpdateOne {
_u.mutation.AddReadIDs(ids...)
return _u
}
// AddReads adds the "reads" edges to the AnnouncementRead entity.
func (_u *AnnouncementUpdateOne) AddReads(v ...*AnnouncementRead) *AnnouncementUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddReadIDs(ids...)
}
// Mutation returns the AnnouncementMutation object of the builder.
func (_u *AnnouncementUpdateOne) Mutation() *AnnouncementMutation {
return _u.mutation
}
// ClearReads clears all "reads" edges to the AnnouncementRead entity.
func (_u *AnnouncementUpdateOne) ClearReads() *AnnouncementUpdateOne {
_u.mutation.ClearReads()
return _u
}
// RemoveReadIDs removes the "reads" edge to AnnouncementRead entities by IDs.
func (_u *AnnouncementUpdateOne) RemoveReadIDs(ids ...int64) *AnnouncementUpdateOne {
_u.mutation.RemoveReadIDs(ids...)
return _u
}
// RemoveReads removes "reads" edges to AnnouncementRead entities.
func (_u *AnnouncementUpdateOne) RemoveReads(v ...*AnnouncementRead) *AnnouncementUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveReadIDs(ids...)
}
// Where appends a list predicates to the AnnouncementUpdate builder.
func (_u *AnnouncementUpdateOne) Where(ps ...predicate.Announcement) *AnnouncementUpdateOne {
_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 *AnnouncementUpdateOne) Select(field string, fields ...string) *AnnouncementUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated Announcement entity.
func (_u *AnnouncementUpdateOne) Save(ctx context.Context) (*Announcement, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AnnouncementUpdateOne) SaveX(ctx context.Context) *Announcement {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *AnnouncementUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AnnouncementUpdateOne) 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 *AnnouncementUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := announcement.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AnnouncementUpdateOne) check() error {
if v, ok := _u.mutation.Title(); ok {
if err := announcement.TitleValidator(v); err != nil {
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Announcement.title": %w`, err)}
}
}
if v, ok := _u.mutation.Content(); ok {
if err := announcement.ContentValidator(v); err != nil {
return &ValidationError{Name: "content", err: fmt.Errorf(`ent: validator failed for field "Announcement.content": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := announcement.StatusValidator(v); err != nil {
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
}
func (_u *AnnouncementUpdateOne) sqlSave(ctx context.Context) (_node *Announcement, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Announcement.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, announcement.FieldID)
for _, f := range fields {
if !announcement.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != announcement.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.Title(); ok {
_spec.SetField(announcement.FieldTitle, field.TypeString, value)
}
if value, ok := _u.mutation.Content(); ok {
_spec.SetField(announcement.FieldContent, field.TypeString, value)
}
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)
}
if _u.mutation.TargetingCleared() {
_spec.ClearField(announcement.FieldTargeting, field.TypeJSON)
}
if value, ok := _u.mutation.StartsAt(); ok {
_spec.SetField(announcement.FieldStartsAt, field.TypeTime, value)
}
if _u.mutation.StartsAtCleared() {
_spec.ClearField(announcement.FieldStartsAt, field.TypeTime)
}
if value, ok := _u.mutation.EndsAt(); ok {
_spec.SetField(announcement.FieldEndsAt, field.TypeTime, value)
}
if _u.mutation.EndsAtCleared() {
_spec.ClearField(announcement.FieldEndsAt, field.TypeTime)
}
if value, ok := _u.mutation.CreatedBy(); ok {
_spec.SetField(announcement.FieldCreatedBy, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedCreatedBy(); ok {
_spec.AddField(announcement.FieldCreatedBy, field.TypeInt64, value)
}
if _u.mutation.CreatedByCleared() {
_spec.ClearField(announcement.FieldCreatedBy, field.TypeInt64)
}
if value, ok := _u.mutation.UpdatedBy(); ok {
_spec.SetField(announcement.FieldUpdatedBy, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedUpdatedBy(); ok {
_spec.AddField(announcement.FieldUpdatedBy, field.TypeInt64, value)
}
if _u.mutation.UpdatedByCleared() {
_spec.ClearField(announcement.FieldUpdatedBy, field.TypeInt64)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(announcement.FieldUpdatedAt, field.TypeTime, value)
}
if _u.mutation.ReadsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedReadsIDs(); len(nodes) > 0 && !_u.mutation.ReadsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ReadsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: announcement.ReadsTable,
Columns: []string{announcement.ReadsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Announcement{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{announcement.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

@@ -0,0 +1,185 @@
// 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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AnnouncementRead is the model entity for the AnnouncementRead schema.
type AnnouncementRead struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// AnnouncementID holds the value of the "announcement_id" field.
AnnouncementID int64 `json:"announcement_id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// 用户首次已读时间
ReadAt time.Time `json:"read_at,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AnnouncementReadQuery when eager-loading is set.
Edges AnnouncementReadEdges `json:"edges"`
selectValues sql.SelectValues
}
// AnnouncementReadEdges holds the relations/edges for other nodes in the graph.
type AnnouncementReadEdges struct {
// Announcement holds the value of the announcement edge.
Announcement *Announcement `json:"announcement,omitempty"`
// 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 [2]bool
}
// AnnouncementOrErr returns the Announcement value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AnnouncementReadEdges) AnnouncementOrErr() (*Announcement, error) {
if e.Announcement != nil {
return e.Announcement, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: announcement.Label}
}
return nil, &NotLoadedError{edge: "announcement"}
}
// 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 AnnouncementReadEdges) UserOrErr() (*User, error) {
if e.User != nil {
return e.User, nil
} else if e.loadedTypes[1] {
return nil, &NotFoundError{label: user.Label}
}
return nil, &NotLoadedError{edge: "user"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AnnouncementRead) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case announcementread.FieldID, announcementread.FieldAnnouncementID, announcementread.FieldUserID:
values[i] = new(sql.NullInt64)
case announcementread.FieldReadAt, announcementread.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 AnnouncementRead fields.
func (_m *AnnouncementRead) 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 announcementread.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 announcementread.FieldAnnouncementID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field announcement_id", values[i])
} else if value.Valid {
_m.AnnouncementID = value.Int64
}
case announcementread.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 announcementread.FieldReadAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field read_at", values[i])
} else if value.Valid {
_m.ReadAt = value.Time
}
case announcementread.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 AnnouncementRead.
// This includes values selected through modifiers, order, etc.
func (_m *AnnouncementRead) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryAnnouncement queries the "announcement" edge of the AnnouncementRead entity.
func (_m *AnnouncementRead) QueryAnnouncement() *AnnouncementQuery {
return NewAnnouncementReadClient(_m.config).QueryAnnouncement(_m)
}
// QueryUser queries the "user" edge of the AnnouncementRead entity.
func (_m *AnnouncementRead) QueryUser() *UserQuery {
return NewAnnouncementReadClient(_m.config).QueryUser(_m)
}
// Update returns a builder for updating this AnnouncementRead.
// Note that you need to call AnnouncementRead.Unwrap() before calling this method if this AnnouncementRead
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *AnnouncementRead) Update() *AnnouncementReadUpdateOne {
return NewAnnouncementReadClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the AnnouncementRead 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 *AnnouncementRead) Unwrap() *AnnouncementRead {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: AnnouncementRead is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *AnnouncementRead) String() string {
var builder strings.Builder
builder.WriteString("AnnouncementRead(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("announcement_id=")
builder.WriteString(fmt.Sprintf("%v", _m.AnnouncementID))
builder.WriteString(", ")
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
builder.WriteString("read_at=")
builder.WriteString(_m.ReadAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// AnnouncementReads is a parsable slice of AnnouncementRead.
type AnnouncementReads []*AnnouncementRead

View File

@@ -0,0 +1,127 @@
// Code generated by ent, DO NOT EDIT.
package announcementread
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the announcementread type in the database.
Label = "announcement_read"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldAnnouncementID holds the string denoting the announcement_id field in the database.
FieldAnnouncementID = "announcement_id"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldReadAt holds the string denoting the read_at field in the database.
FieldReadAt = "read_at"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// EdgeAnnouncement holds the string denoting the announcement edge name in mutations.
EdgeAnnouncement = "announcement"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// Table holds the table name of the announcementread in the database.
Table = "announcement_reads"
// AnnouncementTable is the table that holds the announcement relation/edge.
AnnouncementTable = "announcement_reads"
// AnnouncementInverseTable is the table name for the Announcement entity.
// It exists in this package in order to avoid circular dependency with the "announcement" package.
AnnouncementInverseTable = "announcements"
// AnnouncementColumn is the table column denoting the announcement relation/edge.
AnnouncementColumn = "announcement_id"
// UserTable is the table that holds the user relation/edge.
UserTable = "announcement_reads"
// 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 announcementread fields.
var Columns = []string{
FieldID,
FieldAnnouncementID,
FieldUserID,
FieldReadAt,
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 (
// DefaultReadAt holds the default value on creation for the "read_at" field.
DefaultReadAt func() time.Time
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
)
// OrderOption defines the ordering options for the AnnouncementRead 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()
}
// ByAnnouncementID orders the results by the announcement_id field.
func ByAnnouncementID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAnnouncementID, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByReadAt orders the results by the read_at field.
func ByReadAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldReadAt, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByAnnouncementField orders the results by announcement field.
func ByAnnouncementField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAnnouncementStep(), sql.OrderByField(field, opts...))
}
}
// 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 newAnnouncementStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AnnouncementInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, AnnouncementTable, AnnouncementColumn),
)
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}

View File

@@ -0,0 +1,257 @@
// Code generated by ent, DO NOT EDIT.
package announcementread
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLTE(FieldID, id))
}
// AnnouncementID applies equality check predicate on the "announcement_id" field. It's identical to AnnouncementIDEQ.
func AnnouncementID(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldAnnouncementID, v))
}
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
func UserID(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldUserID, v))
}
// ReadAt applies equality check predicate on the "read_at" field. It's identical to ReadAtEQ.
func ReadAt(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldReadAt, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldCreatedAt, v))
}
// AnnouncementIDEQ applies the EQ predicate on the "announcement_id" field.
func AnnouncementIDEQ(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldAnnouncementID, v))
}
// AnnouncementIDNEQ applies the NEQ predicate on the "announcement_id" field.
func AnnouncementIDNEQ(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNEQ(FieldAnnouncementID, v))
}
// AnnouncementIDIn applies the In predicate on the "announcement_id" field.
func AnnouncementIDIn(vs ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldIn(FieldAnnouncementID, vs...))
}
// AnnouncementIDNotIn applies the NotIn predicate on the "announcement_id" field.
func AnnouncementIDNotIn(vs ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNotIn(FieldAnnouncementID, vs...))
}
// UserIDEQ applies the EQ predicate on the "user_id" field.
func UserIDEQ(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldUserID, v))
}
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
func UserIDNEQ(v int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNEQ(FieldUserID, v))
}
// UserIDIn applies the In predicate on the "user_id" field.
func UserIDIn(vs ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldIn(FieldUserID, vs...))
}
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
func UserIDNotIn(vs ...int64) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNotIn(FieldUserID, vs...))
}
// ReadAtEQ applies the EQ predicate on the "read_at" field.
func ReadAtEQ(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldReadAt, v))
}
// ReadAtNEQ applies the NEQ predicate on the "read_at" field.
func ReadAtNEQ(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNEQ(FieldReadAt, v))
}
// ReadAtIn applies the In predicate on the "read_at" field.
func ReadAtIn(vs ...time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldIn(FieldReadAt, vs...))
}
// ReadAtNotIn applies the NotIn predicate on the "read_at" field.
func ReadAtNotIn(vs ...time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNotIn(FieldReadAt, vs...))
}
// ReadAtGT applies the GT predicate on the "read_at" field.
func ReadAtGT(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGT(FieldReadAt, v))
}
// ReadAtGTE applies the GTE predicate on the "read_at" field.
func ReadAtGTE(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGTE(FieldReadAt, v))
}
// ReadAtLT applies the LT predicate on the "read_at" field.
func ReadAtLT(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLT(FieldReadAt, v))
}
// ReadAtLTE applies the LTE predicate on the "read_at" field.
func ReadAtLTE(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLTE(FieldReadAt, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.FieldLTE(FieldCreatedAt, v))
}
// HasAnnouncement applies the HasEdge predicate on the "announcement" edge.
func HasAnnouncement() predicate.AnnouncementRead {
return predicate.AnnouncementRead(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, AnnouncementTable, AnnouncementColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasAnnouncementWith applies the HasEdge predicate on the "announcement" edge with a given conditions (other predicates).
func HasAnnouncementWith(preds ...predicate.Announcement) predicate.AnnouncementRead {
return predicate.AnnouncementRead(func(s *sql.Selector) {
step := newAnnouncementStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.AnnouncementRead {
return predicate.AnnouncementRead(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.AnnouncementRead {
return predicate.AnnouncementRead(func(s *sql.Selector) {
step := newUserStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.AnnouncementRead) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AnnouncementRead) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.AnnouncementRead) predicate.AnnouncementRead {
return predicate.AnnouncementRead(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,660 @@
// 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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AnnouncementReadCreate is the builder for creating a AnnouncementRead entity.
type AnnouncementReadCreate struct {
config
mutation *AnnouncementReadMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetAnnouncementID sets the "announcement_id" field.
func (_c *AnnouncementReadCreate) SetAnnouncementID(v int64) *AnnouncementReadCreate {
_c.mutation.SetAnnouncementID(v)
return _c
}
// SetUserID sets the "user_id" field.
func (_c *AnnouncementReadCreate) SetUserID(v int64) *AnnouncementReadCreate {
_c.mutation.SetUserID(v)
return _c
}
// SetReadAt sets the "read_at" field.
func (_c *AnnouncementReadCreate) SetReadAt(v time.Time) *AnnouncementReadCreate {
_c.mutation.SetReadAt(v)
return _c
}
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
func (_c *AnnouncementReadCreate) SetNillableReadAt(v *time.Time) *AnnouncementReadCreate {
if v != nil {
_c.SetReadAt(*v)
}
return _c
}
// SetCreatedAt sets the "created_at" field.
func (_c *AnnouncementReadCreate) SetCreatedAt(v time.Time) *AnnouncementReadCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *AnnouncementReadCreate) SetNillableCreatedAt(v *time.Time) *AnnouncementReadCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
func (_c *AnnouncementReadCreate) SetAnnouncement(v *Announcement) *AnnouncementReadCreate {
return _c.SetAnnouncementID(v.ID)
}
// SetUser sets the "user" edge to the User entity.
func (_c *AnnouncementReadCreate) SetUser(v *User) *AnnouncementReadCreate {
return _c.SetUserID(v.ID)
}
// Mutation returns the AnnouncementReadMutation object of the builder.
func (_c *AnnouncementReadCreate) Mutation() *AnnouncementReadMutation {
return _c.mutation
}
// Save creates the AnnouncementRead in the database.
func (_c *AnnouncementReadCreate) Save(ctx context.Context) (*AnnouncementRead, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *AnnouncementReadCreate) SaveX(ctx context.Context) *AnnouncementRead {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AnnouncementReadCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AnnouncementReadCreate) 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 *AnnouncementReadCreate) defaults() {
if _, ok := _c.mutation.ReadAt(); !ok {
v := announcementread.DefaultReadAt()
_c.mutation.SetReadAt(v)
}
if _, ok := _c.mutation.CreatedAt(); !ok {
v := announcementread.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *AnnouncementReadCreate) check() error {
if _, ok := _c.mutation.AnnouncementID(); !ok {
return &ValidationError{Name: "announcement_id", err: errors.New(`ent: missing required field "AnnouncementRead.announcement_id"`)}
}
if _, ok := _c.mutation.UserID(); !ok {
return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "AnnouncementRead.user_id"`)}
}
if _, ok := _c.mutation.ReadAt(); !ok {
return &ValidationError{Name: "read_at", err: errors.New(`ent: missing required field "AnnouncementRead.read_at"`)}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AnnouncementRead.created_at"`)}
}
if len(_c.mutation.AnnouncementIDs()) == 0 {
return &ValidationError{Name: "announcement", err: errors.New(`ent: missing required edge "AnnouncementRead.announcement"`)}
}
if len(_c.mutation.UserIDs()) == 0 {
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "AnnouncementRead.user"`)}
}
return nil
}
func (_c *AnnouncementReadCreate) sqlSave(ctx context.Context) (*AnnouncementRead, 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 *AnnouncementReadCreate) createSpec() (*AnnouncementRead, *sqlgraph.CreateSpec) {
var (
_node = &AnnouncementRead{config: _c.config}
_spec = sqlgraph.NewCreateSpec(announcementread.Table, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.ReadAt(); ok {
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
_node.ReadAt = value
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(announcementread.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if nodes := _c.mutation.AnnouncementIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.AnnouncementTable,
Columns: []string{announcementread.AnnouncementColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.AnnouncementID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.UserTable,
Columns: []string{announcementread.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.UserID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.AnnouncementRead.Create().
// SetAnnouncementID(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.AnnouncementReadUpsert) {
// SetAnnouncementID(v+v).
// }).
// Exec(ctx)
func (_c *AnnouncementReadCreate) OnConflict(opts ...sql.ConflictOption) *AnnouncementReadUpsertOne {
_c.conflict = opts
return &AnnouncementReadUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *AnnouncementReadCreate) OnConflictColumns(columns ...string) *AnnouncementReadUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &AnnouncementReadUpsertOne{
create: _c,
}
}
type (
// AnnouncementReadUpsertOne is the builder for "upsert"-ing
// one AnnouncementRead node.
AnnouncementReadUpsertOne struct {
create *AnnouncementReadCreate
}
// AnnouncementReadUpsert is the "OnConflict" setter.
AnnouncementReadUpsert struct {
*sql.UpdateSet
}
)
// SetAnnouncementID sets the "announcement_id" field.
func (u *AnnouncementReadUpsert) SetAnnouncementID(v int64) *AnnouncementReadUpsert {
u.Set(announcementread.FieldAnnouncementID, v)
return u
}
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsert) UpdateAnnouncementID() *AnnouncementReadUpsert {
u.SetExcluded(announcementread.FieldAnnouncementID)
return u
}
// SetUserID sets the "user_id" field.
func (u *AnnouncementReadUpsert) SetUserID(v int64) *AnnouncementReadUpsert {
u.Set(announcementread.FieldUserID, v)
return u
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsert) UpdateUserID() *AnnouncementReadUpsert {
u.SetExcluded(announcementread.FieldUserID)
return u
}
// SetReadAt sets the "read_at" field.
func (u *AnnouncementReadUpsert) SetReadAt(v time.Time) *AnnouncementReadUpsert {
u.Set(announcementread.FieldReadAt, v)
return u
}
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
func (u *AnnouncementReadUpsert) UpdateReadAt() *AnnouncementReadUpsert {
u.SetExcluded(announcementread.FieldReadAt)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AnnouncementReadUpsertOne) UpdateNewValues() *AnnouncementReadUpsertOne {
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(announcementread.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AnnouncementReadUpsertOne) Ignore() *AnnouncementReadUpsertOne {
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 *AnnouncementReadUpsertOne) DoNothing() *AnnouncementReadUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AnnouncementReadCreate.OnConflict
// documentation for more info.
func (u *AnnouncementReadUpsertOne) Update(set func(*AnnouncementReadUpsert)) *AnnouncementReadUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AnnouncementReadUpsert{UpdateSet: update})
}))
return u
}
// SetAnnouncementID sets the "announcement_id" field.
func (u *AnnouncementReadUpsertOne) SetAnnouncementID(v int64) *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetAnnouncementID(v)
})
}
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsertOne) UpdateAnnouncementID() *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateAnnouncementID()
})
}
// SetUserID sets the "user_id" field.
func (u *AnnouncementReadUpsertOne) SetUserID(v int64) *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetUserID(v)
})
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsertOne) UpdateUserID() *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateUserID()
})
}
// SetReadAt sets the "read_at" field.
func (u *AnnouncementReadUpsertOne) SetReadAt(v time.Time) *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetReadAt(v)
})
}
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
func (u *AnnouncementReadUpsertOne) UpdateReadAt() *AnnouncementReadUpsertOne {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateReadAt()
})
}
// Exec executes the query.
func (u *AnnouncementReadUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AnnouncementReadCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AnnouncementReadUpsertOne) 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 *AnnouncementReadUpsertOne) 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 *AnnouncementReadUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// AnnouncementReadCreateBulk is the builder for creating many AnnouncementRead entities in bulk.
type AnnouncementReadCreateBulk struct {
config
err error
builders []*AnnouncementReadCreate
conflict []sql.ConflictOption
}
// Save creates the AnnouncementRead entities in the database.
func (_c *AnnouncementReadCreateBulk) Save(ctx context.Context) ([]*AnnouncementRead, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*AnnouncementRead, 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.(*AnnouncementReadMutation)
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 *AnnouncementReadCreateBulk) SaveX(ctx context.Context) []*AnnouncementRead {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AnnouncementReadCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AnnouncementReadCreateBulk) 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.AnnouncementRead.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.AnnouncementReadUpsert) {
// SetAnnouncementID(v+v).
// }).
// Exec(ctx)
func (_c *AnnouncementReadCreateBulk) OnConflict(opts ...sql.ConflictOption) *AnnouncementReadUpsertBulk {
_c.conflict = opts
return &AnnouncementReadUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *AnnouncementReadCreateBulk) OnConflictColumns(columns ...string) *AnnouncementReadUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &AnnouncementReadUpsertBulk{
create: _c,
}
}
// AnnouncementReadUpsertBulk is the builder for "upsert"-ing
// a bulk of AnnouncementRead nodes.
type AnnouncementReadUpsertBulk struct {
create *AnnouncementReadCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AnnouncementReadUpsertBulk) UpdateNewValues() *AnnouncementReadUpsertBulk {
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(announcementread.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AnnouncementRead.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AnnouncementReadUpsertBulk) Ignore() *AnnouncementReadUpsertBulk {
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 *AnnouncementReadUpsertBulk) DoNothing() *AnnouncementReadUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AnnouncementReadCreateBulk.OnConflict
// documentation for more info.
func (u *AnnouncementReadUpsertBulk) Update(set func(*AnnouncementReadUpsert)) *AnnouncementReadUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AnnouncementReadUpsert{UpdateSet: update})
}))
return u
}
// SetAnnouncementID sets the "announcement_id" field.
func (u *AnnouncementReadUpsertBulk) SetAnnouncementID(v int64) *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetAnnouncementID(v)
})
}
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsertBulk) UpdateAnnouncementID() *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateAnnouncementID()
})
}
// SetUserID sets the "user_id" field.
func (u *AnnouncementReadUpsertBulk) SetUserID(v int64) *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetUserID(v)
})
}
// UpdateUserID sets the "user_id" field to the value that was provided on create.
func (u *AnnouncementReadUpsertBulk) UpdateUserID() *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateUserID()
})
}
// SetReadAt sets the "read_at" field.
func (u *AnnouncementReadUpsertBulk) SetReadAt(v time.Time) *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.SetReadAt(v)
})
}
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
func (u *AnnouncementReadUpsertBulk) UpdateReadAt() *AnnouncementReadUpsertBulk {
return u.Update(func(s *AnnouncementReadUpsert) {
s.UpdateReadAt()
})
}
// Exec executes the query.
func (u *AnnouncementReadUpsertBulk) 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 AnnouncementReadCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AnnouncementReadCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AnnouncementReadUpsertBulk) 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/announcementread"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AnnouncementReadDelete is the builder for deleting a AnnouncementRead entity.
type AnnouncementReadDelete struct {
config
hooks []Hook
mutation *AnnouncementReadMutation
}
// Where appends a list predicates to the AnnouncementReadDelete builder.
func (_d *AnnouncementReadDelete) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AnnouncementReadDelete) 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 *AnnouncementReadDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *AnnouncementReadDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(announcementread.Table, sqlgraph.NewFieldSpec(announcementread.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
}
// AnnouncementReadDeleteOne is the builder for deleting a single AnnouncementRead entity.
type AnnouncementReadDeleteOne struct {
_d *AnnouncementReadDelete
}
// Where appends a list predicates to the AnnouncementReadDelete builder.
func (_d *AnnouncementReadDeleteOne) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *AnnouncementReadDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{announcementread.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AnnouncementReadDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,718 @@
// 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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AnnouncementReadQuery is the builder for querying AnnouncementRead entities.
type AnnouncementReadQuery struct {
config
ctx *QueryContext
order []announcementread.OrderOption
inters []Interceptor
predicates []predicate.AnnouncementRead
withAnnouncement *AnnouncementQuery
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 AnnouncementReadQuery builder.
func (_q *AnnouncementReadQuery) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *AnnouncementReadQuery) Limit(limit int) *AnnouncementReadQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *AnnouncementReadQuery) Offset(offset int) *AnnouncementReadQuery {
_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 *AnnouncementReadQuery) Unique(unique bool) *AnnouncementReadQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *AnnouncementReadQuery) Order(o ...announcementread.OrderOption) *AnnouncementReadQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryAnnouncement chains the current query on the "announcement" edge.
func (_q *AnnouncementReadQuery) QueryAnnouncement() *AnnouncementQuery {
query := (&AnnouncementClient{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(announcementread.Table, announcementread.FieldID, selector),
sqlgraph.To(announcement.Table, announcement.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.AnnouncementTable, announcementread.AnnouncementColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryUser chains the current query on the "user" edge.
func (_q *AnnouncementReadQuery) 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(announcementread.Table, announcementread.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.UserTable, announcementread.UserColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first AnnouncementRead entity from the query.
// Returns a *NotFoundError when no AnnouncementRead was found.
func (_q *AnnouncementReadQuery) First(ctx context.Context) (*AnnouncementRead, 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{announcementread.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *AnnouncementReadQuery) FirstX(ctx context.Context) *AnnouncementRead {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first AnnouncementRead ID from the query.
// Returns a *NotFoundError when no AnnouncementRead ID was found.
func (_q *AnnouncementReadQuery) 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{announcementread.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *AnnouncementReadQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single AnnouncementRead entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one AnnouncementRead entity is found.
// Returns a *NotFoundError when no AnnouncementRead entities are found.
func (_q *AnnouncementReadQuery) Only(ctx context.Context) (*AnnouncementRead, 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{announcementread.Label}
default:
return nil, &NotSingularError{announcementread.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *AnnouncementReadQuery) OnlyX(ctx context.Context) *AnnouncementRead {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only AnnouncementRead ID in the query.
// Returns a *NotSingularError when more than one AnnouncementRead ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *AnnouncementReadQuery) 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{announcementread.Label}
default:
err = &NotSingularError{announcementread.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *AnnouncementReadQuery) 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 AnnouncementReads.
func (_q *AnnouncementReadQuery) All(ctx context.Context) ([]*AnnouncementRead, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AnnouncementRead, *AnnouncementReadQuery]()
return withInterceptors[[]*AnnouncementRead](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *AnnouncementReadQuery) AllX(ctx context.Context) []*AnnouncementRead {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of AnnouncementRead IDs.
func (_q *AnnouncementReadQuery) 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(announcementread.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *AnnouncementReadQuery) 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 *AnnouncementReadQuery) 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[*AnnouncementReadQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *AnnouncementReadQuery) 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 *AnnouncementReadQuery) 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 *AnnouncementReadQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AnnouncementReadQuery 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 *AnnouncementReadQuery) Clone() *AnnouncementReadQuery {
if _q == nil {
return nil
}
return &AnnouncementReadQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]announcementread.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.AnnouncementRead{}, _q.predicates...),
withAnnouncement: _q.withAnnouncement.Clone(),
withUser: _q.withUser.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithAnnouncement tells the query-builder to eager-load the nodes that are connected to
// the "announcement" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AnnouncementReadQuery) WithAnnouncement(opts ...func(*AnnouncementQuery)) *AnnouncementReadQuery {
query := (&AnnouncementClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withAnnouncement = query
return _q
}
// 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 *AnnouncementReadQuery) WithUser(opts ...func(*UserQuery)) *AnnouncementReadQuery {
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 {
// AnnouncementID int64 `json:"announcement_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.AnnouncementRead.Query().
// GroupBy(announcementread.FieldAnnouncementID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *AnnouncementReadQuery) GroupBy(field string, fields ...string) *AnnouncementReadGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &AnnouncementReadGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = announcementread.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 {
// AnnouncementID int64 `json:"announcement_id,omitempty"`
// }
//
// client.AnnouncementRead.Query().
// Select(announcementread.FieldAnnouncementID).
// Scan(ctx, &v)
func (_q *AnnouncementReadQuery) Select(fields ...string) *AnnouncementReadSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &AnnouncementReadSelect{AnnouncementReadQuery: _q}
sbuild.label = announcementread.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AnnouncementReadSelect configured with the given aggregations.
func (_q *AnnouncementReadQuery) Aggregate(fns ...AggregateFunc) *AnnouncementReadSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *AnnouncementReadQuery) 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 !announcementread.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 *AnnouncementReadQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AnnouncementRead, error) {
var (
nodes = []*AnnouncementRead{}
_spec = _q.querySpec()
loadedTypes = [2]bool{
_q.withAnnouncement != nil,
_q.withUser != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*AnnouncementRead).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &AnnouncementRead{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.withAnnouncement; query != nil {
if err := _q.loadAnnouncement(ctx, query, nodes, nil,
func(n *AnnouncementRead, e *Announcement) { n.Edges.Announcement = e }); err != nil {
return nil, err
}
}
if query := _q.withUser; query != nil {
if err := _q.loadUser(ctx, query, nodes, nil,
func(n *AnnouncementRead, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *AnnouncementReadQuery) loadAnnouncement(ctx context.Context, query *AnnouncementQuery, nodes []*AnnouncementRead, init func(*AnnouncementRead), assign func(*AnnouncementRead, *Announcement)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*AnnouncementRead)
for i := range nodes {
fk := nodes[i].AnnouncementID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(announcement.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 "announcement_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *AnnouncementReadQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*AnnouncementRead, init func(*AnnouncementRead), assign func(*AnnouncementRead, *User)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*AnnouncementRead)
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 *AnnouncementReadQuery) 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 *AnnouncementReadQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.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, announcementread.FieldID)
for i := range fields {
if fields[i] != announcementread.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withAnnouncement != nil {
_spec.Node.AddColumnOnce(announcementread.FieldAnnouncementID)
}
if _q.withUser != nil {
_spec.Node.AddColumnOnce(announcementread.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 *AnnouncementReadQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(announcementread.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = announcementread.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 *AnnouncementReadQuery) ForUpdate(opts ...sql.LockOption) *AnnouncementReadQuery {
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 *AnnouncementReadQuery) ForShare(opts ...sql.LockOption) *AnnouncementReadQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// AnnouncementReadGroupBy is the group-by builder for AnnouncementRead entities.
type AnnouncementReadGroupBy struct {
selector
build *AnnouncementReadQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *AnnouncementReadGroupBy) Aggregate(fns ...AggregateFunc) *AnnouncementReadGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *AnnouncementReadGroupBy) 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[*AnnouncementReadQuery, *AnnouncementReadGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *AnnouncementReadGroupBy) sqlScan(ctx context.Context, root *AnnouncementReadQuery, 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)
}
// AnnouncementReadSelect is the builder for selecting fields of AnnouncementRead entities.
type AnnouncementReadSelect struct {
*AnnouncementReadQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *AnnouncementReadSelect) Aggregate(fns ...AggregateFunc) *AnnouncementReadSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *AnnouncementReadSelect) 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[*AnnouncementReadQuery, *AnnouncementReadSelect](ctx, _s.AnnouncementReadQuery, _s, _s.inters, v)
}
func (_s *AnnouncementReadSelect) sqlScan(ctx context.Context, root *AnnouncementReadQuery, 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,456 @@
// 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/announcement"
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AnnouncementReadUpdate is the builder for updating AnnouncementRead entities.
type AnnouncementReadUpdate struct {
config
hooks []Hook
mutation *AnnouncementReadMutation
}
// Where appends a list predicates to the AnnouncementReadUpdate builder.
func (_u *AnnouncementReadUpdate) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetAnnouncementID sets the "announcement_id" field.
func (_u *AnnouncementReadUpdate) SetAnnouncementID(v int64) *AnnouncementReadUpdate {
_u.mutation.SetAnnouncementID(v)
return _u
}
// SetNillableAnnouncementID sets the "announcement_id" field if the given value is not nil.
func (_u *AnnouncementReadUpdate) SetNillableAnnouncementID(v *int64) *AnnouncementReadUpdate {
if v != nil {
_u.SetAnnouncementID(*v)
}
return _u
}
// SetUserID sets the "user_id" field.
func (_u *AnnouncementReadUpdate) SetUserID(v int64) *AnnouncementReadUpdate {
_u.mutation.SetUserID(v)
return _u
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (_u *AnnouncementReadUpdate) SetNillableUserID(v *int64) *AnnouncementReadUpdate {
if v != nil {
_u.SetUserID(*v)
}
return _u
}
// SetReadAt sets the "read_at" field.
func (_u *AnnouncementReadUpdate) SetReadAt(v time.Time) *AnnouncementReadUpdate {
_u.mutation.SetReadAt(v)
return _u
}
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
func (_u *AnnouncementReadUpdate) SetNillableReadAt(v *time.Time) *AnnouncementReadUpdate {
if v != nil {
_u.SetReadAt(*v)
}
return _u
}
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
func (_u *AnnouncementReadUpdate) SetAnnouncement(v *Announcement) *AnnouncementReadUpdate {
return _u.SetAnnouncementID(v.ID)
}
// SetUser sets the "user" edge to the User entity.
func (_u *AnnouncementReadUpdate) SetUser(v *User) *AnnouncementReadUpdate {
return _u.SetUserID(v.ID)
}
// Mutation returns the AnnouncementReadMutation object of the builder.
func (_u *AnnouncementReadUpdate) Mutation() *AnnouncementReadMutation {
return _u.mutation
}
// ClearAnnouncement clears the "announcement" edge to the Announcement entity.
func (_u *AnnouncementReadUpdate) ClearAnnouncement() *AnnouncementReadUpdate {
_u.mutation.ClearAnnouncement()
return _u
}
// ClearUser clears the "user" edge to the User entity.
func (_u *AnnouncementReadUpdate) ClearUser() *AnnouncementReadUpdate {
_u.mutation.ClearUser()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *AnnouncementReadUpdate) 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 *AnnouncementReadUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *AnnouncementReadUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AnnouncementReadUpdate) 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 *AnnouncementReadUpdate) check() error {
if _u.mutation.AnnouncementCleared() && len(_u.mutation.AnnouncementIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.announcement"`)
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.user"`)
}
return nil
}
func (_u *AnnouncementReadUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.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.ReadAt(); ok {
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
}
if _u.mutation.AnnouncementCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.AnnouncementTable,
Columns: []string{announcementread.AnnouncementColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AnnouncementIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.AnnouncementTable,
Columns: []string{announcementread.AnnouncementColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.UserTable,
Columns: []string{announcementread.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.UserTable,
Columns: []string{announcementread.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{announcementread.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// AnnouncementReadUpdateOne is the builder for updating a single AnnouncementRead entity.
type AnnouncementReadUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AnnouncementReadMutation
}
// SetAnnouncementID sets the "announcement_id" field.
func (_u *AnnouncementReadUpdateOne) SetAnnouncementID(v int64) *AnnouncementReadUpdateOne {
_u.mutation.SetAnnouncementID(v)
return _u
}
// SetNillableAnnouncementID sets the "announcement_id" field if the given value is not nil.
func (_u *AnnouncementReadUpdateOne) SetNillableAnnouncementID(v *int64) *AnnouncementReadUpdateOne {
if v != nil {
_u.SetAnnouncementID(*v)
}
return _u
}
// SetUserID sets the "user_id" field.
func (_u *AnnouncementReadUpdateOne) SetUserID(v int64) *AnnouncementReadUpdateOne {
_u.mutation.SetUserID(v)
return _u
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (_u *AnnouncementReadUpdateOne) SetNillableUserID(v *int64) *AnnouncementReadUpdateOne {
if v != nil {
_u.SetUserID(*v)
}
return _u
}
// SetReadAt sets the "read_at" field.
func (_u *AnnouncementReadUpdateOne) SetReadAt(v time.Time) *AnnouncementReadUpdateOne {
_u.mutation.SetReadAt(v)
return _u
}
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
func (_u *AnnouncementReadUpdateOne) SetNillableReadAt(v *time.Time) *AnnouncementReadUpdateOne {
if v != nil {
_u.SetReadAt(*v)
}
return _u
}
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
func (_u *AnnouncementReadUpdateOne) SetAnnouncement(v *Announcement) *AnnouncementReadUpdateOne {
return _u.SetAnnouncementID(v.ID)
}
// SetUser sets the "user" edge to the User entity.
func (_u *AnnouncementReadUpdateOne) SetUser(v *User) *AnnouncementReadUpdateOne {
return _u.SetUserID(v.ID)
}
// Mutation returns the AnnouncementReadMutation object of the builder.
func (_u *AnnouncementReadUpdateOne) Mutation() *AnnouncementReadMutation {
return _u.mutation
}
// ClearAnnouncement clears the "announcement" edge to the Announcement entity.
func (_u *AnnouncementReadUpdateOne) ClearAnnouncement() *AnnouncementReadUpdateOne {
_u.mutation.ClearAnnouncement()
return _u
}
// ClearUser clears the "user" edge to the User entity.
func (_u *AnnouncementReadUpdateOne) ClearUser() *AnnouncementReadUpdateOne {
_u.mutation.ClearUser()
return _u
}
// Where appends a list predicates to the AnnouncementReadUpdate builder.
func (_u *AnnouncementReadUpdateOne) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadUpdateOne {
_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 *AnnouncementReadUpdateOne) Select(field string, fields ...string) *AnnouncementReadUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated AnnouncementRead entity.
func (_u *AnnouncementReadUpdateOne) Save(ctx context.Context) (*AnnouncementRead, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AnnouncementReadUpdateOne) SaveX(ctx context.Context) *AnnouncementRead {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *AnnouncementReadUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AnnouncementReadUpdateOne) 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 *AnnouncementReadUpdateOne) check() error {
if _u.mutation.AnnouncementCleared() && len(_u.mutation.AnnouncementIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.announcement"`)
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.user"`)
}
return nil
}
func (_u *AnnouncementReadUpdateOne) sqlSave(ctx context.Context) (_node *AnnouncementRead, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AnnouncementRead.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, announcementread.FieldID)
for _, f := range fields {
if !announcementread.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != announcementread.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.ReadAt(); ok {
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
}
if _u.mutation.AnnouncementCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.AnnouncementTable,
Columns: []string{announcementread.AnnouncementColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AnnouncementIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.AnnouncementTable,
Columns: []string{announcementread.AnnouncementColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.UserTable,
Columns: []string{announcementread.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: announcementread.UserTable,
Columns: []string{announcementread.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &AnnouncementRead{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{announcementread.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

@@ -36,10 +36,36 @@ type APIKey struct {
GroupID *int64 `json:"group_id,omitempty"` GroupID *int64 `json:"group_id,omitempty"`
// Status holds the value of the "status" field. // Status holds the value of the "status" field.
Status string `json:"status,omitempty"` 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"] // Allowed IPs/CIDRs, e.g. ["192.168.1.100", "10.0.0.0/8"]
IPWhitelist []string `json:"ip_whitelist,omitempty"` IPWhitelist []string `json:"ip_whitelist,omitempty"`
// Blocked IPs/CIDRs // Blocked IPs/CIDRs
IPBlacklist []string `json:"ip_blacklist,omitempty"` IPBlacklist []string `json:"ip_blacklist,omitempty"`
// Quota limit in USD for this API key (0 = unlimited)
Quota float64 `json:"quota,omitempty"`
// Used quota amount in USD
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. // Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the APIKeyQuery when eager-loading is set. // The values are being populated by the APIKeyQuery when eager-loading is set.
Edges APIKeyEdges `json:"edges"` Edges APIKeyEdges `json:"edges"`
@@ -97,11 +123,13 @@ func (*APIKey) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist: case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist:
values[i] = new([]byte) values[i] = new([]byte)
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: case apikey.FieldID, apikey.FieldUserID, apikey.FieldGroupID:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus: case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt: case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt, apikey.FieldWindow5hStart, apikey.FieldWindow1dStart, apikey.FieldWindow7dStart:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
@@ -174,6 +202,13 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
_m.Status = value.String _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: case apikey.FieldIPWhitelist:
if value, ok := values[i].(*[]byte); !ok { if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field ip_whitelist", values[i]) return fmt.Errorf("unexpected type %T for field ip_whitelist", values[i])
@@ -190,6 +225,82 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
return fmt.Errorf("unmarshal field ip_blacklist: %w", err) return fmt.Errorf("unmarshal field ip_blacklist: %w", err)
} }
} }
case apikey.FieldQuota:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field quota", values[i])
} else if value.Valid {
_m.Quota = value.Float64
}
case apikey.FieldQuotaUsed:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field quota_used", values[i])
} else if value.Valid {
_m.QuotaUsed = value.Float64
}
case apikey.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 = 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: default:
_m.selectValues.Set(columns[i], values[i]) _m.selectValues.Set(columns[i], values[i])
} }
@@ -269,11 +380,60 @@ func (_m *APIKey) String() string {
builder.WriteString("status=") builder.WriteString("status=")
builder.WriteString(_m.Status) builder.WriteString(_m.Status)
builder.WriteString(", ") 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("ip_whitelist=")
builder.WriteString(fmt.Sprintf("%v", _m.IPWhitelist)) builder.WriteString(fmt.Sprintf("%v", _m.IPWhitelist))
builder.WriteString(", ") builder.WriteString(", ")
builder.WriteString("ip_blacklist=") builder.WriteString("ip_blacklist=")
builder.WriteString(fmt.Sprintf("%v", _m.IPBlacklist)) builder.WriteString(fmt.Sprintf("%v", _m.IPBlacklist))
builder.WriteString(", ")
builder.WriteString("quota=")
builder.WriteString(fmt.Sprintf("%v", _m.Quota))
builder.WriteString(", ")
builder.WriteString("quota_used=")
builder.WriteString(fmt.Sprintf("%v", _m.QuotaUsed))
builder.WriteString(", ")
if v := _m.ExpiresAt; v != nil {
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(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View File

@@ -31,10 +31,36 @@ const (
FieldGroupID = "group_id" FieldGroupID = "group_id"
// FieldStatus holds the string denoting the status field in the database. // FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status" 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 holds the string denoting the ip_whitelist field in the database.
FieldIPWhitelist = "ip_whitelist" FieldIPWhitelist = "ip_whitelist"
// FieldIPBlacklist holds the string denoting the ip_blacklist field in the database. // FieldIPBlacklist holds the string denoting the ip_blacklist field in the database.
FieldIPBlacklist = "ip_blacklist" FieldIPBlacklist = "ip_blacklist"
// FieldQuota holds the string denoting the quota field in the database.
FieldQuota = "quota"
// FieldQuotaUsed holds the string denoting the quota_used field in the database.
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 holds the string denoting the user edge name in mutations.
EdgeUser = "user" EdgeUser = "user"
// EdgeGroup holds the string denoting the group edge name in mutations. // EdgeGroup holds the string denoting the group edge name in mutations.
@@ -77,8 +103,21 @@ var Columns = []string{
FieldName, FieldName,
FieldGroupID, FieldGroupID,
FieldStatus, FieldStatus,
FieldLastUsedAt,
FieldIPWhitelist, FieldIPWhitelist,
FieldIPBlacklist, 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). // ValidColumn reports if the column name is valid (part of the table columns).
@@ -113,6 +152,22 @@ var (
DefaultStatus string DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save. // StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error StatusValidator func(string) error
// DefaultQuota holds the default value on creation for the "quota" field.
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. // OrderOption defines the ordering options for the APIKey queries.
@@ -163,6 +218,71 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc() 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()
}
// ByQuotaUsed orders the results by the quota_used field.
func ByQuotaUsed(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQuotaUsed, opts...).ToFunc()
}
// ByExpiresAt orders the results by the expires_at field.
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. // ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) { return func(s *sql.Selector) {

View File

@@ -95,6 +95,71 @@ func Status(v string) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldStatus, v)) 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))
}
// QuotaUsed applies equality check predicate on the "quota_used" field. It's identical to QuotaUsedEQ.
func QuotaUsed(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldQuotaUsed, v))
}
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
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. // CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.APIKey { func CreatedAtEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v)) return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v))
@@ -470,6 +535,56 @@ func StatusContainsFold(v string) predicate.APIKey {
return predicate.APIKey(sql.FieldContainsFold(FieldStatus, v)) 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. // IPWhitelistIsNil applies the IsNil predicate on the "ip_whitelist" field.
func IPWhitelistIsNil() predicate.APIKey { func IPWhitelistIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldIPWhitelist)) return predicate.APIKey(sql.FieldIsNull(FieldIPWhitelist))
@@ -490,6 +605,526 @@ func IPBlacklistNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldIPBlacklist)) return predicate.APIKey(sql.FieldNotNull(FieldIPBlacklist))
} }
// QuotaEQ applies the EQ predicate on the "quota" field.
func QuotaEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldQuota, v))
}
// QuotaNEQ applies the NEQ predicate on the "quota" field.
func QuotaNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldQuota, v))
}
// QuotaIn applies the In predicate on the "quota" field.
func QuotaIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldQuota, vs...))
}
// QuotaNotIn applies the NotIn predicate on the "quota" field.
func QuotaNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldQuota, vs...))
}
// QuotaGT applies the GT predicate on the "quota" field.
func QuotaGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldQuota, v))
}
// QuotaGTE applies the GTE predicate on the "quota" field.
func QuotaGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldQuota, v))
}
// QuotaLT applies the LT predicate on the "quota" field.
func QuotaLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldQuota, v))
}
// QuotaLTE applies the LTE predicate on the "quota" field.
func QuotaLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldQuota, v))
}
// QuotaUsedEQ applies the EQ predicate on the "quota_used" field.
func QuotaUsedEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldQuotaUsed, v))
}
// QuotaUsedNEQ applies the NEQ predicate on the "quota_used" field.
func QuotaUsedNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldQuotaUsed, v))
}
// QuotaUsedIn applies the In predicate on the "quota_used" field.
func QuotaUsedIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldQuotaUsed, vs...))
}
// QuotaUsedNotIn applies the NotIn predicate on the "quota_used" field.
func QuotaUsedNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldQuotaUsed, vs...))
}
// QuotaUsedGT applies the GT predicate on the "quota_used" field.
func QuotaUsedGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldQuotaUsed, v))
}
// QuotaUsedGTE applies the GTE predicate on the "quota_used" field.
func QuotaUsedGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldQuotaUsed, v))
}
// QuotaUsedLT applies the LT predicate on the "quota_used" field.
func QuotaUsedLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldQuotaUsed, v))
}
// QuotaUsedLTE applies the LTE predicate on the "quota_used" field.
func QuotaUsedLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldQuotaUsed, v))
}
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
func ExpiresAtEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldExpiresAt, v))
}
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
func ExpiresAtNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldExpiresAt, v))
}
// ExpiresAtIn applies the In predicate on the "expires_at" field.
func ExpiresAtIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldExpiresAt, vs...))
}
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
func ExpiresAtNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldExpiresAt, vs...))
}
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
func ExpiresAtGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldExpiresAt, v))
}
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
func ExpiresAtGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldExpiresAt, v))
}
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
func ExpiresAtLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldExpiresAt, v))
}
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
func ExpiresAtLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldExpiresAt, v))
}
// ExpiresAtIsNil applies the IsNil predicate on the "expires_at" field.
func ExpiresAtIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldExpiresAt))
}
// ExpiresAtNotNil applies the NotNil predicate on the "expires_at" field.
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. // HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.APIKey { func HasUser() predicate.APIKey {
return predicate.APIKey(func(s *sql.Selector) { return predicate.APIKey(func(s *sql.Selector) {

File diff suppressed because it is too large Load Diff

View File

@@ -134,6 +134,26 @@ func (_u *APIKeyUpdate) SetNillableStatus(v *string) *APIKeyUpdate {
return _u 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. // SetIPWhitelist sets the "ip_whitelist" field.
func (_u *APIKeyUpdate) SetIPWhitelist(v []string) *APIKeyUpdate { func (_u *APIKeyUpdate) SetIPWhitelist(v []string) *APIKeyUpdate {
_u.mutation.SetIPWhitelist(v) _u.mutation.SetIPWhitelist(v)
@@ -170,6 +190,254 @@ func (_u *APIKeyUpdate) ClearIPBlacklist() *APIKeyUpdate {
return _u return _u
} }
// SetQuota sets the "quota" field.
func (_u *APIKeyUpdate) SetQuota(v float64) *APIKeyUpdate {
_u.mutation.ResetQuota()
_u.mutation.SetQuota(v)
return _u
}
// SetNillableQuota sets the "quota" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableQuota(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetQuota(*v)
}
return _u
}
// AddQuota adds value to the "quota" field.
func (_u *APIKeyUpdate) AddQuota(v float64) *APIKeyUpdate {
_u.mutation.AddQuota(v)
return _u
}
// SetQuotaUsed sets the "quota_used" field.
func (_u *APIKeyUpdate) SetQuotaUsed(v float64) *APIKeyUpdate {
_u.mutation.ResetQuotaUsed()
_u.mutation.SetQuotaUsed(v)
return _u
}
// SetNillableQuotaUsed sets the "quota_used" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableQuotaUsed(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetQuotaUsed(*v)
}
return _u
}
// AddQuotaUsed adds value to the "quota_used" field.
func (_u *APIKeyUpdate) AddQuotaUsed(v float64) *APIKeyUpdate {
_u.mutation.AddQuotaUsed(v)
return _u
}
// SetExpiresAt sets the "expires_at" field.
func (_u *APIKeyUpdate) SetExpiresAt(v time.Time) *APIKeyUpdate {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableExpiresAt(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (_u *APIKeyUpdate) ClearExpiresAt() *APIKeyUpdate {
_u.mutation.ClearExpiresAt()
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. // SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate { func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate {
return _u.SetUserID(v.ID) return _u.SetUserID(v.ID)
@@ -328,6 +596,12 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Status(); ok { if value, ok := _u.mutation.Status(); ok {
_spec.SetField(apikey.FieldStatus, field.TypeString, value) _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 { if value, ok := _u.mutation.IPWhitelist(); ok {
_spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value) _spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value)
} }
@@ -350,6 +624,78 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.IPBlacklistCleared() { if _u.mutation.IPBlacklistCleared() {
_spec.ClearField(apikey.FieldIPBlacklist, field.TypeJSON) _spec.ClearField(apikey.FieldIPBlacklist, field.TypeJSON)
} }
if value, ok := _u.mutation.Quota(); ok {
_spec.SetField(apikey.FieldQuota, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedQuota(); ok {
_spec.AddField(apikey.FieldQuota, field.TypeFloat64, value)
}
if value, ok := _u.mutation.QuotaUsed(); ok {
_spec.SetField(apikey.FieldQuotaUsed, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedQuotaUsed(); ok {
_spec.AddField(apikey.FieldQuotaUsed, field.TypeFloat64, value)
}
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value)
}
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() { if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@@ -575,6 +921,26 @@ func (_u *APIKeyUpdateOne) SetNillableStatus(v *string) *APIKeyUpdateOne {
return _u 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. // SetIPWhitelist sets the "ip_whitelist" field.
func (_u *APIKeyUpdateOne) SetIPWhitelist(v []string) *APIKeyUpdateOne { func (_u *APIKeyUpdateOne) SetIPWhitelist(v []string) *APIKeyUpdateOne {
_u.mutation.SetIPWhitelist(v) _u.mutation.SetIPWhitelist(v)
@@ -611,6 +977,254 @@ func (_u *APIKeyUpdateOne) ClearIPBlacklist() *APIKeyUpdateOne {
return _u return _u
} }
// SetQuota sets the "quota" field.
func (_u *APIKeyUpdateOne) SetQuota(v float64) *APIKeyUpdateOne {
_u.mutation.ResetQuota()
_u.mutation.SetQuota(v)
return _u
}
// SetNillableQuota sets the "quota" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableQuota(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetQuota(*v)
}
return _u
}
// AddQuota adds value to the "quota" field.
func (_u *APIKeyUpdateOne) AddQuota(v float64) *APIKeyUpdateOne {
_u.mutation.AddQuota(v)
return _u
}
// SetQuotaUsed sets the "quota_used" field.
func (_u *APIKeyUpdateOne) SetQuotaUsed(v float64) *APIKeyUpdateOne {
_u.mutation.ResetQuotaUsed()
_u.mutation.SetQuotaUsed(v)
return _u
}
// SetNillableQuotaUsed sets the "quota_used" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableQuotaUsed(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetQuotaUsed(*v)
}
return _u
}
// AddQuotaUsed adds value to the "quota_used" field.
func (_u *APIKeyUpdateOne) AddQuotaUsed(v float64) *APIKeyUpdateOne {
_u.mutation.AddQuotaUsed(v)
return _u
}
// SetExpiresAt sets the "expires_at" field.
func (_u *APIKeyUpdateOne) SetExpiresAt(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableExpiresAt(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (_u *APIKeyUpdateOne) ClearExpiresAt() *APIKeyUpdateOne {
_u.mutation.ClearExpiresAt()
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. // SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne { func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne {
return _u.SetUserID(v.ID) return _u.SetUserID(v.ID)
@@ -799,6 +1413,12 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
if value, ok := _u.mutation.Status(); ok { if value, ok := _u.mutation.Status(); ok {
_spec.SetField(apikey.FieldStatus, field.TypeString, value) _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 { if value, ok := _u.mutation.IPWhitelist(); ok {
_spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value) _spec.SetField(apikey.FieldIPWhitelist, field.TypeJSON, value)
} }
@@ -821,6 +1441,78 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
if _u.mutation.IPBlacklistCleared() { if _u.mutation.IPBlacklistCleared() {
_spec.ClearField(apikey.FieldIPBlacklist, field.TypeJSON) _spec.ClearField(apikey.FieldIPBlacklist, field.TypeJSON)
} }
if value, ok := _u.mutation.Quota(); ok {
_spec.SetField(apikey.FieldQuota, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedQuota(); ok {
_spec.AddField(apikey.FieldQuota, field.TypeFloat64, value)
}
if value, ok := _u.mutation.QuotaUsed(); ok {
_spec.SetField(apikey.FieldQuotaUsed, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedQuotaUsed(); ok {
_spec.AddField(apikey.FieldQuotaUsed, field.TypeFloat64, value)
}
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value)
}
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() { if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,

266
backend/ent/authidentity.go Normal file
View File

@@ -0,0 +1,266 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AuthIdentity is the model entity for the AuthIdentity schema.
type AuthIdentity 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"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// ProviderType holds the value of the "provider_type" field.
ProviderType string `json:"provider_type,omitempty"`
// ProviderKey holds the value of the "provider_key" field.
ProviderKey string `json:"provider_key,omitempty"`
// ProviderSubject holds the value of the "provider_subject" field.
ProviderSubject string `json:"provider_subject,omitempty"`
// VerifiedAt holds the value of the "verified_at" field.
VerifiedAt *time.Time `json:"verified_at,omitempty"`
// Issuer holds the value of the "issuer" field.
Issuer *string `json:"issuer,omitempty"`
// Metadata holds the value of the "metadata" field.
Metadata map[string]interface{} `json:"metadata,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AuthIdentityQuery when eager-loading is set.
Edges AuthIdentityEdges `json:"edges"`
selectValues sql.SelectValues
}
// AuthIdentityEdges holds the relations/edges for other nodes in the graph.
type AuthIdentityEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// Channels holds the value of the channels edge.
Channels []*AuthIdentityChannel `json:"channels,omitempty"`
// AdoptionDecisions holds the value of the adoption_decisions edge.
AdoptionDecisions []*IdentityAdoptionDecision `json:"adoption_decisions,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [3]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 AuthIdentityEdges) 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"}
}
// ChannelsOrErr returns the Channels value or an error if the edge
// was not loaded in eager-loading.
func (e AuthIdentityEdges) ChannelsOrErr() ([]*AuthIdentityChannel, error) {
if e.loadedTypes[1] {
return e.Channels, nil
}
return nil, &NotLoadedError{edge: "channels"}
}
// AdoptionDecisionsOrErr returns the AdoptionDecisions value or an error if the edge
// was not loaded in eager-loading.
func (e AuthIdentityEdges) AdoptionDecisionsOrErr() ([]*IdentityAdoptionDecision, error) {
if e.loadedTypes[2] {
return e.AdoptionDecisions, nil
}
return nil, &NotLoadedError{edge: "adoption_decisions"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AuthIdentity) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case authidentity.FieldMetadata:
values[i] = new([]byte)
case authidentity.FieldID, authidentity.FieldUserID:
values[i] = new(sql.NullInt64)
case authidentity.FieldProviderType, authidentity.FieldProviderKey, authidentity.FieldProviderSubject, authidentity.FieldIssuer:
values[i] = new(sql.NullString)
case authidentity.FieldCreatedAt, authidentity.FieldUpdatedAt, authidentity.FieldVerifiedAt:
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 AuthIdentity fields.
func (_m *AuthIdentity) 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 authidentity.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 authidentity.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 authidentity.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 authidentity.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 authidentity.FieldProviderType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_type", values[i])
} else if value.Valid {
_m.ProviderType = value.String
}
case authidentity.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 authidentity.FieldProviderSubject:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_subject", values[i])
} else if value.Valid {
_m.ProviderSubject = value.String
}
case authidentity.FieldVerifiedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field verified_at", values[i])
} else if value.Valid {
_m.VerifiedAt = new(time.Time)
*_m.VerifiedAt = value.Time
}
case authidentity.FieldIssuer:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field issuer", values[i])
} else if value.Valid {
_m.Issuer = new(string)
*_m.Issuer = value.String
}
case authidentity.FieldMetadata:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field metadata", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Metadata); err != nil {
return fmt.Errorf("unmarshal field metadata: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the AuthIdentity.
// This includes values selected through modifiers, order, etc.
func (_m *AuthIdentity) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the AuthIdentity entity.
func (_m *AuthIdentity) QueryUser() *UserQuery {
return NewAuthIdentityClient(_m.config).QueryUser(_m)
}
// QueryChannels queries the "channels" edge of the AuthIdentity entity.
func (_m *AuthIdentity) QueryChannels() *AuthIdentityChannelQuery {
return NewAuthIdentityClient(_m.config).QueryChannels(_m)
}
// QueryAdoptionDecisions queries the "adoption_decisions" edge of the AuthIdentity entity.
func (_m *AuthIdentity) QueryAdoptionDecisions() *IdentityAdoptionDecisionQuery {
return NewAuthIdentityClient(_m.config).QueryAdoptionDecisions(_m)
}
// Update returns a builder for updating this AuthIdentity.
// Note that you need to call AuthIdentity.Unwrap() before calling this method if this AuthIdentity
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *AuthIdentity) Update() *AuthIdentityUpdateOne {
return NewAuthIdentityClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the AuthIdentity 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 *AuthIdentity) Unwrap() *AuthIdentity {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: AuthIdentity is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *AuthIdentity) String() string {
var builder strings.Builder
builder.WriteString("AuthIdentity(")
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("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
builder.WriteString("provider_type=")
builder.WriteString(_m.ProviderType)
builder.WriteString(", ")
builder.WriteString("provider_key=")
builder.WriteString(_m.ProviderKey)
builder.WriteString(", ")
builder.WriteString("provider_subject=")
builder.WriteString(_m.ProviderSubject)
builder.WriteString(", ")
if v := _m.VerifiedAt; v != nil {
builder.WriteString("verified_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.Issuer; v != nil {
builder.WriteString("issuer=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("metadata=")
builder.WriteString(fmt.Sprintf("%v", _m.Metadata))
builder.WriteByte(')')
return builder.String()
}
// AuthIdentities is a parsable slice of AuthIdentity.
type AuthIdentities []*AuthIdentity

View File

@@ -0,0 +1,209 @@
// Code generated by ent, DO NOT EDIT.
package authidentity
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the authidentity type in the database.
Label = "auth_identity"
// 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"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldProviderType holds the string denoting the provider_type field in the database.
FieldProviderType = "provider_type"
// FieldProviderKey holds the string denoting the provider_key field in the database.
FieldProviderKey = "provider_key"
// FieldProviderSubject holds the string denoting the provider_subject field in the database.
FieldProviderSubject = "provider_subject"
// FieldVerifiedAt holds the string denoting the verified_at field in the database.
FieldVerifiedAt = "verified_at"
// FieldIssuer holds the string denoting the issuer field in the database.
FieldIssuer = "issuer"
// FieldMetadata holds the string denoting the metadata field in the database.
FieldMetadata = "metadata"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// EdgeChannels holds the string denoting the channels edge name in mutations.
EdgeChannels = "channels"
// EdgeAdoptionDecisions holds the string denoting the adoption_decisions edge name in mutations.
EdgeAdoptionDecisions = "adoption_decisions"
// Table holds the table name of the authidentity in the database.
Table = "auth_identities"
// UserTable is the table that holds the user relation/edge.
UserTable = "auth_identities"
// 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"
// ChannelsTable is the table that holds the channels relation/edge.
ChannelsTable = "auth_identity_channels"
// ChannelsInverseTable is the table name for the AuthIdentityChannel entity.
// It exists in this package in order to avoid circular dependency with the "authidentitychannel" package.
ChannelsInverseTable = "auth_identity_channels"
// ChannelsColumn is the table column denoting the channels relation/edge.
ChannelsColumn = "identity_id"
// AdoptionDecisionsTable is the table that holds the adoption_decisions relation/edge.
AdoptionDecisionsTable = "identity_adoption_decisions"
// AdoptionDecisionsInverseTable is the table name for the IdentityAdoptionDecision entity.
// It exists in this package in order to avoid circular dependency with the "identityadoptiondecision" package.
AdoptionDecisionsInverseTable = "identity_adoption_decisions"
// AdoptionDecisionsColumn is the table column denoting the adoption_decisions relation/edge.
AdoptionDecisionsColumn = "identity_id"
)
// Columns holds all SQL columns for authidentity fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldUserID,
FieldProviderType,
FieldProviderKey,
FieldProviderSubject,
FieldVerifiedAt,
FieldIssuer,
FieldMetadata,
}
// 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
// ProviderTypeValidator is a validator for the "provider_type" field. It is called by the builders before save.
ProviderTypeValidator func(string) error
// ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
ProviderKeyValidator func(string) error
// ProviderSubjectValidator is a validator for the "provider_subject" field. It is called by the builders before save.
ProviderSubjectValidator func(string) error
// DefaultMetadata holds the default value on creation for the "metadata" field.
DefaultMetadata func() map[string]interface{}
)
// OrderOption defines the ordering options for the AuthIdentity 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()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByProviderType orders the results by the provider_type field.
func ByProviderType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderType, opts...).ToFunc()
}
// ByProviderKey orders the results by the provider_key field.
func ByProviderKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderKey, opts...).ToFunc()
}
// ByProviderSubject orders the results by the provider_subject field.
func ByProviderSubject(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderSubject, opts...).ToFunc()
}
// ByVerifiedAt orders the results by the verified_at field.
func ByVerifiedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldVerifiedAt, opts...).ToFunc()
}
// ByIssuer orders the results by the issuer field.
func ByIssuer(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIssuer, 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...))
}
}
// ByChannelsCount orders the results by channels count.
func ByChannelsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newChannelsStep(), opts...)
}
}
// ByChannels orders the results by channels terms.
func ByChannels(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newChannelsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByAdoptionDecisionsCount orders the results by adoption_decisions count.
func ByAdoptionDecisionsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newAdoptionDecisionsStep(), opts...)
}
}
// ByAdoptionDecisions orders the results by adoption_decisions terms.
func ByAdoptionDecisions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAdoptionDecisionsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}
func newChannelsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(ChannelsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChannelsTable, ChannelsColumn),
)
}
func newAdoptionDecisionsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AdoptionDecisionsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AdoptionDecisionsTable, AdoptionDecisionsColumn),
)
}

View File

@@ -0,0 +1,600 @@
// Code generated by ent, DO NOT EDIT.
package authidentity
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.AuthIdentity {
return predicate.AuthIdentity(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.AuthIdentity {
return predicate.AuthIdentity(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.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldUpdatedAt, v))
}
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
func UserID(v int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldUserID, v))
}
// ProviderType applies equality check predicate on the "provider_type" field. It's identical to ProviderTypeEQ.
func ProviderType(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderType, v))
}
// ProviderKey applies equality check predicate on the "provider_key" field. It's identical to ProviderKeyEQ.
func ProviderKey(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderKey, v))
}
// ProviderSubject applies equality check predicate on the "provider_subject" field. It's identical to ProviderSubjectEQ.
func ProviderSubject(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderSubject, v))
}
// VerifiedAt applies equality check predicate on the "verified_at" field. It's identical to VerifiedAtEQ.
func VerifiedAt(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldVerifiedAt, v))
}
// Issuer applies equality check predicate on the "issuer" field. It's identical to IssuerEQ.
func Issuer(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldIssuer, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldUpdatedAt, v))
}
// UserIDEQ applies the EQ predicate on the "user_id" field.
func UserIDEQ(v int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldUserID, v))
}
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
func UserIDNEQ(v int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldUserID, v))
}
// UserIDIn applies the In predicate on the "user_id" field.
func UserIDIn(vs ...int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldUserID, vs...))
}
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
func UserIDNotIn(vs ...int64) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldUserID, vs...))
}
// ProviderTypeEQ applies the EQ predicate on the "provider_type" field.
func ProviderTypeEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderType, v))
}
// ProviderTypeNEQ applies the NEQ predicate on the "provider_type" field.
func ProviderTypeNEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldProviderType, v))
}
// ProviderTypeIn applies the In predicate on the "provider_type" field.
func ProviderTypeIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldProviderType, vs...))
}
// ProviderTypeNotIn applies the NotIn predicate on the "provider_type" field.
func ProviderTypeNotIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldProviderType, vs...))
}
// ProviderTypeGT applies the GT predicate on the "provider_type" field.
func ProviderTypeGT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldProviderType, v))
}
// ProviderTypeGTE applies the GTE predicate on the "provider_type" field.
func ProviderTypeGTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldProviderType, v))
}
// ProviderTypeLT applies the LT predicate on the "provider_type" field.
func ProviderTypeLT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldProviderType, v))
}
// ProviderTypeLTE applies the LTE predicate on the "provider_type" field.
func ProviderTypeLTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldProviderType, v))
}
// ProviderTypeContains applies the Contains predicate on the "provider_type" field.
func ProviderTypeContains(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContains(FieldProviderType, v))
}
// ProviderTypeHasPrefix applies the HasPrefix predicate on the "provider_type" field.
func ProviderTypeHasPrefix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasPrefix(FieldProviderType, v))
}
// ProviderTypeHasSuffix applies the HasSuffix predicate on the "provider_type" field.
func ProviderTypeHasSuffix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasSuffix(FieldProviderType, v))
}
// ProviderTypeEqualFold applies the EqualFold predicate on the "provider_type" field.
func ProviderTypeEqualFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEqualFold(FieldProviderType, v))
}
// ProviderTypeContainsFold applies the ContainsFold predicate on the "provider_type" field.
func ProviderTypeContainsFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContainsFold(FieldProviderType, v))
}
// ProviderKeyEQ applies the EQ predicate on the "provider_key" field.
func ProviderKeyEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderKey, v))
}
// ProviderKeyNEQ applies the NEQ predicate on the "provider_key" field.
func ProviderKeyNEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldProviderKey, v))
}
// ProviderKeyIn applies the In predicate on the "provider_key" field.
func ProviderKeyIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldProviderKey, vs...))
}
// ProviderKeyNotIn applies the NotIn predicate on the "provider_key" field.
func ProviderKeyNotIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldProviderKey, vs...))
}
// ProviderKeyGT applies the GT predicate on the "provider_key" field.
func ProviderKeyGT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldProviderKey, v))
}
// ProviderKeyGTE applies the GTE predicate on the "provider_key" field.
func ProviderKeyGTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldProviderKey, v))
}
// ProviderKeyLT applies the LT predicate on the "provider_key" field.
func ProviderKeyLT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldProviderKey, v))
}
// ProviderKeyLTE applies the LTE predicate on the "provider_key" field.
func ProviderKeyLTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldProviderKey, v))
}
// ProviderKeyContains applies the Contains predicate on the "provider_key" field.
func ProviderKeyContains(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContains(FieldProviderKey, v))
}
// ProviderKeyHasPrefix applies the HasPrefix predicate on the "provider_key" field.
func ProviderKeyHasPrefix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasPrefix(FieldProviderKey, v))
}
// ProviderKeyHasSuffix applies the HasSuffix predicate on the "provider_key" field.
func ProviderKeyHasSuffix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasSuffix(FieldProviderKey, v))
}
// ProviderKeyEqualFold applies the EqualFold predicate on the "provider_key" field.
func ProviderKeyEqualFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEqualFold(FieldProviderKey, v))
}
// ProviderKeyContainsFold applies the ContainsFold predicate on the "provider_key" field.
func ProviderKeyContainsFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContainsFold(FieldProviderKey, v))
}
// ProviderSubjectEQ applies the EQ predicate on the "provider_subject" field.
func ProviderSubjectEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldProviderSubject, v))
}
// ProviderSubjectNEQ applies the NEQ predicate on the "provider_subject" field.
func ProviderSubjectNEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldProviderSubject, v))
}
// ProviderSubjectIn applies the In predicate on the "provider_subject" field.
func ProviderSubjectIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldProviderSubject, vs...))
}
// ProviderSubjectNotIn applies the NotIn predicate on the "provider_subject" field.
func ProviderSubjectNotIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldProviderSubject, vs...))
}
// ProviderSubjectGT applies the GT predicate on the "provider_subject" field.
func ProviderSubjectGT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldProviderSubject, v))
}
// ProviderSubjectGTE applies the GTE predicate on the "provider_subject" field.
func ProviderSubjectGTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldProviderSubject, v))
}
// ProviderSubjectLT applies the LT predicate on the "provider_subject" field.
func ProviderSubjectLT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldProviderSubject, v))
}
// ProviderSubjectLTE applies the LTE predicate on the "provider_subject" field.
func ProviderSubjectLTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldProviderSubject, v))
}
// ProviderSubjectContains applies the Contains predicate on the "provider_subject" field.
func ProviderSubjectContains(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContains(FieldProviderSubject, v))
}
// ProviderSubjectHasPrefix applies the HasPrefix predicate on the "provider_subject" field.
func ProviderSubjectHasPrefix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasPrefix(FieldProviderSubject, v))
}
// ProviderSubjectHasSuffix applies the HasSuffix predicate on the "provider_subject" field.
func ProviderSubjectHasSuffix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasSuffix(FieldProviderSubject, v))
}
// ProviderSubjectEqualFold applies the EqualFold predicate on the "provider_subject" field.
func ProviderSubjectEqualFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEqualFold(FieldProviderSubject, v))
}
// ProviderSubjectContainsFold applies the ContainsFold predicate on the "provider_subject" field.
func ProviderSubjectContainsFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContainsFold(FieldProviderSubject, v))
}
// VerifiedAtEQ applies the EQ predicate on the "verified_at" field.
func VerifiedAtEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldVerifiedAt, v))
}
// VerifiedAtNEQ applies the NEQ predicate on the "verified_at" field.
func VerifiedAtNEQ(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldVerifiedAt, v))
}
// VerifiedAtIn applies the In predicate on the "verified_at" field.
func VerifiedAtIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldVerifiedAt, vs...))
}
// VerifiedAtNotIn applies the NotIn predicate on the "verified_at" field.
func VerifiedAtNotIn(vs ...time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldVerifiedAt, vs...))
}
// VerifiedAtGT applies the GT predicate on the "verified_at" field.
func VerifiedAtGT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldVerifiedAt, v))
}
// VerifiedAtGTE applies the GTE predicate on the "verified_at" field.
func VerifiedAtGTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldVerifiedAt, v))
}
// VerifiedAtLT applies the LT predicate on the "verified_at" field.
func VerifiedAtLT(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldVerifiedAt, v))
}
// VerifiedAtLTE applies the LTE predicate on the "verified_at" field.
func VerifiedAtLTE(v time.Time) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldVerifiedAt, v))
}
// VerifiedAtIsNil applies the IsNil predicate on the "verified_at" field.
func VerifiedAtIsNil() predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIsNull(FieldVerifiedAt))
}
// VerifiedAtNotNil applies the NotNil predicate on the "verified_at" field.
func VerifiedAtNotNil() predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotNull(FieldVerifiedAt))
}
// IssuerEQ applies the EQ predicate on the "issuer" field.
func IssuerEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEQ(FieldIssuer, v))
}
// IssuerNEQ applies the NEQ predicate on the "issuer" field.
func IssuerNEQ(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNEQ(FieldIssuer, v))
}
// IssuerIn applies the In predicate on the "issuer" field.
func IssuerIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIn(FieldIssuer, vs...))
}
// IssuerNotIn applies the NotIn predicate on the "issuer" field.
func IssuerNotIn(vs ...string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotIn(FieldIssuer, vs...))
}
// IssuerGT applies the GT predicate on the "issuer" field.
func IssuerGT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGT(FieldIssuer, v))
}
// IssuerGTE applies the GTE predicate on the "issuer" field.
func IssuerGTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldGTE(FieldIssuer, v))
}
// IssuerLT applies the LT predicate on the "issuer" field.
func IssuerLT(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLT(FieldIssuer, v))
}
// IssuerLTE applies the LTE predicate on the "issuer" field.
func IssuerLTE(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldLTE(FieldIssuer, v))
}
// IssuerContains applies the Contains predicate on the "issuer" field.
func IssuerContains(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContains(FieldIssuer, v))
}
// IssuerHasPrefix applies the HasPrefix predicate on the "issuer" field.
func IssuerHasPrefix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasPrefix(FieldIssuer, v))
}
// IssuerHasSuffix applies the HasSuffix predicate on the "issuer" field.
func IssuerHasSuffix(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldHasSuffix(FieldIssuer, v))
}
// IssuerIsNil applies the IsNil predicate on the "issuer" field.
func IssuerIsNil() predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldIsNull(FieldIssuer))
}
// IssuerNotNil applies the NotNil predicate on the "issuer" field.
func IssuerNotNil() predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldNotNull(FieldIssuer))
}
// IssuerEqualFold applies the EqualFold predicate on the "issuer" field.
func IssuerEqualFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldEqualFold(FieldIssuer, v))
}
// IssuerContainsFold applies the ContainsFold predicate on the "issuer" field.
func IssuerContainsFold(v string) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.FieldContainsFold(FieldIssuer, v))
}
// HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
func HasUserWith(preds ...predicate.User) predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := newUserStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasChannels applies the HasEdge predicate on the "channels" edge.
func HasChannels() predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, ChannelsTable, ChannelsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasChannelsWith applies the HasEdge predicate on the "channels" edge with a given conditions (other predicates).
func HasChannelsWith(preds ...predicate.AuthIdentityChannel) predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := newChannelsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasAdoptionDecisions applies the HasEdge predicate on the "adoption_decisions" edge.
func HasAdoptionDecisions() predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, AdoptionDecisionsTable, AdoptionDecisionsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasAdoptionDecisionsWith applies the HasEdge predicate on the "adoption_decisions" edge with a given conditions (other predicates).
func HasAdoptionDecisionsWith(preds ...predicate.IdentityAdoptionDecision) predicate.AuthIdentity {
return predicate.AuthIdentity(func(s *sql.Selector) {
step := newAdoptionDecisionsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.AuthIdentity) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AuthIdentity) predicate.AuthIdentity {
return predicate.AuthIdentity(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.AuthIdentity) predicate.AuthIdentity {
return predicate.AuthIdentity(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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AuthIdentityDelete is the builder for deleting a AuthIdentity entity.
type AuthIdentityDelete struct {
config
hooks []Hook
mutation *AuthIdentityMutation
}
// Where appends a list predicates to the AuthIdentityDelete builder.
func (_d *AuthIdentityDelete) Where(ps ...predicate.AuthIdentity) *AuthIdentityDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AuthIdentityDelete) 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 *AuthIdentityDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *AuthIdentityDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(authidentity.Table, sqlgraph.NewFieldSpec(authidentity.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
}
// AuthIdentityDeleteOne is the builder for deleting a single AuthIdentity entity.
type AuthIdentityDeleteOne struct {
_d *AuthIdentityDelete
}
// Where appends a list predicates to the AuthIdentityDelete builder.
func (_d *AuthIdentityDeleteOne) Where(ps ...predicate.AuthIdentity) *AuthIdentityDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *AuthIdentityDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{authidentity.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AuthIdentityDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,797 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"database/sql/driver"
"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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/identityadoptiondecision"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AuthIdentityQuery is the builder for querying AuthIdentity entities.
type AuthIdentityQuery struct {
config
ctx *QueryContext
order []authidentity.OrderOption
inters []Interceptor
predicates []predicate.AuthIdentity
withUser *UserQuery
withChannels *AuthIdentityChannelQuery
withAdoptionDecisions *IdentityAdoptionDecisionQuery
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 AuthIdentityQuery builder.
func (_q *AuthIdentityQuery) Where(ps ...predicate.AuthIdentity) *AuthIdentityQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *AuthIdentityQuery) Limit(limit int) *AuthIdentityQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *AuthIdentityQuery) Offset(offset int) *AuthIdentityQuery {
_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 *AuthIdentityQuery) Unique(unique bool) *AuthIdentityQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *AuthIdentityQuery) Order(o ...authidentity.OrderOption) *AuthIdentityQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryUser chains the current query on the "user" edge.
func (_q *AuthIdentityQuery) 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(authidentity.Table, authidentity.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, authidentity.UserTable, authidentity.UserColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryChannels chains the current query on the "channels" edge.
func (_q *AuthIdentityQuery) QueryChannels() *AuthIdentityChannelQuery {
query := (&AuthIdentityChannelClient{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(authidentity.Table, authidentity.FieldID, selector),
sqlgraph.To(authidentitychannel.Table, authidentitychannel.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, authidentity.ChannelsTable, authidentity.ChannelsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryAdoptionDecisions chains the current query on the "adoption_decisions" edge.
func (_q *AuthIdentityQuery) QueryAdoptionDecisions() *IdentityAdoptionDecisionQuery {
query := (&IdentityAdoptionDecisionClient{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(authidentity.Table, authidentity.FieldID, selector),
sqlgraph.To(identityadoptiondecision.Table, identityadoptiondecision.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, authidentity.AdoptionDecisionsTable, authidentity.AdoptionDecisionsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first AuthIdentity entity from the query.
// Returns a *NotFoundError when no AuthIdentity was found.
func (_q *AuthIdentityQuery) First(ctx context.Context) (*AuthIdentity, 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{authidentity.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *AuthIdentityQuery) FirstX(ctx context.Context) *AuthIdentity {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first AuthIdentity ID from the query.
// Returns a *NotFoundError when no AuthIdentity ID was found.
func (_q *AuthIdentityQuery) 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{authidentity.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *AuthIdentityQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single AuthIdentity entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one AuthIdentity entity is found.
// Returns a *NotFoundError when no AuthIdentity entities are found.
func (_q *AuthIdentityQuery) Only(ctx context.Context) (*AuthIdentity, 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{authidentity.Label}
default:
return nil, &NotSingularError{authidentity.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *AuthIdentityQuery) OnlyX(ctx context.Context) *AuthIdentity {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only AuthIdentity ID in the query.
// Returns a *NotSingularError when more than one AuthIdentity ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *AuthIdentityQuery) 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{authidentity.Label}
default:
err = &NotSingularError{authidentity.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *AuthIdentityQuery) 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 AuthIdentities.
func (_q *AuthIdentityQuery) All(ctx context.Context) ([]*AuthIdentity, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AuthIdentity, *AuthIdentityQuery]()
return withInterceptors[[]*AuthIdentity](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *AuthIdentityQuery) AllX(ctx context.Context) []*AuthIdentity {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of AuthIdentity IDs.
func (_q *AuthIdentityQuery) 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(authidentity.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *AuthIdentityQuery) 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 *AuthIdentityQuery) 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[*AuthIdentityQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *AuthIdentityQuery) 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 *AuthIdentityQuery) 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 *AuthIdentityQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AuthIdentityQuery 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 *AuthIdentityQuery) Clone() *AuthIdentityQuery {
if _q == nil {
return nil
}
return &AuthIdentityQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]authidentity.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.AuthIdentity{}, _q.predicates...),
withUser: _q.withUser.Clone(),
withChannels: _q.withChannels.Clone(),
withAdoptionDecisions: _q.withAdoptionDecisions.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 *AuthIdentityQuery) WithUser(opts ...func(*UserQuery)) *AuthIdentityQuery {
query := (&UserClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withUser = query
return _q
}
// WithChannels tells the query-builder to eager-load the nodes that are connected to
// the "channels" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AuthIdentityQuery) WithChannels(opts ...func(*AuthIdentityChannelQuery)) *AuthIdentityQuery {
query := (&AuthIdentityChannelClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withChannels = query
return _q
}
// WithAdoptionDecisions tells the query-builder to eager-load the nodes that are connected to
// the "adoption_decisions" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AuthIdentityQuery) WithAdoptionDecisions(opts ...func(*IdentityAdoptionDecisionQuery)) *AuthIdentityQuery {
query := (&IdentityAdoptionDecisionClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withAdoptionDecisions = 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 {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.AuthIdentity.Query().
// GroupBy(authidentity.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *AuthIdentityQuery) GroupBy(field string, fields ...string) *AuthIdentityGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuthIdentityGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = authidentity.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.AuthIdentity.Query().
// Select(authidentity.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *AuthIdentityQuery) Select(fields ...string) *AuthIdentitySelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &AuthIdentitySelect{AuthIdentityQuery: _q}
sbuild.label = authidentity.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AuthIdentitySelect configured with the given aggregations.
func (_q *AuthIdentityQuery) Aggregate(fns ...AggregateFunc) *AuthIdentitySelect {
return _q.Select().Aggregate(fns...)
}
func (_q *AuthIdentityQuery) 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 !authidentity.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 *AuthIdentityQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthIdentity, error) {
var (
nodes = []*AuthIdentity{}
_spec = _q.querySpec()
loadedTypes = [3]bool{
_q.withUser != nil,
_q.withChannels != nil,
_q.withAdoptionDecisions != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*AuthIdentity).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &AuthIdentity{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 *AuthIdentity, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
if query := _q.withChannels; query != nil {
if err := _q.loadChannels(ctx, query, nodes,
func(n *AuthIdentity) { n.Edges.Channels = []*AuthIdentityChannel{} },
func(n *AuthIdentity, e *AuthIdentityChannel) { n.Edges.Channels = append(n.Edges.Channels, e) }); err != nil {
return nil, err
}
}
if query := _q.withAdoptionDecisions; query != nil {
if err := _q.loadAdoptionDecisions(ctx, query, nodes,
func(n *AuthIdentity) { n.Edges.AdoptionDecisions = []*IdentityAdoptionDecision{} },
func(n *AuthIdentity, e *IdentityAdoptionDecision) {
n.Edges.AdoptionDecisions = append(n.Edges.AdoptionDecisions, e)
}); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *AuthIdentityQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*AuthIdentity, init func(*AuthIdentity), assign func(*AuthIdentity, *User)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*AuthIdentity)
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 *AuthIdentityQuery) loadChannels(ctx context.Context, query *AuthIdentityChannelQuery, nodes []*AuthIdentity, init func(*AuthIdentity), assign func(*AuthIdentity, *AuthIdentityChannel)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*AuthIdentity)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(authidentitychannel.FieldIdentityID)
}
query.Where(predicate.AuthIdentityChannel(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(authidentity.ChannelsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.IdentityID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "identity_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *AuthIdentityQuery) loadAdoptionDecisions(ctx context.Context, query *IdentityAdoptionDecisionQuery, nodes []*AuthIdentity, init func(*AuthIdentity), assign func(*AuthIdentity, *IdentityAdoptionDecision)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*AuthIdentity)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(identityadoptiondecision.FieldIdentityID)
}
query.Where(predicate.IdentityAdoptionDecision(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(authidentity.AdoptionDecisionsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.IdentityID
if fk == nil {
return fmt.Errorf(`foreign-key "identity_id" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "identity_id" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *AuthIdentityQuery) 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 *AuthIdentityQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(authidentity.Table, authidentity.Columns, sqlgraph.NewFieldSpec(authidentity.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, authidentity.FieldID)
for i := range fields {
if fields[i] != authidentity.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withUser != nil {
_spec.Node.AddColumnOnce(authidentity.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 *AuthIdentityQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(authidentity.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = authidentity.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 *AuthIdentityQuery) ForUpdate(opts ...sql.LockOption) *AuthIdentityQuery {
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 *AuthIdentityQuery) ForShare(opts ...sql.LockOption) *AuthIdentityQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// AuthIdentityGroupBy is the group-by builder for AuthIdentity entities.
type AuthIdentityGroupBy struct {
selector
build *AuthIdentityQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *AuthIdentityGroupBy) Aggregate(fns ...AggregateFunc) *AuthIdentityGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *AuthIdentityGroupBy) 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[*AuthIdentityQuery, *AuthIdentityGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *AuthIdentityGroupBy) sqlScan(ctx context.Context, root *AuthIdentityQuery, 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)
}
// AuthIdentitySelect is the builder for selecting fields of AuthIdentity entities.
type AuthIdentitySelect struct {
*AuthIdentityQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *AuthIdentitySelect) Aggregate(fns ...AggregateFunc) *AuthIdentitySelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *AuthIdentitySelect) 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[*AuthIdentityQuery, *AuthIdentitySelect](ctx, _s.AuthIdentityQuery, _s, _s.inters, v)
}
func (_s *AuthIdentitySelect) sqlScan(ctx context.Context, root *AuthIdentityQuery, 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,923 @@
// 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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/identityadoptiondecision"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// AuthIdentityUpdate is the builder for updating AuthIdentity entities.
type AuthIdentityUpdate struct {
config
hooks []Hook
mutation *AuthIdentityMutation
}
// Where appends a list predicates to the AuthIdentityUpdate builder.
func (_u *AuthIdentityUpdate) Where(ps ...predicate.AuthIdentity) *AuthIdentityUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AuthIdentityUpdate) SetUpdatedAt(v time.Time) *AuthIdentityUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetUserID sets the "user_id" field.
func (_u *AuthIdentityUpdate) SetUserID(v int64) *AuthIdentityUpdate {
_u.mutation.SetUserID(v)
return _u
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableUserID(v *int64) *AuthIdentityUpdate {
if v != nil {
_u.SetUserID(*v)
}
return _u
}
// SetProviderType sets the "provider_type" field.
func (_u *AuthIdentityUpdate) SetProviderType(v string) *AuthIdentityUpdate {
_u.mutation.SetProviderType(v)
return _u
}
// SetNillableProviderType sets the "provider_type" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableProviderType(v *string) *AuthIdentityUpdate {
if v != nil {
_u.SetProviderType(*v)
}
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *AuthIdentityUpdate) SetProviderKey(v string) *AuthIdentityUpdate {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableProviderKey(v *string) *AuthIdentityUpdate {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetProviderSubject sets the "provider_subject" field.
func (_u *AuthIdentityUpdate) SetProviderSubject(v string) *AuthIdentityUpdate {
_u.mutation.SetProviderSubject(v)
return _u
}
// SetNillableProviderSubject sets the "provider_subject" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableProviderSubject(v *string) *AuthIdentityUpdate {
if v != nil {
_u.SetProviderSubject(*v)
}
return _u
}
// SetVerifiedAt sets the "verified_at" field.
func (_u *AuthIdentityUpdate) SetVerifiedAt(v time.Time) *AuthIdentityUpdate {
_u.mutation.SetVerifiedAt(v)
return _u
}
// SetNillableVerifiedAt sets the "verified_at" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableVerifiedAt(v *time.Time) *AuthIdentityUpdate {
if v != nil {
_u.SetVerifiedAt(*v)
}
return _u
}
// ClearVerifiedAt clears the value of the "verified_at" field.
func (_u *AuthIdentityUpdate) ClearVerifiedAt() *AuthIdentityUpdate {
_u.mutation.ClearVerifiedAt()
return _u
}
// SetIssuer sets the "issuer" field.
func (_u *AuthIdentityUpdate) SetIssuer(v string) *AuthIdentityUpdate {
_u.mutation.SetIssuer(v)
return _u
}
// SetNillableIssuer sets the "issuer" field if the given value is not nil.
func (_u *AuthIdentityUpdate) SetNillableIssuer(v *string) *AuthIdentityUpdate {
if v != nil {
_u.SetIssuer(*v)
}
return _u
}
// ClearIssuer clears the value of the "issuer" field.
func (_u *AuthIdentityUpdate) ClearIssuer() *AuthIdentityUpdate {
_u.mutation.ClearIssuer()
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuthIdentityUpdate) SetMetadata(v map[string]interface{}) *AuthIdentityUpdate {
_u.mutation.SetMetadata(v)
return _u
}
// SetUser sets the "user" edge to the User entity.
func (_u *AuthIdentityUpdate) SetUser(v *User) *AuthIdentityUpdate {
return _u.SetUserID(v.ID)
}
// AddChannelIDs adds the "channels" edge to the AuthIdentityChannel entity by IDs.
func (_u *AuthIdentityUpdate) AddChannelIDs(ids ...int64) *AuthIdentityUpdate {
_u.mutation.AddChannelIDs(ids...)
return _u
}
// AddChannels adds the "channels" edges to the AuthIdentityChannel entity.
func (_u *AuthIdentityUpdate) AddChannels(v ...*AuthIdentityChannel) *AuthIdentityUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddChannelIDs(ids...)
}
// AddAdoptionDecisionIDs adds the "adoption_decisions" edge to the IdentityAdoptionDecision entity by IDs.
func (_u *AuthIdentityUpdate) AddAdoptionDecisionIDs(ids ...int64) *AuthIdentityUpdate {
_u.mutation.AddAdoptionDecisionIDs(ids...)
return _u
}
// AddAdoptionDecisions adds the "adoption_decisions" edges to the IdentityAdoptionDecision entity.
func (_u *AuthIdentityUpdate) AddAdoptionDecisions(v ...*IdentityAdoptionDecision) *AuthIdentityUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddAdoptionDecisionIDs(ids...)
}
// Mutation returns the AuthIdentityMutation object of the builder.
func (_u *AuthIdentityUpdate) Mutation() *AuthIdentityMutation {
return _u.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (_u *AuthIdentityUpdate) ClearUser() *AuthIdentityUpdate {
_u.mutation.ClearUser()
return _u
}
// ClearChannels clears all "channels" edges to the AuthIdentityChannel entity.
func (_u *AuthIdentityUpdate) ClearChannels() *AuthIdentityUpdate {
_u.mutation.ClearChannels()
return _u
}
// RemoveChannelIDs removes the "channels" edge to AuthIdentityChannel entities by IDs.
func (_u *AuthIdentityUpdate) RemoveChannelIDs(ids ...int64) *AuthIdentityUpdate {
_u.mutation.RemoveChannelIDs(ids...)
return _u
}
// RemoveChannels removes "channels" edges to AuthIdentityChannel entities.
func (_u *AuthIdentityUpdate) RemoveChannels(v ...*AuthIdentityChannel) *AuthIdentityUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveChannelIDs(ids...)
}
// ClearAdoptionDecisions clears all "adoption_decisions" edges to the IdentityAdoptionDecision entity.
func (_u *AuthIdentityUpdate) ClearAdoptionDecisions() *AuthIdentityUpdate {
_u.mutation.ClearAdoptionDecisions()
return _u
}
// RemoveAdoptionDecisionIDs removes the "adoption_decisions" edge to IdentityAdoptionDecision entities by IDs.
func (_u *AuthIdentityUpdate) RemoveAdoptionDecisionIDs(ids ...int64) *AuthIdentityUpdate {
_u.mutation.RemoveAdoptionDecisionIDs(ids...)
return _u
}
// RemoveAdoptionDecisions removes "adoption_decisions" edges to IdentityAdoptionDecision entities.
func (_u *AuthIdentityUpdate) RemoveAdoptionDecisions(v ...*IdentityAdoptionDecision) *AuthIdentityUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveAdoptionDecisionIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *AuthIdentityUpdate) 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 *AuthIdentityUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *AuthIdentityUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuthIdentityUpdate) 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 *AuthIdentityUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := authidentity.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuthIdentityUpdate) check() error {
if v, ok := _u.mutation.ProviderType(); ok {
if err := authidentity.ProviderTypeValidator(v); err != nil {
return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_type": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderKey(); ok {
if err := authidentity.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderSubject(); ok {
if err := authidentity.ProviderSubjectValidator(v); err != nil {
return &ValidationError{Name: "provider_subject", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_subject": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AuthIdentity.user"`)
}
return nil
}
func (_u *AuthIdentityUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(authidentity.Table, authidentity.Columns, sqlgraph.NewFieldSpec(authidentity.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(authidentity.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.ProviderType(); ok {
_spec.SetField(authidentity.FieldProviderType, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(authidentity.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderSubject(); ok {
_spec.SetField(authidentity.FieldProviderSubject, field.TypeString, value)
}
if value, ok := _u.mutation.VerifiedAt(); ok {
_spec.SetField(authidentity.FieldVerifiedAt, field.TypeTime, value)
}
if _u.mutation.VerifiedAtCleared() {
_spec.ClearField(authidentity.FieldVerifiedAt, field.TypeTime)
}
if value, ok := _u.mutation.Issuer(); ok {
_spec.SetField(authidentity.FieldIssuer, field.TypeString, value)
}
if _u.mutation.IssuerCleared() {
_spec.ClearField(authidentity.FieldIssuer, field.TypeString)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(authidentity.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentity.UserTable,
Columns: []string{authidentity.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentity.UserTable,
Columns: []string{authidentity.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ChannelsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedChannelsIDs(); len(nodes) > 0 && !_u.mutation.ChannelsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ChannelsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.AdoptionDecisionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedAdoptionDecisionsIDs(); len(nodes) > 0 && !_u.mutation.AdoptionDecisionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AdoptionDecisionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{authidentity.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// AuthIdentityUpdateOne is the builder for updating a single AuthIdentity entity.
type AuthIdentityUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AuthIdentityMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AuthIdentityUpdateOne) SetUpdatedAt(v time.Time) *AuthIdentityUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetUserID sets the "user_id" field.
func (_u *AuthIdentityUpdateOne) SetUserID(v int64) *AuthIdentityUpdateOne {
_u.mutation.SetUserID(v)
return _u
}
// SetNillableUserID sets the "user_id" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableUserID(v *int64) *AuthIdentityUpdateOne {
if v != nil {
_u.SetUserID(*v)
}
return _u
}
// SetProviderType sets the "provider_type" field.
func (_u *AuthIdentityUpdateOne) SetProviderType(v string) *AuthIdentityUpdateOne {
_u.mutation.SetProviderType(v)
return _u
}
// SetNillableProviderType sets the "provider_type" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableProviderType(v *string) *AuthIdentityUpdateOne {
if v != nil {
_u.SetProviderType(*v)
}
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *AuthIdentityUpdateOne) SetProviderKey(v string) *AuthIdentityUpdateOne {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableProviderKey(v *string) *AuthIdentityUpdateOne {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetProviderSubject sets the "provider_subject" field.
func (_u *AuthIdentityUpdateOne) SetProviderSubject(v string) *AuthIdentityUpdateOne {
_u.mutation.SetProviderSubject(v)
return _u
}
// SetNillableProviderSubject sets the "provider_subject" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableProviderSubject(v *string) *AuthIdentityUpdateOne {
if v != nil {
_u.SetProviderSubject(*v)
}
return _u
}
// SetVerifiedAt sets the "verified_at" field.
func (_u *AuthIdentityUpdateOne) SetVerifiedAt(v time.Time) *AuthIdentityUpdateOne {
_u.mutation.SetVerifiedAt(v)
return _u
}
// SetNillableVerifiedAt sets the "verified_at" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableVerifiedAt(v *time.Time) *AuthIdentityUpdateOne {
if v != nil {
_u.SetVerifiedAt(*v)
}
return _u
}
// ClearVerifiedAt clears the value of the "verified_at" field.
func (_u *AuthIdentityUpdateOne) ClearVerifiedAt() *AuthIdentityUpdateOne {
_u.mutation.ClearVerifiedAt()
return _u
}
// SetIssuer sets the "issuer" field.
func (_u *AuthIdentityUpdateOne) SetIssuer(v string) *AuthIdentityUpdateOne {
_u.mutation.SetIssuer(v)
return _u
}
// SetNillableIssuer sets the "issuer" field if the given value is not nil.
func (_u *AuthIdentityUpdateOne) SetNillableIssuer(v *string) *AuthIdentityUpdateOne {
if v != nil {
_u.SetIssuer(*v)
}
return _u
}
// ClearIssuer clears the value of the "issuer" field.
func (_u *AuthIdentityUpdateOne) ClearIssuer() *AuthIdentityUpdateOne {
_u.mutation.ClearIssuer()
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuthIdentityUpdateOne) SetMetadata(v map[string]interface{}) *AuthIdentityUpdateOne {
_u.mutation.SetMetadata(v)
return _u
}
// SetUser sets the "user" edge to the User entity.
func (_u *AuthIdentityUpdateOne) SetUser(v *User) *AuthIdentityUpdateOne {
return _u.SetUserID(v.ID)
}
// AddChannelIDs adds the "channels" edge to the AuthIdentityChannel entity by IDs.
func (_u *AuthIdentityUpdateOne) AddChannelIDs(ids ...int64) *AuthIdentityUpdateOne {
_u.mutation.AddChannelIDs(ids...)
return _u
}
// AddChannels adds the "channels" edges to the AuthIdentityChannel entity.
func (_u *AuthIdentityUpdateOne) AddChannels(v ...*AuthIdentityChannel) *AuthIdentityUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddChannelIDs(ids...)
}
// AddAdoptionDecisionIDs adds the "adoption_decisions" edge to the IdentityAdoptionDecision entity by IDs.
func (_u *AuthIdentityUpdateOne) AddAdoptionDecisionIDs(ids ...int64) *AuthIdentityUpdateOne {
_u.mutation.AddAdoptionDecisionIDs(ids...)
return _u
}
// AddAdoptionDecisions adds the "adoption_decisions" edges to the IdentityAdoptionDecision entity.
func (_u *AuthIdentityUpdateOne) AddAdoptionDecisions(v ...*IdentityAdoptionDecision) *AuthIdentityUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddAdoptionDecisionIDs(ids...)
}
// Mutation returns the AuthIdentityMutation object of the builder.
func (_u *AuthIdentityUpdateOne) Mutation() *AuthIdentityMutation {
return _u.mutation
}
// ClearUser clears the "user" edge to the User entity.
func (_u *AuthIdentityUpdateOne) ClearUser() *AuthIdentityUpdateOne {
_u.mutation.ClearUser()
return _u
}
// ClearChannels clears all "channels" edges to the AuthIdentityChannel entity.
func (_u *AuthIdentityUpdateOne) ClearChannels() *AuthIdentityUpdateOne {
_u.mutation.ClearChannels()
return _u
}
// RemoveChannelIDs removes the "channels" edge to AuthIdentityChannel entities by IDs.
func (_u *AuthIdentityUpdateOne) RemoveChannelIDs(ids ...int64) *AuthIdentityUpdateOne {
_u.mutation.RemoveChannelIDs(ids...)
return _u
}
// RemoveChannels removes "channels" edges to AuthIdentityChannel entities.
func (_u *AuthIdentityUpdateOne) RemoveChannels(v ...*AuthIdentityChannel) *AuthIdentityUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveChannelIDs(ids...)
}
// ClearAdoptionDecisions clears all "adoption_decisions" edges to the IdentityAdoptionDecision entity.
func (_u *AuthIdentityUpdateOne) ClearAdoptionDecisions() *AuthIdentityUpdateOne {
_u.mutation.ClearAdoptionDecisions()
return _u
}
// RemoveAdoptionDecisionIDs removes the "adoption_decisions" edge to IdentityAdoptionDecision entities by IDs.
func (_u *AuthIdentityUpdateOne) RemoveAdoptionDecisionIDs(ids ...int64) *AuthIdentityUpdateOne {
_u.mutation.RemoveAdoptionDecisionIDs(ids...)
return _u
}
// RemoveAdoptionDecisions removes "adoption_decisions" edges to IdentityAdoptionDecision entities.
func (_u *AuthIdentityUpdateOne) RemoveAdoptionDecisions(v ...*IdentityAdoptionDecision) *AuthIdentityUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveAdoptionDecisionIDs(ids...)
}
// Where appends a list predicates to the AuthIdentityUpdate builder.
func (_u *AuthIdentityUpdateOne) Where(ps ...predicate.AuthIdentity) *AuthIdentityUpdateOne {
_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 *AuthIdentityUpdateOne) Select(field string, fields ...string) *AuthIdentityUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated AuthIdentity entity.
func (_u *AuthIdentityUpdateOne) Save(ctx context.Context) (*AuthIdentity, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AuthIdentityUpdateOne) SaveX(ctx context.Context) *AuthIdentity {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *AuthIdentityUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuthIdentityUpdateOne) 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 *AuthIdentityUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := authidentity.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuthIdentityUpdateOne) check() error {
if v, ok := _u.mutation.ProviderType(); ok {
if err := authidentity.ProviderTypeValidator(v); err != nil {
return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_type": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderKey(); ok {
if err := authidentity.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderSubject(); ok {
if err := authidentity.ProviderSubjectValidator(v); err != nil {
return &ValidationError{Name: "provider_subject", err: fmt.Errorf(`ent: validator failed for field "AuthIdentity.provider_subject": %w`, err)}
}
}
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AuthIdentity.user"`)
}
return nil
}
func (_u *AuthIdentityUpdateOne) sqlSave(ctx context.Context) (_node *AuthIdentity, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(authidentity.Table, authidentity.Columns, sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthIdentity.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, authidentity.FieldID)
for _, f := range fields {
if !authidentity.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != authidentity.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(authidentity.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.ProviderType(); ok {
_spec.SetField(authidentity.FieldProviderType, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(authidentity.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderSubject(); ok {
_spec.SetField(authidentity.FieldProviderSubject, field.TypeString, value)
}
if value, ok := _u.mutation.VerifiedAt(); ok {
_spec.SetField(authidentity.FieldVerifiedAt, field.TypeTime, value)
}
if _u.mutation.VerifiedAtCleared() {
_spec.ClearField(authidentity.FieldVerifiedAt, field.TypeTime)
}
if value, ok := _u.mutation.Issuer(); ok {
_spec.SetField(authidentity.FieldIssuer, field.TypeString, value)
}
if _u.mutation.IssuerCleared() {
_spec.ClearField(authidentity.FieldIssuer, field.TypeString)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(authidentity.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentity.UserTable,
Columns: []string{authidentity.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentity.UserTable,
Columns: []string{authidentity.UserColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.ChannelsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedChannelsIDs(); len(nodes) > 0 && !_u.mutation.ChannelsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.ChannelsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.ChannelsTable,
Columns: []string{authidentity.ChannelsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.AdoptionDecisionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedAdoptionDecisionsIDs(); len(nodes) > 0 && !_u.mutation.AdoptionDecisionsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AdoptionDecisionsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: authidentity.AdoptionDecisionsTable,
Columns: []string{authidentity.AdoptionDecisionsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(identityadoptiondecision.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &AuthIdentity{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{authidentity.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

@@ -0,0 +1,228 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
)
// AuthIdentityChannel is the model entity for the AuthIdentityChannel schema.
type AuthIdentityChannel 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"`
// IdentityID holds the value of the "identity_id" field.
IdentityID int64 `json:"identity_id,omitempty"`
// ProviderType holds the value of the "provider_type" field.
ProviderType string `json:"provider_type,omitempty"`
// ProviderKey holds the value of the "provider_key" field.
ProviderKey string `json:"provider_key,omitempty"`
// Channel holds the value of the "channel" field.
Channel string `json:"channel,omitempty"`
// ChannelAppID holds the value of the "channel_app_id" field.
ChannelAppID string `json:"channel_app_id,omitempty"`
// ChannelSubject holds the value of the "channel_subject" field.
ChannelSubject string `json:"channel_subject,omitempty"`
// Metadata holds the value of the "metadata" field.
Metadata map[string]interface{} `json:"metadata,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the AuthIdentityChannelQuery when eager-loading is set.
Edges AuthIdentityChannelEdges `json:"edges"`
selectValues sql.SelectValues
}
// AuthIdentityChannelEdges holds the relations/edges for other nodes in the graph.
type AuthIdentityChannelEdges struct {
// Identity holds the value of the identity edge.
Identity *AuthIdentity `json:"identity,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// IdentityOrErr returns the Identity value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AuthIdentityChannelEdges) IdentityOrErr() (*AuthIdentity, error) {
if e.Identity != nil {
return e.Identity, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: authidentity.Label}
}
return nil, &NotLoadedError{edge: "identity"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*AuthIdentityChannel) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case authidentitychannel.FieldMetadata:
values[i] = new([]byte)
case authidentitychannel.FieldID, authidentitychannel.FieldIdentityID:
values[i] = new(sql.NullInt64)
case authidentitychannel.FieldProviderType, authidentitychannel.FieldProviderKey, authidentitychannel.FieldChannel, authidentitychannel.FieldChannelAppID, authidentitychannel.FieldChannelSubject:
values[i] = new(sql.NullString)
case authidentitychannel.FieldCreatedAt, authidentitychannel.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 AuthIdentityChannel fields.
func (_m *AuthIdentityChannel) 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 authidentitychannel.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 authidentitychannel.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 authidentitychannel.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 authidentitychannel.FieldIdentityID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field identity_id", values[i])
} else if value.Valid {
_m.IdentityID = value.Int64
}
case authidentitychannel.FieldProviderType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_type", values[i])
} else if value.Valid {
_m.ProviderType = value.String
}
case authidentitychannel.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 authidentitychannel.FieldChannel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field channel", values[i])
} else if value.Valid {
_m.Channel = value.String
}
case authidentitychannel.FieldChannelAppID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field channel_app_id", values[i])
} else if value.Valid {
_m.ChannelAppID = value.String
}
case authidentitychannel.FieldChannelSubject:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field channel_subject", values[i])
} else if value.Valid {
_m.ChannelSubject = value.String
}
case authidentitychannel.FieldMetadata:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field metadata", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.Metadata); err != nil {
return fmt.Errorf("unmarshal field metadata: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the AuthIdentityChannel.
// This includes values selected through modifiers, order, etc.
func (_m *AuthIdentityChannel) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryIdentity queries the "identity" edge of the AuthIdentityChannel entity.
func (_m *AuthIdentityChannel) QueryIdentity() *AuthIdentityQuery {
return NewAuthIdentityChannelClient(_m.config).QueryIdentity(_m)
}
// Update returns a builder for updating this AuthIdentityChannel.
// Note that you need to call AuthIdentityChannel.Unwrap() before calling this method if this AuthIdentityChannel
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *AuthIdentityChannel) Update() *AuthIdentityChannelUpdateOne {
return NewAuthIdentityChannelClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the AuthIdentityChannel 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 *AuthIdentityChannel) Unwrap() *AuthIdentityChannel {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: AuthIdentityChannel is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *AuthIdentityChannel) String() string {
var builder strings.Builder
builder.WriteString("AuthIdentityChannel(")
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("identity_id=")
builder.WriteString(fmt.Sprintf("%v", _m.IdentityID))
builder.WriteString(", ")
builder.WriteString("provider_type=")
builder.WriteString(_m.ProviderType)
builder.WriteString(", ")
builder.WriteString("provider_key=")
builder.WriteString(_m.ProviderKey)
builder.WriteString(", ")
builder.WriteString("channel=")
builder.WriteString(_m.Channel)
builder.WriteString(", ")
builder.WriteString("channel_app_id=")
builder.WriteString(_m.ChannelAppID)
builder.WriteString(", ")
builder.WriteString("channel_subject=")
builder.WriteString(_m.ChannelSubject)
builder.WriteString(", ")
builder.WriteString("metadata=")
builder.WriteString(fmt.Sprintf("%v", _m.Metadata))
builder.WriteByte(')')
return builder.String()
}
// AuthIdentityChannels is a parsable slice of AuthIdentityChannel.
type AuthIdentityChannels []*AuthIdentityChannel

View File

@@ -0,0 +1,153 @@
// Code generated by ent, DO NOT EDIT.
package authidentitychannel
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the authidentitychannel type in the database.
Label = "auth_identity_channel"
// 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"
// FieldIdentityID holds the string denoting the identity_id field in the database.
FieldIdentityID = "identity_id"
// FieldProviderType holds the string denoting the provider_type field in the database.
FieldProviderType = "provider_type"
// FieldProviderKey holds the string denoting the provider_key field in the database.
FieldProviderKey = "provider_key"
// FieldChannel holds the string denoting the channel field in the database.
FieldChannel = "channel"
// FieldChannelAppID holds the string denoting the channel_app_id field in the database.
FieldChannelAppID = "channel_app_id"
// FieldChannelSubject holds the string denoting the channel_subject field in the database.
FieldChannelSubject = "channel_subject"
// FieldMetadata holds the string denoting the metadata field in the database.
FieldMetadata = "metadata"
// EdgeIdentity holds the string denoting the identity edge name in mutations.
EdgeIdentity = "identity"
// Table holds the table name of the authidentitychannel in the database.
Table = "auth_identity_channels"
// IdentityTable is the table that holds the identity relation/edge.
IdentityTable = "auth_identity_channels"
// IdentityInverseTable is the table name for the AuthIdentity entity.
// It exists in this package in order to avoid circular dependency with the "authidentity" package.
IdentityInverseTable = "auth_identities"
// IdentityColumn is the table column denoting the identity relation/edge.
IdentityColumn = "identity_id"
)
// Columns holds all SQL columns for authidentitychannel fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldIdentityID,
FieldProviderType,
FieldProviderKey,
FieldChannel,
FieldChannelAppID,
FieldChannelSubject,
FieldMetadata,
}
// 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
// ProviderTypeValidator is a validator for the "provider_type" field. It is called by the builders before save.
ProviderTypeValidator func(string) error
// ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
ProviderKeyValidator func(string) error
// ChannelValidator is a validator for the "channel" field. It is called by the builders before save.
ChannelValidator func(string) error
// ChannelAppIDValidator is a validator for the "channel_app_id" field. It is called by the builders before save.
ChannelAppIDValidator func(string) error
// ChannelSubjectValidator is a validator for the "channel_subject" field. It is called by the builders before save.
ChannelSubjectValidator func(string) error
// DefaultMetadata holds the default value on creation for the "metadata" field.
DefaultMetadata func() map[string]interface{}
)
// OrderOption defines the ordering options for the AuthIdentityChannel 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()
}
// ByIdentityID orders the results by the identity_id field.
func ByIdentityID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIdentityID, opts...).ToFunc()
}
// ByProviderType orders the results by the provider_type field.
func ByProviderType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderType, opts...).ToFunc()
}
// ByProviderKey orders the results by the provider_key field.
func ByProviderKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderKey, opts...).ToFunc()
}
// ByChannel orders the results by the channel field.
func ByChannel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldChannel, opts...).ToFunc()
}
// ByChannelAppID orders the results by the channel_app_id field.
func ByChannelAppID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldChannelAppID, opts...).ToFunc()
}
// ByChannelSubject orders the results by the channel_subject field.
func ByChannelSubject(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldChannelSubject, opts...).ToFunc()
}
// ByIdentityField orders the results by identity field.
func ByIdentityField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newIdentityStep(), sql.OrderByField(field, opts...))
}
}
func newIdentityStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(IdentityInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, IdentityTable, IdentityColumn),
)
}

View File

@@ -0,0 +1,559 @@
// Code generated by ent, DO NOT EDIT.
package authidentitychannel
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(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.AuthIdentityChannel {
return predicate.AuthIdentityChannel(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.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldUpdatedAt, v))
}
// IdentityID applies equality check predicate on the "identity_id" field. It's identical to IdentityIDEQ.
func IdentityID(v int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldIdentityID, v))
}
// ProviderType applies equality check predicate on the "provider_type" field. It's identical to ProviderTypeEQ.
func ProviderType(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldProviderType, v))
}
// ProviderKey applies equality check predicate on the "provider_key" field. It's identical to ProviderKeyEQ.
func ProviderKey(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldProviderKey, v))
}
// Channel applies equality check predicate on the "channel" field. It's identical to ChannelEQ.
func Channel(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannel, v))
}
// ChannelAppID applies equality check predicate on the "channel_app_id" field. It's identical to ChannelAppIDEQ.
func ChannelAppID(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannelAppID, v))
}
// ChannelSubject applies equality check predicate on the "channel_subject" field. It's identical to ChannelSubjectEQ.
func ChannelSubject(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannelSubject, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldUpdatedAt, v))
}
// IdentityIDEQ applies the EQ predicate on the "identity_id" field.
func IdentityIDEQ(v int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldIdentityID, v))
}
// IdentityIDNEQ applies the NEQ predicate on the "identity_id" field.
func IdentityIDNEQ(v int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldIdentityID, v))
}
// IdentityIDIn applies the In predicate on the "identity_id" field.
func IdentityIDIn(vs ...int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldIdentityID, vs...))
}
// IdentityIDNotIn applies the NotIn predicate on the "identity_id" field.
func IdentityIDNotIn(vs ...int64) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldIdentityID, vs...))
}
// ProviderTypeEQ applies the EQ predicate on the "provider_type" field.
func ProviderTypeEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldProviderType, v))
}
// ProviderTypeNEQ applies the NEQ predicate on the "provider_type" field.
func ProviderTypeNEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldProviderType, v))
}
// ProviderTypeIn applies the In predicate on the "provider_type" field.
func ProviderTypeIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldProviderType, vs...))
}
// ProviderTypeNotIn applies the NotIn predicate on the "provider_type" field.
func ProviderTypeNotIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldProviderType, vs...))
}
// ProviderTypeGT applies the GT predicate on the "provider_type" field.
func ProviderTypeGT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldProviderType, v))
}
// ProviderTypeGTE applies the GTE predicate on the "provider_type" field.
func ProviderTypeGTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldProviderType, v))
}
// ProviderTypeLT applies the LT predicate on the "provider_type" field.
func ProviderTypeLT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldProviderType, v))
}
// ProviderTypeLTE applies the LTE predicate on the "provider_type" field.
func ProviderTypeLTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldProviderType, v))
}
// ProviderTypeContains applies the Contains predicate on the "provider_type" field.
func ProviderTypeContains(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContains(FieldProviderType, v))
}
// ProviderTypeHasPrefix applies the HasPrefix predicate on the "provider_type" field.
func ProviderTypeHasPrefix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasPrefix(FieldProviderType, v))
}
// ProviderTypeHasSuffix applies the HasSuffix predicate on the "provider_type" field.
func ProviderTypeHasSuffix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasSuffix(FieldProviderType, v))
}
// ProviderTypeEqualFold applies the EqualFold predicate on the "provider_type" field.
func ProviderTypeEqualFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEqualFold(FieldProviderType, v))
}
// ProviderTypeContainsFold applies the ContainsFold predicate on the "provider_type" field.
func ProviderTypeContainsFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContainsFold(FieldProviderType, v))
}
// ProviderKeyEQ applies the EQ predicate on the "provider_key" field.
func ProviderKeyEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldProviderKey, v))
}
// ProviderKeyNEQ applies the NEQ predicate on the "provider_key" field.
func ProviderKeyNEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldProviderKey, v))
}
// ProviderKeyIn applies the In predicate on the "provider_key" field.
func ProviderKeyIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldProviderKey, vs...))
}
// ProviderKeyNotIn applies the NotIn predicate on the "provider_key" field.
func ProviderKeyNotIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldProviderKey, vs...))
}
// ProviderKeyGT applies the GT predicate on the "provider_key" field.
func ProviderKeyGT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldProviderKey, v))
}
// ProviderKeyGTE applies the GTE predicate on the "provider_key" field.
func ProviderKeyGTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldProviderKey, v))
}
// ProviderKeyLT applies the LT predicate on the "provider_key" field.
func ProviderKeyLT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldProviderKey, v))
}
// ProviderKeyLTE applies the LTE predicate on the "provider_key" field.
func ProviderKeyLTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldProviderKey, v))
}
// ProviderKeyContains applies the Contains predicate on the "provider_key" field.
func ProviderKeyContains(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContains(FieldProviderKey, v))
}
// ProviderKeyHasPrefix applies the HasPrefix predicate on the "provider_key" field.
func ProviderKeyHasPrefix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasPrefix(FieldProviderKey, v))
}
// ProviderKeyHasSuffix applies the HasSuffix predicate on the "provider_key" field.
func ProviderKeyHasSuffix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasSuffix(FieldProviderKey, v))
}
// ProviderKeyEqualFold applies the EqualFold predicate on the "provider_key" field.
func ProviderKeyEqualFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEqualFold(FieldProviderKey, v))
}
// ProviderKeyContainsFold applies the ContainsFold predicate on the "provider_key" field.
func ProviderKeyContainsFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContainsFold(FieldProviderKey, v))
}
// ChannelEQ applies the EQ predicate on the "channel" field.
func ChannelEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannel, v))
}
// ChannelNEQ applies the NEQ predicate on the "channel" field.
func ChannelNEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldChannel, v))
}
// ChannelIn applies the In predicate on the "channel" field.
func ChannelIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldChannel, vs...))
}
// ChannelNotIn applies the NotIn predicate on the "channel" field.
func ChannelNotIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldChannel, vs...))
}
// ChannelGT applies the GT predicate on the "channel" field.
func ChannelGT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldChannel, v))
}
// ChannelGTE applies the GTE predicate on the "channel" field.
func ChannelGTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldChannel, v))
}
// ChannelLT applies the LT predicate on the "channel" field.
func ChannelLT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldChannel, v))
}
// ChannelLTE applies the LTE predicate on the "channel" field.
func ChannelLTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldChannel, v))
}
// ChannelContains applies the Contains predicate on the "channel" field.
func ChannelContains(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContains(FieldChannel, v))
}
// ChannelHasPrefix applies the HasPrefix predicate on the "channel" field.
func ChannelHasPrefix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasPrefix(FieldChannel, v))
}
// ChannelHasSuffix applies the HasSuffix predicate on the "channel" field.
func ChannelHasSuffix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasSuffix(FieldChannel, v))
}
// ChannelEqualFold applies the EqualFold predicate on the "channel" field.
func ChannelEqualFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEqualFold(FieldChannel, v))
}
// ChannelContainsFold applies the ContainsFold predicate on the "channel" field.
func ChannelContainsFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContainsFold(FieldChannel, v))
}
// ChannelAppIDEQ applies the EQ predicate on the "channel_app_id" field.
func ChannelAppIDEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannelAppID, v))
}
// ChannelAppIDNEQ applies the NEQ predicate on the "channel_app_id" field.
func ChannelAppIDNEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldChannelAppID, v))
}
// ChannelAppIDIn applies the In predicate on the "channel_app_id" field.
func ChannelAppIDIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldChannelAppID, vs...))
}
// ChannelAppIDNotIn applies the NotIn predicate on the "channel_app_id" field.
func ChannelAppIDNotIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldChannelAppID, vs...))
}
// ChannelAppIDGT applies the GT predicate on the "channel_app_id" field.
func ChannelAppIDGT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldChannelAppID, v))
}
// ChannelAppIDGTE applies the GTE predicate on the "channel_app_id" field.
func ChannelAppIDGTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldChannelAppID, v))
}
// ChannelAppIDLT applies the LT predicate on the "channel_app_id" field.
func ChannelAppIDLT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldChannelAppID, v))
}
// ChannelAppIDLTE applies the LTE predicate on the "channel_app_id" field.
func ChannelAppIDLTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldChannelAppID, v))
}
// ChannelAppIDContains applies the Contains predicate on the "channel_app_id" field.
func ChannelAppIDContains(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContains(FieldChannelAppID, v))
}
// ChannelAppIDHasPrefix applies the HasPrefix predicate on the "channel_app_id" field.
func ChannelAppIDHasPrefix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasPrefix(FieldChannelAppID, v))
}
// ChannelAppIDHasSuffix applies the HasSuffix predicate on the "channel_app_id" field.
func ChannelAppIDHasSuffix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasSuffix(FieldChannelAppID, v))
}
// ChannelAppIDEqualFold applies the EqualFold predicate on the "channel_app_id" field.
func ChannelAppIDEqualFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEqualFold(FieldChannelAppID, v))
}
// ChannelAppIDContainsFold applies the ContainsFold predicate on the "channel_app_id" field.
func ChannelAppIDContainsFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContainsFold(FieldChannelAppID, v))
}
// ChannelSubjectEQ applies the EQ predicate on the "channel_subject" field.
func ChannelSubjectEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEQ(FieldChannelSubject, v))
}
// ChannelSubjectNEQ applies the NEQ predicate on the "channel_subject" field.
func ChannelSubjectNEQ(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNEQ(FieldChannelSubject, v))
}
// ChannelSubjectIn applies the In predicate on the "channel_subject" field.
func ChannelSubjectIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldIn(FieldChannelSubject, vs...))
}
// ChannelSubjectNotIn applies the NotIn predicate on the "channel_subject" field.
func ChannelSubjectNotIn(vs ...string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldNotIn(FieldChannelSubject, vs...))
}
// ChannelSubjectGT applies the GT predicate on the "channel_subject" field.
func ChannelSubjectGT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGT(FieldChannelSubject, v))
}
// ChannelSubjectGTE applies the GTE predicate on the "channel_subject" field.
func ChannelSubjectGTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldGTE(FieldChannelSubject, v))
}
// ChannelSubjectLT applies the LT predicate on the "channel_subject" field.
func ChannelSubjectLT(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLT(FieldChannelSubject, v))
}
// ChannelSubjectLTE applies the LTE predicate on the "channel_subject" field.
func ChannelSubjectLTE(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldLTE(FieldChannelSubject, v))
}
// ChannelSubjectContains applies the Contains predicate on the "channel_subject" field.
func ChannelSubjectContains(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContains(FieldChannelSubject, v))
}
// ChannelSubjectHasPrefix applies the HasPrefix predicate on the "channel_subject" field.
func ChannelSubjectHasPrefix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasPrefix(FieldChannelSubject, v))
}
// ChannelSubjectHasSuffix applies the HasSuffix predicate on the "channel_subject" field.
func ChannelSubjectHasSuffix(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldHasSuffix(FieldChannelSubject, v))
}
// ChannelSubjectEqualFold applies the EqualFold predicate on the "channel_subject" field.
func ChannelSubjectEqualFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldEqualFold(FieldChannelSubject, v))
}
// ChannelSubjectContainsFold applies the ContainsFold predicate on the "channel_subject" field.
func ChannelSubjectContainsFold(v string) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.FieldContainsFold(FieldChannelSubject, v))
}
// HasIdentity applies the HasEdge predicate on the "identity" edge.
func HasIdentity() predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, IdentityTable, IdentityColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasIdentityWith applies the HasEdge predicate on the "identity" edge with a given conditions (other predicates).
func HasIdentityWith(preds ...predicate.AuthIdentity) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(func(s *sql.Selector) {
step := newIdentityStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.AuthIdentityChannel) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.AuthIdentityChannel) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.AuthIdentityChannel) predicate.AuthIdentityChannel {
return predicate.AuthIdentityChannel(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,932 @@
// 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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
)
// AuthIdentityChannelCreate is the builder for creating a AuthIdentityChannel entity.
type AuthIdentityChannelCreate struct {
config
mutation *AuthIdentityChannelMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetCreatedAt sets the "created_at" field.
func (_c *AuthIdentityChannelCreate) SetCreatedAt(v time.Time) *AuthIdentityChannelCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *AuthIdentityChannelCreate) SetNillableCreatedAt(v *time.Time) *AuthIdentityChannelCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// SetUpdatedAt sets the "updated_at" field.
func (_c *AuthIdentityChannelCreate) SetUpdatedAt(v time.Time) *AuthIdentityChannelCreate {
_c.mutation.SetUpdatedAt(v)
return _c
}
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
func (_c *AuthIdentityChannelCreate) SetNillableUpdatedAt(v *time.Time) *AuthIdentityChannelCreate {
if v != nil {
_c.SetUpdatedAt(*v)
}
return _c
}
// SetIdentityID sets the "identity_id" field.
func (_c *AuthIdentityChannelCreate) SetIdentityID(v int64) *AuthIdentityChannelCreate {
_c.mutation.SetIdentityID(v)
return _c
}
// SetProviderType sets the "provider_type" field.
func (_c *AuthIdentityChannelCreate) SetProviderType(v string) *AuthIdentityChannelCreate {
_c.mutation.SetProviderType(v)
return _c
}
// SetProviderKey sets the "provider_key" field.
func (_c *AuthIdentityChannelCreate) SetProviderKey(v string) *AuthIdentityChannelCreate {
_c.mutation.SetProviderKey(v)
return _c
}
// SetChannel sets the "channel" field.
func (_c *AuthIdentityChannelCreate) SetChannel(v string) *AuthIdentityChannelCreate {
_c.mutation.SetChannel(v)
return _c
}
// SetChannelAppID sets the "channel_app_id" field.
func (_c *AuthIdentityChannelCreate) SetChannelAppID(v string) *AuthIdentityChannelCreate {
_c.mutation.SetChannelAppID(v)
return _c
}
// SetChannelSubject sets the "channel_subject" field.
func (_c *AuthIdentityChannelCreate) SetChannelSubject(v string) *AuthIdentityChannelCreate {
_c.mutation.SetChannelSubject(v)
return _c
}
// SetMetadata sets the "metadata" field.
func (_c *AuthIdentityChannelCreate) SetMetadata(v map[string]interface{}) *AuthIdentityChannelCreate {
_c.mutation.SetMetadata(v)
return _c
}
// SetIdentity sets the "identity" edge to the AuthIdentity entity.
func (_c *AuthIdentityChannelCreate) SetIdentity(v *AuthIdentity) *AuthIdentityChannelCreate {
return _c.SetIdentityID(v.ID)
}
// Mutation returns the AuthIdentityChannelMutation object of the builder.
func (_c *AuthIdentityChannelCreate) Mutation() *AuthIdentityChannelMutation {
return _c.mutation
}
// Save creates the AuthIdentityChannel in the database.
func (_c *AuthIdentityChannelCreate) Save(ctx context.Context) (*AuthIdentityChannel, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *AuthIdentityChannelCreate) SaveX(ctx context.Context) *AuthIdentityChannel {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AuthIdentityChannelCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AuthIdentityChannelCreate) 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 *AuthIdentityChannelCreate) defaults() {
if _, ok := _c.mutation.CreatedAt(); !ok {
v := authidentitychannel.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
if _, ok := _c.mutation.UpdatedAt(); !ok {
v := authidentitychannel.DefaultUpdatedAt()
_c.mutation.SetUpdatedAt(v)
}
if _, ok := _c.mutation.Metadata(); !ok {
v := authidentitychannel.DefaultMetadata()
_c.mutation.SetMetadata(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *AuthIdentityChannelCreate) check() error {
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AuthIdentityChannel.created_at"`)}
}
if _, ok := _c.mutation.UpdatedAt(); !ok {
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "AuthIdentityChannel.updated_at"`)}
}
if _, ok := _c.mutation.IdentityID(); !ok {
return &ValidationError{Name: "identity_id", err: errors.New(`ent: missing required field "AuthIdentityChannel.identity_id"`)}
}
if _, ok := _c.mutation.ProviderType(); !ok {
return &ValidationError{Name: "provider_type", err: errors.New(`ent: missing required field "AuthIdentityChannel.provider_type"`)}
}
if v, ok := _c.mutation.ProviderType(); ok {
if err := authidentitychannel.ProviderTypeValidator(v); err != nil {
return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_type": %w`, err)}
}
}
if _, ok := _c.mutation.ProviderKey(); !ok {
return &ValidationError{Name: "provider_key", err: errors.New(`ent: missing required field "AuthIdentityChannel.provider_key"`)}
}
if v, ok := _c.mutation.ProviderKey(); ok {
if err := authidentitychannel.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_key": %w`, err)}
}
}
if _, ok := _c.mutation.Channel(); !ok {
return &ValidationError{Name: "channel", err: errors.New(`ent: missing required field "AuthIdentityChannel.channel"`)}
}
if v, ok := _c.mutation.Channel(); ok {
if err := authidentitychannel.ChannelValidator(v); err != nil {
return &ValidationError{Name: "channel", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel": %w`, err)}
}
}
if _, ok := _c.mutation.ChannelAppID(); !ok {
return &ValidationError{Name: "channel_app_id", err: errors.New(`ent: missing required field "AuthIdentityChannel.channel_app_id"`)}
}
if v, ok := _c.mutation.ChannelAppID(); ok {
if err := authidentitychannel.ChannelAppIDValidator(v); err != nil {
return &ValidationError{Name: "channel_app_id", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_app_id": %w`, err)}
}
}
if _, ok := _c.mutation.ChannelSubject(); !ok {
return &ValidationError{Name: "channel_subject", err: errors.New(`ent: missing required field "AuthIdentityChannel.channel_subject"`)}
}
if v, ok := _c.mutation.ChannelSubject(); ok {
if err := authidentitychannel.ChannelSubjectValidator(v); err != nil {
return &ValidationError{Name: "channel_subject", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_subject": %w`, err)}
}
}
if _, ok := _c.mutation.Metadata(); !ok {
return &ValidationError{Name: "metadata", err: errors.New(`ent: missing required field "AuthIdentityChannel.metadata"`)}
}
if len(_c.mutation.IdentityIDs()) == 0 {
return &ValidationError{Name: "identity", err: errors.New(`ent: missing required edge "AuthIdentityChannel.identity"`)}
}
return nil
}
func (_c *AuthIdentityChannelCreate) sqlSave(ctx context.Context) (*AuthIdentityChannel, 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 *AuthIdentityChannelCreate) createSpec() (*AuthIdentityChannel, *sqlgraph.CreateSpec) {
var (
_node = &AuthIdentityChannel{config: _c.config}
_spec = sqlgraph.NewCreateSpec(authidentitychannel.Table, sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(authidentitychannel.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
if value, ok := _c.mutation.UpdatedAt(); ok {
_spec.SetField(authidentitychannel.FieldUpdatedAt, field.TypeTime, value)
_node.UpdatedAt = value
}
if value, ok := _c.mutation.ProviderType(); ok {
_spec.SetField(authidentitychannel.FieldProviderType, field.TypeString, value)
_node.ProviderType = value
}
if value, ok := _c.mutation.ProviderKey(); ok {
_spec.SetField(authidentitychannel.FieldProviderKey, field.TypeString, value)
_node.ProviderKey = value
}
if value, ok := _c.mutation.Channel(); ok {
_spec.SetField(authidentitychannel.FieldChannel, field.TypeString, value)
_node.Channel = value
}
if value, ok := _c.mutation.ChannelAppID(); ok {
_spec.SetField(authidentitychannel.FieldChannelAppID, field.TypeString, value)
_node.ChannelAppID = value
}
if value, ok := _c.mutation.ChannelSubject(); ok {
_spec.SetField(authidentitychannel.FieldChannelSubject, field.TypeString, value)
_node.ChannelSubject = value
}
if value, ok := _c.mutation.Metadata(); ok {
_spec.SetField(authidentitychannel.FieldMetadata, field.TypeJSON, value)
_node.Metadata = value
}
if nodes := _c.mutation.IdentityIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentitychannel.IdentityTable,
Columns: []string{authidentitychannel.IdentityColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.IdentityID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.AuthIdentityChannel.Create().
// SetCreatedAt(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.AuthIdentityChannelUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (_c *AuthIdentityChannelCreate) OnConflict(opts ...sql.ConflictOption) *AuthIdentityChannelUpsertOne {
_c.conflict = opts
return &AuthIdentityChannelUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *AuthIdentityChannelCreate) OnConflictColumns(columns ...string) *AuthIdentityChannelUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &AuthIdentityChannelUpsertOne{
create: _c,
}
}
type (
// AuthIdentityChannelUpsertOne is the builder for "upsert"-ing
// one AuthIdentityChannel node.
AuthIdentityChannelUpsertOne struct {
create *AuthIdentityChannelCreate
}
// AuthIdentityChannelUpsert is the "OnConflict" setter.
AuthIdentityChannelUpsert struct {
*sql.UpdateSet
}
)
// SetUpdatedAt sets the "updated_at" field.
func (u *AuthIdentityChannelUpsert) SetUpdatedAt(v time.Time) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldUpdatedAt, v)
return u
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateUpdatedAt() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldUpdatedAt)
return u
}
// SetIdentityID sets the "identity_id" field.
func (u *AuthIdentityChannelUpsert) SetIdentityID(v int64) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldIdentityID, v)
return u
}
// UpdateIdentityID sets the "identity_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateIdentityID() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldIdentityID)
return u
}
// SetProviderType sets the "provider_type" field.
func (u *AuthIdentityChannelUpsert) SetProviderType(v string) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldProviderType, v)
return u
}
// UpdateProviderType sets the "provider_type" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateProviderType() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldProviderType)
return u
}
// SetProviderKey sets the "provider_key" field.
func (u *AuthIdentityChannelUpsert) SetProviderKey(v string) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldProviderKey, v)
return u
}
// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateProviderKey() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldProviderKey)
return u
}
// SetChannel sets the "channel" field.
func (u *AuthIdentityChannelUpsert) SetChannel(v string) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldChannel, v)
return u
}
// UpdateChannel sets the "channel" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateChannel() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldChannel)
return u
}
// SetChannelAppID sets the "channel_app_id" field.
func (u *AuthIdentityChannelUpsert) SetChannelAppID(v string) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldChannelAppID, v)
return u
}
// UpdateChannelAppID sets the "channel_app_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateChannelAppID() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldChannelAppID)
return u
}
// SetChannelSubject sets the "channel_subject" field.
func (u *AuthIdentityChannelUpsert) SetChannelSubject(v string) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldChannelSubject, v)
return u
}
// UpdateChannelSubject sets the "channel_subject" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateChannelSubject() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldChannelSubject)
return u
}
// SetMetadata sets the "metadata" field.
func (u *AuthIdentityChannelUpsert) SetMetadata(v map[string]interface{}) *AuthIdentityChannelUpsert {
u.Set(authidentitychannel.FieldMetadata, v)
return u
}
// UpdateMetadata sets the "metadata" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsert) UpdateMetadata() *AuthIdentityChannelUpsert {
u.SetExcluded(authidentitychannel.FieldMetadata)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AuthIdentityChannelUpsertOne) UpdateNewValues() *AuthIdentityChannelUpsertOne {
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(authidentitychannel.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AuthIdentityChannelUpsertOne) Ignore() *AuthIdentityChannelUpsertOne {
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 *AuthIdentityChannelUpsertOne) DoNothing() *AuthIdentityChannelUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AuthIdentityChannelCreate.OnConflict
// documentation for more info.
func (u *AuthIdentityChannelUpsertOne) Update(set func(*AuthIdentityChannelUpsert)) *AuthIdentityChannelUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AuthIdentityChannelUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *AuthIdentityChannelUpsertOne) SetUpdatedAt(v time.Time) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateUpdatedAt() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateUpdatedAt()
})
}
// SetIdentityID sets the "identity_id" field.
func (u *AuthIdentityChannelUpsertOne) SetIdentityID(v int64) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetIdentityID(v)
})
}
// UpdateIdentityID sets the "identity_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateIdentityID() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateIdentityID()
})
}
// SetProviderType sets the "provider_type" field.
func (u *AuthIdentityChannelUpsertOne) SetProviderType(v string) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetProviderType(v)
})
}
// UpdateProviderType sets the "provider_type" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateProviderType() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateProviderType()
})
}
// SetProviderKey sets the "provider_key" field.
func (u *AuthIdentityChannelUpsertOne) SetProviderKey(v string) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetProviderKey(v)
})
}
// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateProviderKey() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateProviderKey()
})
}
// SetChannel sets the "channel" field.
func (u *AuthIdentityChannelUpsertOne) SetChannel(v string) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannel(v)
})
}
// UpdateChannel sets the "channel" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateChannel() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannel()
})
}
// SetChannelAppID sets the "channel_app_id" field.
func (u *AuthIdentityChannelUpsertOne) SetChannelAppID(v string) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannelAppID(v)
})
}
// UpdateChannelAppID sets the "channel_app_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateChannelAppID() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannelAppID()
})
}
// SetChannelSubject sets the "channel_subject" field.
func (u *AuthIdentityChannelUpsertOne) SetChannelSubject(v string) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannelSubject(v)
})
}
// UpdateChannelSubject sets the "channel_subject" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateChannelSubject() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannelSubject()
})
}
// SetMetadata sets the "metadata" field.
func (u *AuthIdentityChannelUpsertOne) SetMetadata(v map[string]interface{}) *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetMetadata(v)
})
}
// UpdateMetadata sets the "metadata" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertOne) UpdateMetadata() *AuthIdentityChannelUpsertOne {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateMetadata()
})
}
// Exec executes the query.
func (u *AuthIdentityChannelUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AuthIdentityChannelCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AuthIdentityChannelUpsertOne) 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 *AuthIdentityChannelUpsertOne) 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 *AuthIdentityChannelUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// AuthIdentityChannelCreateBulk is the builder for creating many AuthIdentityChannel entities in bulk.
type AuthIdentityChannelCreateBulk struct {
config
err error
builders []*AuthIdentityChannelCreate
conflict []sql.ConflictOption
}
// Save creates the AuthIdentityChannel entities in the database.
func (_c *AuthIdentityChannelCreateBulk) Save(ctx context.Context) ([]*AuthIdentityChannel, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*AuthIdentityChannel, 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.(*AuthIdentityChannelMutation)
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 *AuthIdentityChannelCreateBulk) SaveX(ctx context.Context) []*AuthIdentityChannel {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *AuthIdentityChannelCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *AuthIdentityChannelCreateBulk) 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.AuthIdentityChannel.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.AuthIdentityChannelUpsert) {
// SetCreatedAt(v+v).
// }).
// Exec(ctx)
func (_c *AuthIdentityChannelCreateBulk) OnConflict(opts ...sql.ConflictOption) *AuthIdentityChannelUpsertBulk {
_c.conflict = opts
return &AuthIdentityChannelUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *AuthIdentityChannelCreateBulk) OnConflictColumns(columns ...string) *AuthIdentityChannelUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &AuthIdentityChannelUpsertBulk{
create: _c,
}
}
// AuthIdentityChannelUpsertBulk is the builder for "upsert"-ing
// a bulk of AuthIdentityChannel nodes.
type AuthIdentityChannelUpsertBulk struct {
create *AuthIdentityChannelCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *AuthIdentityChannelUpsertBulk) UpdateNewValues() *AuthIdentityChannelUpsertBulk {
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(authidentitychannel.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.AuthIdentityChannel.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *AuthIdentityChannelUpsertBulk) Ignore() *AuthIdentityChannelUpsertBulk {
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 *AuthIdentityChannelUpsertBulk) DoNothing() *AuthIdentityChannelUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the AuthIdentityChannelCreateBulk.OnConflict
// documentation for more info.
func (u *AuthIdentityChannelUpsertBulk) Update(set func(*AuthIdentityChannelUpsert)) *AuthIdentityChannelUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&AuthIdentityChannelUpsert{UpdateSet: update})
}))
return u
}
// SetUpdatedAt sets the "updated_at" field.
func (u *AuthIdentityChannelUpsertBulk) SetUpdatedAt(v time.Time) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetUpdatedAt(v)
})
}
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateUpdatedAt() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateUpdatedAt()
})
}
// SetIdentityID sets the "identity_id" field.
func (u *AuthIdentityChannelUpsertBulk) SetIdentityID(v int64) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetIdentityID(v)
})
}
// UpdateIdentityID sets the "identity_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateIdentityID() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateIdentityID()
})
}
// SetProviderType sets the "provider_type" field.
func (u *AuthIdentityChannelUpsertBulk) SetProviderType(v string) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetProviderType(v)
})
}
// UpdateProviderType sets the "provider_type" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateProviderType() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateProviderType()
})
}
// SetProviderKey sets the "provider_key" field.
func (u *AuthIdentityChannelUpsertBulk) SetProviderKey(v string) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetProviderKey(v)
})
}
// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateProviderKey() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateProviderKey()
})
}
// SetChannel sets the "channel" field.
func (u *AuthIdentityChannelUpsertBulk) SetChannel(v string) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannel(v)
})
}
// UpdateChannel sets the "channel" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateChannel() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannel()
})
}
// SetChannelAppID sets the "channel_app_id" field.
func (u *AuthIdentityChannelUpsertBulk) SetChannelAppID(v string) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannelAppID(v)
})
}
// UpdateChannelAppID sets the "channel_app_id" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateChannelAppID() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannelAppID()
})
}
// SetChannelSubject sets the "channel_subject" field.
func (u *AuthIdentityChannelUpsertBulk) SetChannelSubject(v string) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetChannelSubject(v)
})
}
// UpdateChannelSubject sets the "channel_subject" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateChannelSubject() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateChannelSubject()
})
}
// SetMetadata sets the "metadata" field.
func (u *AuthIdentityChannelUpsertBulk) SetMetadata(v map[string]interface{}) *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.SetMetadata(v)
})
}
// UpdateMetadata sets the "metadata" field to the value that was provided on create.
func (u *AuthIdentityChannelUpsertBulk) UpdateMetadata() *AuthIdentityChannelUpsertBulk {
return u.Update(func(s *AuthIdentityChannelUpsert) {
s.UpdateMetadata()
})
}
// Exec executes the query.
func (u *AuthIdentityChannelUpsertBulk) 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 AuthIdentityChannelCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for AuthIdentityChannelCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *AuthIdentityChannelUpsertBulk) 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/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AuthIdentityChannelDelete is the builder for deleting a AuthIdentityChannel entity.
type AuthIdentityChannelDelete struct {
config
hooks []Hook
mutation *AuthIdentityChannelMutation
}
// Where appends a list predicates to the AuthIdentityChannelDelete builder.
func (_d *AuthIdentityChannelDelete) Where(ps ...predicate.AuthIdentityChannel) *AuthIdentityChannelDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AuthIdentityChannelDelete) 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 *AuthIdentityChannelDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *AuthIdentityChannelDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(authidentitychannel.Table, sqlgraph.NewFieldSpec(authidentitychannel.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
}
// AuthIdentityChannelDeleteOne is the builder for deleting a single AuthIdentityChannel entity.
type AuthIdentityChannelDeleteOne struct {
_d *AuthIdentityChannelDelete
}
// Where appends a list predicates to the AuthIdentityChannelDelete builder.
func (_d *AuthIdentityChannelDeleteOne) Where(ps ...predicate.AuthIdentityChannel) *AuthIdentityChannelDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *AuthIdentityChannelDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{authidentitychannel.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *AuthIdentityChannelDeleteOne) 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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AuthIdentityChannelQuery is the builder for querying AuthIdentityChannel entities.
type AuthIdentityChannelQuery struct {
config
ctx *QueryContext
order []authidentitychannel.OrderOption
inters []Interceptor
predicates []predicate.AuthIdentityChannel
withIdentity *AuthIdentityQuery
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 AuthIdentityChannelQuery builder.
func (_q *AuthIdentityChannelQuery) Where(ps ...predicate.AuthIdentityChannel) *AuthIdentityChannelQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *AuthIdentityChannelQuery) Limit(limit int) *AuthIdentityChannelQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *AuthIdentityChannelQuery) Offset(offset int) *AuthIdentityChannelQuery {
_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 *AuthIdentityChannelQuery) Unique(unique bool) *AuthIdentityChannelQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *AuthIdentityChannelQuery) Order(o ...authidentitychannel.OrderOption) *AuthIdentityChannelQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryIdentity chains the current query on the "identity" edge.
func (_q *AuthIdentityChannelQuery) QueryIdentity() *AuthIdentityQuery {
query := (&AuthIdentityClient{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(authidentitychannel.Table, authidentitychannel.FieldID, selector),
sqlgraph.To(authidentity.Table, authidentity.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, authidentitychannel.IdentityTable, authidentitychannel.IdentityColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first AuthIdentityChannel entity from the query.
// Returns a *NotFoundError when no AuthIdentityChannel was found.
func (_q *AuthIdentityChannelQuery) First(ctx context.Context) (*AuthIdentityChannel, 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{authidentitychannel.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) FirstX(ctx context.Context) *AuthIdentityChannel {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first AuthIdentityChannel ID from the query.
// Returns a *NotFoundError when no AuthIdentityChannel ID was found.
func (_q *AuthIdentityChannelQuery) 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{authidentitychannel.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single AuthIdentityChannel entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one AuthIdentityChannel entity is found.
// Returns a *NotFoundError when no AuthIdentityChannel entities are found.
func (_q *AuthIdentityChannelQuery) Only(ctx context.Context) (*AuthIdentityChannel, 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{authidentitychannel.Label}
default:
return nil, &NotSingularError{authidentitychannel.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) OnlyX(ctx context.Context) *AuthIdentityChannel {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only AuthIdentityChannel ID in the query.
// Returns a *NotSingularError when more than one AuthIdentityChannel ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *AuthIdentityChannelQuery) 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{authidentitychannel.Label}
default:
err = &NotSingularError{authidentitychannel.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) 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 AuthIdentityChannels.
func (_q *AuthIdentityChannelQuery) All(ctx context.Context) ([]*AuthIdentityChannel, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*AuthIdentityChannel, *AuthIdentityChannelQuery]()
return withInterceptors[[]*AuthIdentityChannel](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) AllX(ctx context.Context) []*AuthIdentityChannel {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of AuthIdentityChannel IDs.
func (_q *AuthIdentityChannelQuery) 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(authidentitychannel.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) 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 *AuthIdentityChannelQuery) 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[*AuthIdentityChannelQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *AuthIdentityChannelQuery) 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 *AuthIdentityChannelQuery) 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 *AuthIdentityChannelQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the AuthIdentityChannelQuery 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 *AuthIdentityChannelQuery) Clone() *AuthIdentityChannelQuery {
if _q == nil {
return nil
}
return &AuthIdentityChannelQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]authidentitychannel.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.AuthIdentityChannel{}, _q.predicates...),
withIdentity: _q.withIdentity.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithIdentity tells the query-builder to eager-load the nodes that are connected to
// the "identity" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *AuthIdentityChannelQuery) WithIdentity(opts ...func(*AuthIdentityQuery)) *AuthIdentityChannelQuery {
query := (&AuthIdentityClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withIdentity = 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 {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.AuthIdentityChannel.Query().
// GroupBy(authidentitychannel.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *AuthIdentityChannelQuery) GroupBy(field string, fields ...string) *AuthIdentityChannelGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &AuthIdentityChannelGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = authidentitychannel.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.AuthIdentityChannel.Query().
// Select(authidentitychannel.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *AuthIdentityChannelQuery) Select(fields ...string) *AuthIdentityChannelSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &AuthIdentityChannelSelect{AuthIdentityChannelQuery: _q}
sbuild.label = authidentitychannel.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a AuthIdentityChannelSelect configured with the given aggregations.
func (_q *AuthIdentityChannelQuery) Aggregate(fns ...AggregateFunc) *AuthIdentityChannelSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *AuthIdentityChannelQuery) 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 !authidentitychannel.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 *AuthIdentityChannelQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthIdentityChannel, error) {
var (
nodes = []*AuthIdentityChannel{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withIdentity != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*AuthIdentityChannel).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &AuthIdentityChannel{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.withIdentity; query != nil {
if err := _q.loadIdentity(ctx, query, nodes, nil,
func(n *AuthIdentityChannel, e *AuthIdentity) { n.Edges.Identity = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *AuthIdentityChannelQuery) loadIdentity(ctx context.Context, query *AuthIdentityQuery, nodes []*AuthIdentityChannel, init func(*AuthIdentityChannel), assign func(*AuthIdentityChannel, *AuthIdentity)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*AuthIdentityChannel)
for i := range nodes {
fk := nodes[i].IdentityID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(authidentity.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 "identity_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *AuthIdentityChannelQuery) 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 *AuthIdentityChannelQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(authidentitychannel.Table, authidentitychannel.Columns, sqlgraph.NewFieldSpec(authidentitychannel.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, authidentitychannel.FieldID)
for i := range fields {
if fields[i] != authidentitychannel.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withIdentity != nil {
_spec.Node.AddColumnOnce(authidentitychannel.FieldIdentityID)
}
}
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 *AuthIdentityChannelQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(authidentitychannel.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = authidentitychannel.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 *AuthIdentityChannelQuery) ForUpdate(opts ...sql.LockOption) *AuthIdentityChannelQuery {
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 *AuthIdentityChannelQuery) ForShare(opts ...sql.LockOption) *AuthIdentityChannelQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// AuthIdentityChannelGroupBy is the group-by builder for AuthIdentityChannel entities.
type AuthIdentityChannelGroupBy struct {
selector
build *AuthIdentityChannelQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *AuthIdentityChannelGroupBy) Aggregate(fns ...AggregateFunc) *AuthIdentityChannelGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *AuthIdentityChannelGroupBy) 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[*AuthIdentityChannelQuery, *AuthIdentityChannelGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *AuthIdentityChannelGroupBy) sqlScan(ctx context.Context, root *AuthIdentityChannelQuery, 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)
}
// AuthIdentityChannelSelect is the builder for selecting fields of AuthIdentityChannel entities.
type AuthIdentityChannelSelect struct {
*AuthIdentityChannelQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *AuthIdentityChannelSelect) Aggregate(fns ...AggregateFunc) *AuthIdentityChannelSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *AuthIdentityChannelSelect) 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[*AuthIdentityChannelQuery, *AuthIdentityChannelSelect](ctx, _s.AuthIdentityChannelQuery, _s, _s.inters, v)
}
func (_s *AuthIdentityChannelSelect) sqlScan(ctx context.Context, root *AuthIdentityChannelQuery, 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,581 @@
// 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/authidentity"
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// AuthIdentityChannelUpdate is the builder for updating AuthIdentityChannel entities.
type AuthIdentityChannelUpdate struct {
config
hooks []Hook
mutation *AuthIdentityChannelMutation
}
// Where appends a list predicates to the AuthIdentityChannelUpdate builder.
func (_u *AuthIdentityChannelUpdate) Where(ps ...predicate.AuthIdentityChannel) *AuthIdentityChannelUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AuthIdentityChannelUpdate) SetUpdatedAt(v time.Time) *AuthIdentityChannelUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetIdentityID sets the "identity_id" field.
func (_u *AuthIdentityChannelUpdate) SetIdentityID(v int64) *AuthIdentityChannelUpdate {
_u.mutation.SetIdentityID(v)
return _u
}
// SetNillableIdentityID sets the "identity_id" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableIdentityID(v *int64) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetIdentityID(*v)
}
return _u
}
// SetProviderType sets the "provider_type" field.
func (_u *AuthIdentityChannelUpdate) SetProviderType(v string) *AuthIdentityChannelUpdate {
_u.mutation.SetProviderType(v)
return _u
}
// SetNillableProviderType sets the "provider_type" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableProviderType(v *string) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetProviderType(*v)
}
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *AuthIdentityChannelUpdate) SetProviderKey(v string) *AuthIdentityChannelUpdate {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableProviderKey(v *string) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetChannel sets the "channel" field.
func (_u *AuthIdentityChannelUpdate) SetChannel(v string) *AuthIdentityChannelUpdate {
_u.mutation.SetChannel(v)
return _u
}
// SetNillableChannel sets the "channel" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableChannel(v *string) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetChannel(*v)
}
return _u
}
// SetChannelAppID sets the "channel_app_id" field.
func (_u *AuthIdentityChannelUpdate) SetChannelAppID(v string) *AuthIdentityChannelUpdate {
_u.mutation.SetChannelAppID(v)
return _u
}
// SetNillableChannelAppID sets the "channel_app_id" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableChannelAppID(v *string) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetChannelAppID(*v)
}
return _u
}
// SetChannelSubject sets the "channel_subject" field.
func (_u *AuthIdentityChannelUpdate) SetChannelSubject(v string) *AuthIdentityChannelUpdate {
_u.mutation.SetChannelSubject(v)
return _u
}
// SetNillableChannelSubject sets the "channel_subject" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdate) SetNillableChannelSubject(v *string) *AuthIdentityChannelUpdate {
if v != nil {
_u.SetChannelSubject(*v)
}
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuthIdentityChannelUpdate) SetMetadata(v map[string]interface{}) *AuthIdentityChannelUpdate {
_u.mutation.SetMetadata(v)
return _u
}
// SetIdentity sets the "identity" edge to the AuthIdentity entity.
func (_u *AuthIdentityChannelUpdate) SetIdentity(v *AuthIdentity) *AuthIdentityChannelUpdate {
return _u.SetIdentityID(v.ID)
}
// Mutation returns the AuthIdentityChannelMutation object of the builder.
func (_u *AuthIdentityChannelUpdate) Mutation() *AuthIdentityChannelMutation {
return _u.mutation
}
// ClearIdentity clears the "identity" edge to the AuthIdentity entity.
func (_u *AuthIdentityChannelUpdate) ClearIdentity() *AuthIdentityChannelUpdate {
_u.mutation.ClearIdentity()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *AuthIdentityChannelUpdate) 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 *AuthIdentityChannelUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *AuthIdentityChannelUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuthIdentityChannelUpdate) 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 *AuthIdentityChannelUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := authidentitychannel.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuthIdentityChannelUpdate) check() error {
if v, ok := _u.mutation.ProviderType(); ok {
if err := authidentitychannel.ProviderTypeValidator(v); err != nil {
return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_type": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderKey(); ok {
if err := authidentitychannel.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Channel(); ok {
if err := authidentitychannel.ChannelValidator(v); err != nil {
return &ValidationError{Name: "channel", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel": %w`, err)}
}
}
if v, ok := _u.mutation.ChannelAppID(); ok {
if err := authidentitychannel.ChannelAppIDValidator(v); err != nil {
return &ValidationError{Name: "channel_app_id", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_app_id": %w`, err)}
}
}
if v, ok := _u.mutation.ChannelSubject(); ok {
if err := authidentitychannel.ChannelSubjectValidator(v); err != nil {
return &ValidationError{Name: "channel_subject", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_subject": %w`, err)}
}
}
if _u.mutation.IdentityCleared() && len(_u.mutation.IdentityIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AuthIdentityChannel.identity"`)
}
return nil
}
func (_u *AuthIdentityChannelUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(authidentitychannel.Table, authidentitychannel.Columns, sqlgraph.NewFieldSpec(authidentitychannel.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(authidentitychannel.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.ProviderType(); ok {
_spec.SetField(authidentitychannel.FieldProviderType, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(authidentitychannel.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Channel(); ok {
_spec.SetField(authidentitychannel.FieldChannel, field.TypeString, value)
}
if value, ok := _u.mutation.ChannelAppID(); ok {
_spec.SetField(authidentitychannel.FieldChannelAppID, field.TypeString, value)
}
if value, ok := _u.mutation.ChannelSubject(); ok {
_spec.SetField(authidentitychannel.FieldChannelSubject, field.TypeString, value)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(authidentitychannel.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.IdentityCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentitychannel.IdentityTable,
Columns: []string{authidentitychannel.IdentityColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.IdentityIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentitychannel.IdentityTable,
Columns: []string{authidentitychannel.IdentityColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{authidentitychannel.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// AuthIdentityChannelUpdateOne is the builder for updating a single AuthIdentityChannel entity.
type AuthIdentityChannelUpdateOne struct {
config
fields []string
hooks []Hook
mutation *AuthIdentityChannelMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *AuthIdentityChannelUpdateOne) SetUpdatedAt(v time.Time) *AuthIdentityChannelUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetIdentityID sets the "identity_id" field.
func (_u *AuthIdentityChannelUpdateOne) SetIdentityID(v int64) *AuthIdentityChannelUpdateOne {
_u.mutation.SetIdentityID(v)
return _u
}
// SetNillableIdentityID sets the "identity_id" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableIdentityID(v *int64) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetIdentityID(*v)
}
return _u
}
// SetProviderType sets the "provider_type" field.
func (_u *AuthIdentityChannelUpdateOne) SetProviderType(v string) *AuthIdentityChannelUpdateOne {
_u.mutation.SetProviderType(v)
return _u
}
// SetNillableProviderType sets the "provider_type" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableProviderType(v *string) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetProviderType(*v)
}
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *AuthIdentityChannelUpdateOne) SetProviderKey(v string) *AuthIdentityChannelUpdateOne {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableProviderKey(v *string) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetChannel sets the "channel" field.
func (_u *AuthIdentityChannelUpdateOne) SetChannel(v string) *AuthIdentityChannelUpdateOne {
_u.mutation.SetChannel(v)
return _u
}
// SetNillableChannel sets the "channel" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableChannel(v *string) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetChannel(*v)
}
return _u
}
// SetChannelAppID sets the "channel_app_id" field.
func (_u *AuthIdentityChannelUpdateOne) SetChannelAppID(v string) *AuthIdentityChannelUpdateOne {
_u.mutation.SetChannelAppID(v)
return _u
}
// SetNillableChannelAppID sets the "channel_app_id" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableChannelAppID(v *string) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetChannelAppID(*v)
}
return _u
}
// SetChannelSubject sets the "channel_subject" field.
func (_u *AuthIdentityChannelUpdateOne) SetChannelSubject(v string) *AuthIdentityChannelUpdateOne {
_u.mutation.SetChannelSubject(v)
return _u
}
// SetNillableChannelSubject sets the "channel_subject" field if the given value is not nil.
func (_u *AuthIdentityChannelUpdateOne) SetNillableChannelSubject(v *string) *AuthIdentityChannelUpdateOne {
if v != nil {
_u.SetChannelSubject(*v)
}
return _u
}
// SetMetadata sets the "metadata" field.
func (_u *AuthIdentityChannelUpdateOne) SetMetadata(v map[string]interface{}) *AuthIdentityChannelUpdateOne {
_u.mutation.SetMetadata(v)
return _u
}
// SetIdentity sets the "identity" edge to the AuthIdentity entity.
func (_u *AuthIdentityChannelUpdateOne) SetIdentity(v *AuthIdentity) *AuthIdentityChannelUpdateOne {
return _u.SetIdentityID(v.ID)
}
// Mutation returns the AuthIdentityChannelMutation object of the builder.
func (_u *AuthIdentityChannelUpdateOne) Mutation() *AuthIdentityChannelMutation {
return _u.mutation
}
// ClearIdentity clears the "identity" edge to the AuthIdentity entity.
func (_u *AuthIdentityChannelUpdateOne) ClearIdentity() *AuthIdentityChannelUpdateOne {
_u.mutation.ClearIdentity()
return _u
}
// Where appends a list predicates to the AuthIdentityChannelUpdate builder.
func (_u *AuthIdentityChannelUpdateOne) Where(ps ...predicate.AuthIdentityChannel) *AuthIdentityChannelUpdateOne {
_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 *AuthIdentityChannelUpdateOne) Select(field string, fields ...string) *AuthIdentityChannelUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated AuthIdentityChannel entity.
func (_u *AuthIdentityChannelUpdateOne) Save(ctx context.Context) (*AuthIdentityChannel, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *AuthIdentityChannelUpdateOne) SaveX(ctx context.Context) *AuthIdentityChannel {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *AuthIdentityChannelUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *AuthIdentityChannelUpdateOne) 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 *AuthIdentityChannelUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := authidentitychannel.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *AuthIdentityChannelUpdateOne) check() error {
if v, ok := _u.mutation.ProviderType(); ok {
if err := authidentitychannel.ProviderTypeValidator(v); err != nil {
return &ValidationError{Name: "provider_type", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_type": %w`, err)}
}
}
if v, ok := _u.mutation.ProviderKey(); ok {
if err := authidentitychannel.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Channel(); ok {
if err := authidentitychannel.ChannelValidator(v); err != nil {
return &ValidationError{Name: "channel", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel": %w`, err)}
}
}
if v, ok := _u.mutation.ChannelAppID(); ok {
if err := authidentitychannel.ChannelAppIDValidator(v); err != nil {
return &ValidationError{Name: "channel_app_id", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_app_id": %w`, err)}
}
}
if v, ok := _u.mutation.ChannelSubject(); ok {
if err := authidentitychannel.ChannelSubjectValidator(v); err != nil {
return &ValidationError{Name: "channel_subject", err: fmt.Errorf(`ent: validator failed for field "AuthIdentityChannel.channel_subject": %w`, err)}
}
}
if _u.mutation.IdentityCleared() && len(_u.mutation.IdentityIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "AuthIdentityChannel.identity"`)
}
return nil
}
func (_u *AuthIdentityChannelUpdateOne) sqlSave(ctx context.Context) (_node *AuthIdentityChannel, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(authidentitychannel.Table, authidentitychannel.Columns, sqlgraph.NewFieldSpec(authidentitychannel.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthIdentityChannel.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, authidentitychannel.FieldID)
for _, f := range fields {
if !authidentitychannel.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != authidentitychannel.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(authidentitychannel.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.ProviderType(); ok {
_spec.SetField(authidentitychannel.FieldProviderType, field.TypeString, value)
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(authidentitychannel.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Channel(); ok {
_spec.SetField(authidentitychannel.FieldChannel, field.TypeString, value)
}
if value, ok := _u.mutation.ChannelAppID(); ok {
_spec.SetField(authidentitychannel.FieldChannelAppID, field.TypeString, value)
}
if value, ok := _u.mutation.ChannelSubject(); ok {
_spec.SetField(authidentitychannel.FieldChannelSubject, field.TypeString, value)
}
if value, ok := _u.mutation.Metadata(); ok {
_spec.SetField(authidentitychannel.FieldMetadata, field.TypeJSON, value)
}
if _u.mutation.IdentityCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentitychannel.IdentityTable,
Columns: []string{authidentitychannel.IdentityColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.IdentityIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: authidentitychannel.IdentityTable,
Columns: []string{authidentitychannel.IdentityColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(authidentity.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &AuthIdentityChannel{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{authidentitychannel.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

@@ -0,0 +1,359 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
)
// ChannelMonitor is the model entity for the ChannelMonitor schema.
type ChannelMonitor 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"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Provider holds the value of the "provider" field.
Provider channelmonitor.Provider `json:"provider,omitempty"`
// Provider base origin, e.g. https://api.openai.com
Endpoint string `json:"endpoint,omitempty"`
// AES-256-GCM encrypted API key
APIKeyEncrypted string `json:"-"`
// PrimaryModel holds the value of the "primary_model" field.
PrimaryModel string `json:"primary_model,omitempty"`
// Additional model names to test alongside primary_model
ExtraModels []string `json:"extra_models,omitempty"`
// GroupName holds the value of the "group_name" field.
GroupName string `json:"group_name,omitempty"`
// Enabled holds the value of the "enabled" field.
Enabled bool `json:"enabled,omitempty"`
// IntervalSeconds holds the value of the "interval_seconds" field.
IntervalSeconds int `json:"interval_seconds,omitempty"`
// LastCheckedAt holds the value of the "last_checked_at" field.
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
// CreatedBy holds the value of the "created_by" field.
CreatedBy int64 `json:"created_by,omitempty"`
// TemplateID holds the value of the "template_id" field.
TemplateID *int64 `json:"template_id,omitempty"`
// ExtraHeaders holds the value of the "extra_headers" field.
ExtraHeaders map[string]string `json:"extra_headers,omitempty"`
// BodyOverrideMode holds the value of the "body_override_mode" field.
BodyOverrideMode string `json:"body_override_mode,omitempty"`
// BodyOverride holds the value of the "body_override" field.
BodyOverride map[string]interface{} `json:"body_override,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ChannelMonitorQuery when eager-loading is set.
Edges ChannelMonitorEdges `json:"edges"`
selectValues sql.SelectValues
}
// ChannelMonitorEdges holds the relations/edges for other nodes in the graph.
type ChannelMonitorEdges struct {
// History holds the value of the history edge.
History []*ChannelMonitorHistory `json:"history,omitempty"`
// DailyRollups holds the value of the daily_rollups edge.
DailyRollups []*ChannelMonitorDailyRollup `json:"daily_rollups,omitempty"`
// RequestTemplate holds the value of the request_template edge.
RequestTemplate *ChannelMonitorRequestTemplate `json:"request_template,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [3]bool
}
// HistoryOrErr returns the History value or an error if the edge
// was not loaded in eager-loading.
func (e ChannelMonitorEdges) HistoryOrErr() ([]*ChannelMonitorHistory, error) {
if e.loadedTypes[0] {
return e.History, nil
}
return nil, &NotLoadedError{edge: "history"}
}
// DailyRollupsOrErr returns the DailyRollups value or an error if the edge
// was not loaded in eager-loading.
func (e ChannelMonitorEdges) DailyRollupsOrErr() ([]*ChannelMonitorDailyRollup, error) {
if e.loadedTypes[1] {
return e.DailyRollups, nil
}
return nil, &NotLoadedError{edge: "daily_rollups"}
}
// RequestTemplateOrErr returns the RequestTemplate value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e ChannelMonitorEdges) RequestTemplateOrErr() (*ChannelMonitorRequestTemplate, error) {
if e.RequestTemplate != nil {
return e.RequestTemplate, nil
} else if e.loadedTypes[2] {
return nil, &NotFoundError{label: channelmonitorrequesttemplate.Label}
}
return nil, &NotLoadedError{edge: "request_template"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*ChannelMonitor) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case channelmonitor.FieldExtraModels, channelmonitor.FieldExtraHeaders, channelmonitor.FieldBodyOverride:
values[i] = new([]byte)
case channelmonitor.FieldEnabled:
values[i] = new(sql.NullBool)
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
values[i] = new(sql.NullInt64)
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName, channelmonitor.FieldBodyOverrideMode:
values[i] = new(sql.NullString)
case channelmonitor.FieldCreatedAt, channelmonitor.FieldUpdatedAt, channelmonitor.FieldLastCheckedAt:
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 ChannelMonitor fields.
func (_m *ChannelMonitor) 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 channelmonitor.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 channelmonitor.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 channelmonitor.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 channelmonitor.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 channelmonitor.FieldProvider:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider", values[i])
} else if value.Valid {
_m.Provider = channelmonitor.Provider(value.String)
}
case channelmonitor.FieldEndpoint:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field endpoint", values[i])
} else if value.Valid {
_m.Endpoint = value.String
}
case channelmonitor.FieldAPIKeyEncrypted:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field api_key_encrypted", values[i])
} else if value.Valid {
_m.APIKeyEncrypted = value.String
}
case channelmonitor.FieldPrimaryModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field primary_model", values[i])
} else if value.Valid {
_m.PrimaryModel = value.String
}
case channelmonitor.FieldExtraModels:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field extra_models", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.ExtraModels); err != nil {
return fmt.Errorf("unmarshal field extra_models: %w", err)
}
}
case channelmonitor.FieldGroupName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field group_name", values[i])
} else if value.Valid {
_m.GroupName = value.String
}
case channelmonitor.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 channelmonitor.FieldIntervalSeconds:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field interval_seconds", values[i])
} else if value.Valid {
_m.IntervalSeconds = int(value.Int64)
}
case channelmonitor.FieldLastCheckedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field last_checked_at", values[i])
} else if value.Valid {
_m.LastCheckedAt = new(time.Time)
*_m.LastCheckedAt = value.Time
}
case channelmonitor.FieldCreatedBy:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field created_by", values[i])
} else if value.Valid {
_m.CreatedBy = value.Int64
}
case channelmonitor.FieldTemplateID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field template_id", values[i])
} else if value.Valid {
_m.TemplateID = new(int64)
*_m.TemplateID = value.Int64
}
case channelmonitor.FieldExtraHeaders:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field extra_headers", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.ExtraHeaders); err != nil {
return fmt.Errorf("unmarshal field extra_headers: %w", err)
}
}
case channelmonitor.FieldBodyOverrideMode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field body_override_mode", values[i])
} else if value.Valid {
_m.BodyOverrideMode = value.String
}
case channelmonitor.FieldBodyOverride:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field body_override", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.BodyOverride); err != nil {
return fmt.Errorf("unmarshal field body_override: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the ChannelMonitor.
// This includes values selected through modifiers, order, etc.
func (_m *ChannelMonitor) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryHistory queries the "history" edge of the ChannelMonitor entity.
func (_m *ChannelMonitor) QueryHistory() *ChannelMonitorHistoryQuery {
return NewChannelMonitorClient(_m.config).QueryHistory(_m)
}
// QueryDailyRollups queries the "daily_rollups" edge of the ChannelMonitor entity.
func (_m *ChannelMonitor) QueryDailyRollups() *ChannelMonitorDailyRollupQuery {
return NewChannelMonitorClient(_m.config).QueryDailyRollups(_m)
}
// QueryRequestTemplate queries the "request_template" edge of the ChannelMonitor entity.
func (_m *ChannelMonitor) QueryRequestTemplate() *ChannelMonitorRequestTemplateQuery {
return NewChannelMonitorClient(_m.config).QueryRequestTemplate(_m)
}
// Update returns a builder for updating this ChannelMonitor.
// Note that you need to call ChannelMonitor.Unwrap() before calling this method if this ChannelMonitor
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *ChannelMonitor) Update() *ChannelMonitorUpdateOne {
return NewChannelMonitorClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the ChannelMonitor 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 *ChannelMonitor) Unwrap() *ChannelMonitor {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: ChannelMonitor is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *ChannelMonitor) String() string {
var builder strings.Builder
builder.WriteString("ChannelMonitor(")
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("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("provider=")
builder.WriteString(fmt.Sprintf("%v", _m.Provider))
builder.WriteString(", ")
builder.WriteString("endpoint=")
builder.WriteString(_m.Endpoint)
builder.WriteString(", ")
builder.WriteString("api_key_encrypted=<sensitive>")
builder.WriteString(", ")
builder.WriteString("primary_model=")
builder.WriteString(_m.PrimaryModel)
builder.WriteString(", ")
builder.WriteString("extra_models=")
builder.WriteString(fmt.Sprintf("%v", _m.ExtraModels))
builder.WriteString(", ")
builder.WriteString("group_name=")
builder.WriteString(_m.GroupName)
builder.WriteString(", ")
builder.WriteString("enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.Enabled))
builder.WriteString(", ")
builder.WriteString("interval_seconds=")
builder.WriteString(fmt.Sprintf("%v", _m.IntervalSeconds))
builder.WriteString(", ")
if v := _m.LastCheckedAt; v != nil {
builder.WriteString("last_checked_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("created_by=")
builder.WriteString(fmt.Sprintf("%v", _m.CreatedBy))
builder.WriteString(", ")
if v := _m.TemplateID; v != nil {
builder.WriteString("template_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("extra_headers=")
builder.WriteString(fmt.Sprintf("%v", _m.ExtraHeaders))
builder.WriteString(", ")
builder.WriteString("body_override_mode=")
builder.WriteString(_m.BodyOverrideMode)
builder.WriteString(", ")
builder.WriteString("body_override=")
builder.WriteString(fmt.Sprintf("%v", _m.BodyOverride))
builder.WriteByte(')')
return builder.String()
}
// ChannelMonitors is a parsable slice of ChannelMonitor.
type ChannelMonitors []*ChannelMonitor

View File

@@ -0,0 +1,304 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitor
import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the channelmonitor type in the database.
Label = "channel_monitor"
// 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"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldProvider holds the string denoting the provider field in the database.
FieldProvider = "provider"
// FieldEndpoint holds the string denoting the endpoint field in the database.
FieldEndpoint = "endpoint"
// FieldAPIKeyEncrypted holds the string denoting the api_key_encrypted field in the database.
FieldAPIKeyEncrypted = "api_key_encrypted"
// FieldPrimaryModel holds the string denoting the primary_model field in the database.
FieldPrimaryModel = "primary_model"
// FieldExtraModels holds the string denoting the extra_models field in the database.
FieldExtraModels = "extra_models"
// FieldGroupName holds the string denoting the group_name field in the database.
FieldGroupName = "group_name"
// FieldEnabled holds the string denoting the enabled field in the database.
FieldEnabled = "enabled"
// FieldIntervalSeconds holds the string denoting the interval_seconds field in the database.
FieldIntervalSeconds = "interval_seconds"
// FieldLastCheckedAt holds the string denoting the last_checked_at field in the database.
FieldLastCheckedAt = "last_checked_at"
// FieldCreatedBy holds the string denoting the created_by field in the database.
FieldCreatedBy = "created_by"
// FieldTemplateID holds the string denoting the template_id field in the database.
FieldTemplateID = "template_id"
// FieldExtraHeaders holds the string denoting the extra_headers field in the database.
FieldExtraHeaders = "extra_headers"
// FieldBodyOverrideMode holds the string denoting the body_override_mode field in the database.
FieldBodyOverrideMode = "body_override_mode"
// FieldBodyOverride holds the string denoting the body_override field in the database.
FieldBodyOverride = "body_override"
// EdgeHistory holds the string denoting the history edge name in mutations.
EdgeHistory = "history"
// EdgeDailyRollups holds the string denoting the daily_rollups edge name in mutations.
EdgeDailyRollups = "daily_rollups"
// EdgeRequestTemplate holds the string denoting the request_template edge name in mutations.
EdgeRequestTemplate = "request_template"
// Table holds the table name of the channelmonitor in the database.
Table = "channel_monitors"
// HistoryTable is the table that holds the history relation/edge.
HistoryTable = "channel_monitor_histories"
// HistoryInverseTable is the table name for the ChannelMonitorHistory entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitorhistory" package.
HistoryInverseTable = "channel_monitor_histories"
// HistoryColumn is the table column denoting the history relation/edge.
HistoryColumn = "monitor_id"
// DailyRollupsTable is the table that holds the daily_rollups relation/edge.
DailyRollupsTable = "channel_monitor_daily_rollups"
// DailyRollupsInverseTable is the table name for the ChannelMonitorDailyRollup entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitordailyrollup" package.
DailyRollupsInverseTable = "channel_monitor_daily_rollups"
// DailyRollupsColumn is the table column denoting the daily_rollups relation/edge.
DailyRollupsColumn = "monitor_id"
// RequestTemplateTable is the table that holds the request_template relation/edge.
RequestTemplateTable = "channel_monitors"
// RequestTemplateInverseTable is the table name for the ChannelMonitorRequestTemplate entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitorrequesttemplate" package.
RequestTemplateInverseTable = "channel_monitor_request_templates"
// RequestTemplateColumn is the table column denoting the request_template relation/edge.
RequestTemplateColumn = "template_id"
)
// Columns holds all SQL columns for channelmonitor fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldName,
FieldProvider,
FieldEndpoint,
FieldAPIKeyEncrypted,
FieldPrimaryModel,
FieldExtraModels,
FieldGroupName,
FieldEnabled,
FieldIntervalSeconds,
FieldLastCheckedAt,
FieldCreatedBy,
FieldTemplateID,
FieldExtraHeaders,
FieldBodyOverrideMode,
FieldBodyOverride,
}
// 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
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
EndpointValidator func(string) error
// APIKeyEncryptedValidator is a validator for the "api_key_encrypted" field. It is called by the builders before save.
APIKeyEncryptedValidator func(string) error
// PrimaryModelValidator is a validator for the "primary_model" field. It is called by the builders before save.
PrimaryModelValidator func(string) error
// DefaultExtraModels holds the default value on creation for the "extra_models" field.
DefaultExtraModels []string
// DefaultGroupName holds the default value on creation for the "group_name" field.
DefaultGroupName string
// GroupNameValidator is a validator for the "group_name" field. It is called by the builders before save.
GroupNameValidator func(string) error
// DefaultEnabled holds the default value on creation for the "enabled" field.
DefaultEnabled bool
// IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
IntervalSecondsValidator func(int) error
// DefaultExtraHeaders holds the default value on creation for the "extra_headers" field.
DefaultExtraHeaders map[string]string
// DefaultBodyOverrideMode holds the default value on creation for the "body_override_mode" field.
DefaultBodyOverrideMode string
// BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
BodyOverrideModeValidator func(string) error
)
// Provider defines the type for the "provider" enum field.
type Provider string
// Provider values.
const (
ProviderOpenai Provider = "openai"
ProviderAnthropic Provider = "anthropic"
ProviderGemini Provider = "gemini"
)
func (pr Provider) String() string {
return string(pr)
}
// ProviderValidator is a validator for the "provider" field enum values. It is called by the builders before save.
func ProviderValidator(pr Provider) error {
switch pr {
case ProviderOpenai, ProviderAnthropic, ProviderGemini:
return nil
default:
return fmt.Errorf("channelmonitor: invalid enum value for provider field: %q", pr)
}
}
// OrderOption defines the ordering options for the ChannelMonitor 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()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByProvider orders the results by the provider field.
func ByProvider(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProvider, opts...).ToFunc()
}
// ByEndpoint orders the results by the endpoint field.
func ByEndpoint(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEndpoint, opts...).ToFunc()
}
// ByAPIKeyEncrypted orders the results by the api_key_encrypted field.
func ByAPIKeyEncrypted(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAPIKeyEncrypted, opts...).ToFunc()
}
// ByPrimaryModel orders the results by the primary_model field.
func ByPrimaryModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPrimaryModel, opts...).ToFunc()
}
// ByGroupName orders the results by the group_name field.
func ByGroupName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldGroupName, opts...).ToFunc()
}
// ByEnabled orders the results by the enabled field.
func ByEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEnabled, opts...).ToFunc()
}
// ByIntervalSeconds orders the results by the interval_seconds field.
func ByIntervalSeconds(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIntervalSeconds, opts...).ToFunc()
}
// ByLastCheckedAt orders the results by the last_checked_at field.
func ByLastCheckedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastCheckedAt, opts...).ToFunc()
}
// ByCreatedBy orders the results by the created_by field.
func ByCreatedBy(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedBy, opts...).ToFunc()
}
// ByTemplateID orders the results by the template_id field.
func ByTemplateID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTemplateID, opts...).ToFunc()
}
// ByBodyOverrideMode orders the results by the body_override_mode field.
func ByBodyOverrideMode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBodyOverrideMode, opts...).ToFunc()
}
// ByHistoryCount orders the results by history count.
func ByHistoryCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newHistoryStep(), opts...)
}
}
// ByHistory orders the results by history terms.
func ByHistory(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newHistoryStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByDailyRollupsCount orders the results by daily_rollups count.
func ByDailyRollupsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newDailyRollupsStep(), opts...)
}
}
// ByDailyRollups orders the results by daily_rollups terms.
func ByDailyRollups(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newDailyRollupsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByRequestTemplateField orders the results by request_template field.
func ByRequestTemplateField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newRequestTemplateStep(), sql.OrderByField(field, opts...))
}
}
func newHistoryStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(HistoryInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, HistoryTable, HistoryColumn),
)
}
func newDailyRollupsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(DailyRollupsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DailyRollupsTable, DailyRollupsColumn),
)
}
func newRequestTemplateStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(RequestTemplateInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, RequestTemplateTable, RequestTemplateColumn),
)
}

View File

@@ -0,0 +1,885 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitor
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(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.ChannelMonitor {
return predicate.ChannelMonitor(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.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldUpdatedAt, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldName, v))
}
// Endpoint applies equality check predicate on the "endpoint" field. It's identical to EndpointEQ.
func Endpoint(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
}
// APIKeyEncrypted applies equality check predicate on the "api_key_encrypted" field. It's identical to APIKeyEncryptedEQ.
func APIKeyEncrypted(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIKeyEncrypted, v))
}
// PrimaryModel applies equality check predicate on the "primary_model" field. It's identical to PrimaryModelEQ.
func PrimaryModel(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldPrimaryModel, v))
}
// GroupName applies equality check predicate on the "group_name" field. It's identical to GroupNameEQ.
func GroupName(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldGroupName, v))
}
// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ.
func Enabled(v bool) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldEnabled, v))
}
// IntervalSeconds applies equality check predicate on the "interval_seconds" field. It's identical to IntervalSecondsEQ.
func IntervalSeconds(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
}
// LastCheckedAt applies equality check predicate on the "last_checked_at" field. It's identical to LastCheckedAtEQ.
func LastCheckedAt(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
}
// CreatedBy applies equality check predicate on the "created_by" field. It's identical to CreatedByEQ.
func CreatedBy(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedBy, v))
}
// TemplateID applies equality check predicate on the "template_id" field. It's identical to TemplateIDEQ.
func TemplateID(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldTemplateID, v))
}
// BodyOverrideMode applies equality check predicate on the "body_override_mode" field. It's identical to BodyOverrideModeEQ.
func BodyOverrideMode(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldBodyOverrideMode, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldUpdatedAt, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldName, v))
}
// ProviderEQ applies the EQ predicate on the "provider" field.
func ProviderEQ(v Provider) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldProvider, v))
}
// ProviderNEQ applies the NEQ predicate on the "provider" field.
func ProviderNEQ(v Provider) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldProvider, v))
}
// ProviderIn applies the In predicate on the "provider" field.
func ProviderIn(vs ...Provider) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldProvider, vs...))
}
// ProviderNotIn applies the NotIn predicate on the "provider" field.
func ProviderNotIn(vs ...Provider) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldProvider, vs...))
}
// EndpointEQ applies the EQ predicate on the "endpoint" field.
func EndpointEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
}
// EndpointNEQ applies the NEQ predicate on the "endpoint" field.
func EndpointNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldEndpoint, v))
}
// EndpointIn applies the In predicate on the "endpoint" field.
func EndpointIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldEndpoint, vs...))
}
// EndpointNotIn applies the NotIn predicate on the "endpoint" field.
func EndpointNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldEndpoint, vs...))
}
// EndpointGT applies the GT predicate on the "endpoint" field.
func EndpointGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldEndpoint, v))
}
// EndpointGTE applies the GTE predicate on the "endpoint" field.
func EndpointGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldEndpoint, v))
}
// EndpointLT applies the LT predicate on the "endpoint" field.
func EndpointLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldEndpoint, v))
}
// EndpointLTE applies the LTE predicate on the "endpoint" field.
func EndpointLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldEndpoint, v))
}
// EndpointContains applies the Contains predicate on the "endpoint" field.
func EndpointContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldEndpoint, v))
}
// EndpointHasPrefix applies the HasPrefix predicate on the "endpoint" field.
func EndpointHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldEndpoint, v))
}
// EndpointHasSuffix applies the HasSuffix predicate on the "endpoint" field.
func EndpointHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldEndpoint, v))
}
// EndpointEqualFold applies the EqualFold predicate on the "endpoint" field.
func EndpointEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldEndpoint, v))
}
// EndpointContainsFold applies the ContainsFold predicate on the "endpoint" field.
func EndpointContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldEndpoint, v))
}
// APIKeyEncryptedEQ applies the EQ predicate on the "api_key_encrypted" field.
func APIKeyEncryptedEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedNEQ applies the NEQ predicate on the "api_key_encrypted" field.
func APIKeyEncryptedNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedIn applies the In predicate on the "api_key_encrypted" field.
func APIKeyEncryptedIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldAPIKeyEncrypted, vs...))
}
// APIKeyEncryptedNotIn applies the NotIn predicate on the "api_key_encrypted" field.
func APIKeyEncryptedNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldAPIKeyEncrypted, vs...))
}
// APIKeyEncryptedGT applies the GT predicate on the "api_key_encrypted" field.
func APIKeyEncryptedGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedGTE applies the GTE predicate on the "api_key_encrypted" field.
func APIKeyEncryptedGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedLT applies the LT predicate on the "api_key_encrypted" field.
func APIKeyEncryptedLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedLTE applies the LTE predicate on the "api_key_encrypted" field.
func APIKeyEncryptedLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedContains applies the Contains predicate on the "api_key_encrypted" field.
func APIKeyEncryptedContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedHasPrefix applies the HasPrefix predicate on the "api_key_encrypted" field.
func APIKeyEncryptedHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedHasSuffix applies the HasSuffix predicate on the "api_key_encrypted" field.
func APIKeyEncryptedHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedEqualFold applies the EqualFold predicate on the "api_key_encrypted" field.
func APIKeyEncryptedEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldAPIKeyEncrypted, v))
}
// APIKeyEncryptedContainsFold applies the ContainsFold predicate on the "api_key_encrypted" field.
func APIKeyEncryptedContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldAPIKeyEncrypted, v))
}
// PrimaryModelEQ applies the EQ predicate on the "primary_model" field.
func PrimaryModelEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldPrimaryModel, v))
}
// PrimaryModelNEQ applies the NEQ predicate on the "primary_model" field.
func PrimaryModelNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldPrimaryModel, v))
}
// PrimaryModelIn applies the In predicate on the "primary_model" field.
func PrimaryModelIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldPrimaryModel, vs...))
}
// PrimaryModelNotIn applies the NotIn predicate on the "primary_model" field.
func PrimaryModelNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldPrimaryModel, vs...))
}
// PrimaryModelGT applies the GT predicate on the "primary_model" field.
func PrimaryModelGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldPrimaryModel, v))
}
// PrimaryModelGTE applies the GTE predicate on the "primary_model" field.
func PrimaryModelGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldPrimaryModel, v))
}
// PrimaryModelLT applies the LT predicate on the "primary_model" field.
func PrimaryModelLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldPrimaryModel, v))
}
// PrimaryModelLTE applies the LTE predicate on the "primary_model" field.
func PrimaryModelLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldPrimaryModel, v))
}
// PrimaryModelContains applies the Contains predicate on the "primary_model" field.
func PrimaryModelContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldPrimaryModel, v))
}
// PrimaryModelHasPrefix applies the HasPrefix predicate on the "primary_model" field.
func PrimaryModelHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldPrimaryModel, v))
}
// PrimaryModelHasSuffix applies the HasSuffix predicate on the "primary_model" field.
func PrimaryModelHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldPrimaryModel, v))
}
// PrimaryModelEqualFold applies the EqualFold predicate on the "primary_model" field.
func PrimaryModelEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldPrimaryModel, v))
}
// PrimaryModelContainsFold applies the ContainsFold predicate on the "primary_model" field.
func PrimaryModelContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldPrimaryModel, v))
}
// GroupNameEQ applies the EQ predicate on the "group_name" field.
func GroupNameEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldGroupName, v))
}
// GroupNameNEQ applies the NEQ predicate on the "group_name" field.
func GroupNameNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldGroupName, v))
}
// GroupNameIn applies the In predicate on the "group_name" field.
func GroupNameIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldGroupName, vs...))
}
// GroupNameNotIn applies the NotIn predicate on the "group_name" field.
func GroupNameNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldGroupName, vs...))
}
// GroupNameGT applies the GT predicate on the "group_name" field.
func GroupNameGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldGroupName, v))
}
// GroupNameGTE applies the GTE predicate on the "group_name" field.
func GroupNameGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldGroupName, v))
}
// GroupNameLT applies the LT predicate on the "group_name" field.
func GroupNameLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldGroupName, v))
}
// GroupNameLTE applies the LTE predicate on the "group_name" field.
func GroupNameLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldGroupName, v))
}
// GroupNameContains applies the Contains predicate on the "group_name" field.
func GroupNameContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldGroupName, v))
}
// GroupNameHasPrefix applies the HasPrefix predicate on the "group_name" field.
func GroupNameHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldGroupName, v))
}
// GroupNameHasSuffix applies the HasSuffix predicate on the "group_name" field.
func GroupNameHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldGroupName, v))
}
// GroupNameIsNil applies the IsNil predicate on the "group_name" field.
func GroupNameIsNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIsNull(FieldGroupName))
}
// GroupNameNotNil applies the NotNil predicate on the "group_name" field.
func GroupNameNotNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotNull(FieldGroupName))
}
// GroupNameEqualFold applies the EqualFold predicate on the "group_name" field.
func GroupNameEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldGroupName, v))
}
// GroupNameContainsFold applies the ContainsFold predicate on the "group_name" field.
func GroupNameContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldGroupName, v))
}
// EnabledEQ applies the EQ predicate on the "enabled" field.
func EnabledEQ(v bool) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldEnabled, v))
}
// EnabledNEQ applies the NEQ predicate on the "enabled" field.
func EnabledNEQ(v bool) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldEnabled, v))
}
// IntervalSecondsEQ applies the EQ predicate on the "interval_seconds" field.
func IntervalSecondsEQ(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
}
// IntervalSecondsNEQ applies the NEQ predicate on the "interval_seconds" field.
func IntervalSecondsNEQ(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldIntervalSeconds, v))
}
// IntervalSecondsIn applies the In predicate on the "interval_seconds" field.
func IntervalSecondsIn(vs ...int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldIntervalSeconds, vs...))
}
// IntervalSecondsNotIn applies the NotIn predicate on the "interval_seconds" field.
func IntervalSecondsNotIn(vs ...int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldIntervalSeconds, vs...))
}
// IntervalSecondsGT applies the GT predicate on the "interval_seconds" field.
func IntervalSecondsGT(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldIntervalSeconds, v))
}
// IntervalSecondsGTE applies the GTE predicate on the "interval_seconds" field.
func IntervalSecondsGTE(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldIntervalSeconds, v))
}
// IntervalSecondsLT applies the LT predicate on the "interval_seconds" field.
func IntervalSecondsLT(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldIntervalSeconds, v))
}
// IntervalSecondsLTE applies the LTE predicate on the "interval_seconds" field.
func IntervalSecondsLTE(v int) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldIntervalSeconds, v))
}
// LastCheckedAtEQ applies the EQ predicate on the "last_checked_at" field.
func LastCheckedAtEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
}
// LastCheckedAtNEQ applies the NEQ predicate on the "last_checked_at" field.
func LastCheckedAtNEQ(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldLastCheckedAt, v))
}
// LastCheckedAtIn applies the In predicate on the "last_checked_at" field.
func LastCheckedAtIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldLastCheckedAt, vs...))
}
// LastCheckedAtNotIn applies the NotIn predicate on the "last_checked_at" field.
func LastCheckedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldLastCheckedAt, vs...))
}
// LastCheckedAtGT applies the GT predicate on the "last_checked_at" field.
func LastCheckedAtGT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldLastCheckedAt, v))
}
// LastCheckedAtGTE applies the GTE predicate on the "last_checked_at" field.
func LastCheckedAtGTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldLastCheckedAt, v))
}
// LastCheckedAtLT applies the LT predicate on the "last_checked_at" field.
func LastCheckedAtLT(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldLastCheckedAt, v))
}
// LastCheckedAtLTE applies the LTE predicate on the "last_checked_at" field.
func LastCheckedAtLTE(v time.Time) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldLastCheckedAt, v))
}
// LastCheckedAtIsNil applies the IsNil predicate on the "last_checked_at" field.
func LastCheckedAtIsNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIsNull(FieldLastCheckedAt))
}
// LastCheckedAtNotNil applies the NotNil predicate on the "last_checked_at" field.
func LastCheckedAtNotNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotNull(FieldLastCheckedAt))
}
// CreatedByEQ applies the EQ predicate on the "created_by" field.
func CreatedByEQ(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedBy, v))
}
// CreatedByNEQ applies the NEQ predicate on the "created_by" field.
func CreatedByNEQ(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldCreatedBy, v))
}
// CreatedByIn applies the In predicate on the "created_by" field.
func CreatedByIn(vs ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldCreatedBy, vs...))
}
// CreatedByNotIn applies the NotIn predicate on the "created_by" field.
func CreatedByNotIn(vs ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldCreatedBy, vs...))
}
// CreatedByGT applies the GT predicate on the "created_by" field.
func CreatedByGT(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldCreatedBy, v))
}
// CreatedByGTE applies the GTE predicate on the "created_by" field.
func CreatedByGTE(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldCreatedBy, v))
}
// CreatedByLT applies the LT predicate on the "created_by" field.
func CreatedByLT(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldCreatedBy, v))
}
// CreatedByLTE applies the LTE predicate on the "created_by" field.
func CreatedByLTE(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldCreatedBy, v))
}
// TemplateIDEQ applies the EQ predicate on the "template_id" field.
func TemplateIDEQ(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldTemplateID, v))
}
// TemplateIDNEQ applies the NEQ predicate on the "template_id" field.
func TemplateIDNEQ(v int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldTemplateID, v))
}
// TemplateIDIn applies the In predicate on the "template_id" field.
func TemplateIDIn(vs ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldTemplateID, vs...))
}
// TemplateIDNotIn applies the NotIn predicate on the "template_id" field.
func TemplateIDNotIn(vs ...int64) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldTemplateID, vs...))
}
// TemplateIDIsNil applies the IsNil predicate on the "template_id" field.
func TemplateIDIsNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIsNull(FieldTemplateID))
}
// TemplateIDNotNil applies the NotNil predicate on the "template_id" field.
func TemplateIDNotNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotNull(FieldTemplateID))
}
// BodyOverrideModeEQ applies the EQ predicate on the "body_override_mode" field.
func BodyOverrideModeEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEQ(FieldBodyOverrideMode, v))
}
// BodyOverrideModeNEQ applies the NEQ predicate on the "body_override_mode" field.
func BodyOverrideModeNEQ(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNEQ(FieldBodyOverrideMode, v))
}
// BodyOverrideModeIn applies the In predicate on the "body_override_mode" field.
func BodyOverrideModeIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIn(FieldBodyOverrideMode, vs...))
}
// BodyOverrideModeNotIn applies the NotIn predicate on the "body_override_mode" field.
func BodyOverrideModeNotIn(vs ...string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotIn(FieldBodyOverrideMode, vs...))
}
// BodyOverrideModeGT applies the GT predicate on the "body_override_mode" field.
func BodyOverrideModeGT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGT(FieldBodyOverrideMode, v))
}
// BodyOverrideModeGTE applies the GTE predicate on the "body_override_mode" field.
func BodyOverrideModeGTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldGTE(FieldBodyOverrideMode, v))
}
// BodyOverrideModeLT applies the LT predicate on the "body_override_mode" field.
func BodyOverrideModeLT(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLT(FieldBodyOverrideMode, v))
}
// BodyOverrideModeLTE applies the LTE predicate on the "body_override_mode" field.
func BodyOverrideModeLTE(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldLTE(FieldBodyOverrideMode, v))
}
// BodyOverrideModeContains applies the Contains predicate on the "body_override_mode" field.
func BodyOverrideModeContains(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContains(FieldBodyOverrideMode, v))
}
// BodyOverrideModeHasPrefix applies the HasPrefix predicate on the "body_override_mode" field.
func BodyOverrideModeHasPrefix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldBodyOverrideMode, v))
}
// BodyOverrideModeHasSuffix applies the HasSuffix predicate on the "body_override_mode" field.
func BodyOverrideModeHasSuffix(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldBodyOverrideMode, v))
}
// BodyOverrideModeEqualFold applies the EqualFold predicate on the "body_override_mode" field.
func BodyOverrideModeEqualFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldBodyOverrideMode, v))
}
// BodyOverrideModeContainsFold applies the ContainsFold predicate on the "body_override_mode" field.
func BodyOverrideModeContainsFold(v string) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldBodyOverrideMode, v))
}
// BodyOverrideIsNil applies the IsNil predicate on the "body_override" field.
func BodyOverrideIsNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldIsNull(FieldBodyOverride))
}
// BodyOverrideNotNil applies the NotNil predicate on the "body_override" field.
func BodyOverrideNotNil() predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.FieldNotNull(FieldBodyOverride))
}
// HasHistory applies the HasEdge predicate on the "history" edge.
func HasHistory() predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, HistoryTable, HistoryColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasHistoryWith applies the HasEdge predicate on the "history" edge with a given conditions (other predicates).
func HasHistoryWith(preds ...predicate.ChannelMonitorHistory) predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := newHistoryStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasDailyRollups applies the HasEdge predicate on the "daily_rollups" edge.
func HasDailyRollups() predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, DailyRollupsTable, DailyRollupsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasDailyRollupsWith applies the HasEdge predicate on the "daily_rollups" edge with a given conditions (other predicates).
func HasDailyRollupsWith(preds ...predicate.ChannelMonitorDailyRollup) predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := newDailyRollupsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasRequestTemplate applies the HasEdge predicate on the "request_template" edge.
func HasRequestTemplate() predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, RequestTemplateTable, RequestTemplateColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasRequestTemplateWith applies the HasEdge predicate on the "request_template" edge with a given conditions (other predicates).
func HasRequestTemplateWith(preds ...predicate.ChannelMonitorRequestTemplate) predicate.ChannelMonitor {
return predicate.ChannelMonitor(func(s *sql.Selector) {
step := newRequestTemplateStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.ChannelMonitor) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.ChannelMonitor) predicate.ChannelMonitor {
return predicate.ChannelMonitor(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.ChannelMonitor) predicate.ChannelMonitor {
return predicate.ChannelMonitor(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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorDelete is the builder for deleting a ChannelMonitor entity.
type ChannelMonitorDelete struct {
config
hooks []Hook
mutation *ChannelMonitorMutation
}
// Where appends a list predicates to the ChannelMonitorDelete builder.
func (_d *ChannelMonitorDelete) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *ChannelMonitorDelete) 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 *ChannelMonitorDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *ChannelMonitorDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(channelmonitor.Table, sqlgraph.NewFieldSpec(channelmonitor.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
}
// ChannelMonitorDeleteOne is the builder for deleting a single ChannelMonitor entity.
type ChannelMonitorDeleteOne struct {
_d *ChannelMonitorDelete
}
// Where appends a list predicates to the ChannelMonitorDelete builder.
func (_d *ChannelMonitorDeleteOne) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *ChannelMonitorDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{channelmonitor.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *ChannelMonitorDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,797 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"database/sql/driver"
"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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorQuery is the builder for querying ChannelMonitor entities.
type ChannelMonitorQuery struct {
config
ctx *QueryContext
order []channelmonitor.OrderOption
inters []Interceptor
predicates []predicate.ChannelMonitor
withHistory *ChannelMonitorHistoryQuery
withDailyRollups *ChannelMonitorDailyRollupQuery
withRequestTemplate *ChannelMonitorRequestTemplateQuery
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 ChannelMonitorQuery builder.
func (_q *ChannelMonitorQuery) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *ChannelMonitorQuery) Limit(limit int) *ChannelMonitorQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *ChannelMonitorQuery) Offset(offset int) *ChannelMonitorQuery {
_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 *ChannelMonitorQuery) Unique(unique bool) *ChannelMonitorQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *ChannelMonitorQuery) Order(o ...channelmonitor.OrderOption) *ChannelMonitorQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryHistory chains the current query on the "history" edge.
func (_q *ChannelMonitorQuery) QueryHistory() *ChannelMonitorHistoryQuery {
query := (&ChannelMonitorHistoryClient{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(channelmonitor.Table, channelmonitor.FieldID, selector),
sqlgraph.To(channelmonitorhistory.Table, channelmonitorhistory.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, channelmonitor.HistoryTable, channelmonitor.HistoryColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryDailyRollups chains the current query on the "daily_rollups" edge.
func (_q *ChannelMonitorQuery) QueryDailyRollups() *ChannelMonitorDailyRollupQuery {
query := (&ChannelMonitorDailyRollupClient{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(channelmonitor.Table, channelmonitor.FieldID, selector),
sqlgraph.To(channelmonitordailyrollup.Table, channelmonitordailyrollup.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, channelmonitor.DailyRollupsTable, channelmonitor.DailyRollupsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryRequestTemplate chains the current query on the "request_template" edge.
func (_q *ChannelMonitorQuery) QueryRequestTemplate() *ChannelMonitorRequestTemplateQuery {
query := (&ChannelMonitorRequestTemplateClient{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(channelmonitor.Table, channelmonitor.FieldID, selector),
sqlgraph.To(channelmonitorrequesttemplate.Table, channelmonitorrequesttemplate.FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, channelmonitor.RequestTemplateTable, channelmonitor.RequestTemplateColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first ChannelMonitor entity from the query.
// Returns a *NotFoundError when no ChannelMonitor was found.
func (_q *ChannelMonitorQuery) First(ctx context.Context) (*ChannelMonitor, 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{channelmonitor.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *ChannelMonitorQuery) FirstX(ctx context.Context) *ChannelMonitor {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first ChannelMonitor ID from the query.
// Returns a *NotFoundError when no ChannelMonitor ID was found.
func (_q *ChannelMonitorQuery) 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{channelmonitor.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *ChannelMonitorQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single ChannelMonitor entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one ChannelMonitor entity is found.
// Returns a *NotFoundError when no ChannelMonitor entities are found.
func (_q *ChannelMonitorQuery) Only(ctx context.Context) (*ChannelMonitor, 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{channelmonitor.Label}
default:
return nil, &NotSingularError{channelmonitor.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *ChannelMonitorQuery) OnlyX(ctx context.Context) *ChannelMonitor {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only ChannelMonitor ID in the query.
// Returns a *NotSingularError when more than one ChannelMonitor ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *ChannelMonitorQuery) 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{channelmonitor.Label}
default:
err = &NotSingularError{channelmonitor.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *ChannelMonitorQuery) 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 ChannelMonitors.
func (_q *ChannelMonitorQuery) All(ctx context.Context) ([]*ChannelMonitor, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*ChannelMonitor, *ChannelMonitorQuery]()
return withInterceptors[[]*ChannelMonitor](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *ChannelMonitorQuery) AllX(ctx context.Context) []*ChannelMonitor {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of ChannelMonitor IDs.
func (_q *ChannelMonitorQuery) 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(channelmonitor.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *ChannelMonitorQuery) 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 *ChannelMonitorQuery) 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[*ChannelMonitorQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *ChannelMonitorQuery) 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 *ChannelMonitorQuery) 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 *ChannelMonitorQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the ChannelMonitorQuery 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 *ChannelMonitorQuery) Clone() *ChannelMonitorQuery {
if _q == nil {
return nil
}
return &ChannelMonitorQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]channelmonitor.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.ChannelMonitor{}, _q.predicates...),
withHistory: _q.withHistory.Clone(),
withDailyRollups: _q.withDailyRollups.Clone(),
withRequestTemplate: _q.withRequestTemplate.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithHistory tells the query-builder to eager-load the nodes that are connected to
// the "history" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ChannelMonitorQuery) WithHistory(opts ...func(*ChannelMonitorHistoryQuery)) *ChannelMonitorQuery {
query := (&ChannelMonitorHistoryClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withHistory = query
return _q
}
// WithDailyRollups tells the query-builder to eager-load the nodes that are connected to
// the "daily_rollups" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ChannelMonitorQuery) WithDailyRollups(opts ...func(*ChannelMonitorDailyRollupQuery)) *ChannelMonitorQuery {
query := (&ChannelMonitorDailyRollupClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withDailyRollups = query
return _q
}
// WithRequestTemplate tells the query-builder to eager-load the nodes that are connected to
// the "request_template" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ChannelMonitorQuery) WithRequestTemplate(opts ...func(*ChannelMonitorRequestTemplateQuery)) *ChannelMonitorQuery {
query := (&ChannelMonitorRequestTemplateClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withRequestTemplate = 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 {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.ChannelMonitor.Query().
// GroupBy(channelmonitor.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *ChannelMonitorQuery) GroupBy(field string, fields ...string) *ChannelMonitorGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &ChannelMonitorGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = channelmonitor.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.ChannelMonitor.Query().
// Select(channelmonitor.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *ChannelMonitorQuery) Select(fields ...string) *ChannelMonitorSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &ChannelMonitorSelect{ChannelMonitorQuery: _q}
sbuild.label = channelmonitor.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a ChannelMonitorSelect configured with the given aggregations.
func (_q *ChannelMonitorQuery) Aggregate(fns ...AggregateFunc) *ChannelMonitorSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *ChannelMonitorQuery) 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 !channelmonitor.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 *ChannelMonitorQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ChannelMonitor, error) {
var (
nodes = []*ChannelMonitor{}
_spec = _q.querySpec()
loadedTypes = [3]bool{
_q.withHistory != nil,
_q.withDailyRollups != nil,
_q.withRequestTemplate != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*ChannelMonitor).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &ChannelMonitor{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.withHistory; query != nil {
if err := _q.loadHistory(ctx, query, nodes,
func(n *ChannelMonitor) { n.Edges.History = []*ChannelMonitorHistory{} },
func(n *ChannelMonitor, e *ChannelMonitorHistory) { n.Edges.History = append(n.Edges.History, e) }); err != nil {
return nil, err
}
}
if query := _q.withDailyRollups; query != nil {
if err := _q.loadDailyRollups(ctx, query, nodes,
func(n *ChannelMonitor) { n.Edges.DailyRollups = []*ChannelMonitorDailyRollup{} },
func(n *ChannelMonitor, e *ChannelMonitorDailyRollup) {
n.Edges.DailyRollups = append(n.Edges.DailyRollups, e)
}); err != nil {
return nil, err
}
}
if query := _q.withRequestTemplate; query != nil {
if err := _q.loadRequestTemplate(ctx, query, nodes, nil,
func(n *ChannelMonitor, e *ChannelMonitorRequestTemplate) { n.Edges.RequestTemplate = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *ChannelMonitorQuery) loadHistory(ctx context.Context, query *ChannelMonitorHistoryQuery, nodes []*ChannelMonitor, init func(*ChannelMonitor), assign func(*ChannelMonitor, *ChannelMonitorHistory)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*ChannelMonitor)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(channelmonitorhistory.FieldMonitorID)
}
query.Where(predicate.ChannelMonitorHistory(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(channelmonitor.HistoryColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.MonitorID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "monitor_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *ChannelMonitorQuery) loadDailyRollups(ctx context.Context, query *ChannelMonitorDailyRollupQuery, nodes []*ChannelMonitor, init func(*ChannelMonitor), assign func(*ChannelMonitor, *ChannelMonitorDailyRollup)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*ChannelMonitor)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(channelmonitordailyrollup.FieldMonitorID)
}
query.Where(predicate.ChannelMonitorDailyRollup(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(channelmonitor.DailyRollupsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.MonitorID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "monitor_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *ChannelMonitorQuery) loadRequestTemplate(ctx context.Context, query *ChannelMonitorRequestTemplateQuery, nodes []*ChannelMonitor, init func(*ChannelMonitor), assign func(*ChannelMonitor, *ChannelMonitorRequestTemplate)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*ChannelMonitor)
for i := range nodes {
if nodes[i].TemplateID == nil {
continue
}
fk := *nodes[i].TemplateID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(channelmonitorrequesttemplate.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 "template_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *ChannelMonitorQuery) 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 *ChannelMonitorQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(channelmonitor.Table, channelmonitor.Columns, sqlgraph.NewFieldSpec(channelmonitor.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, channelmonitor.FieldID)
for i := range fields {
if fields[i] != channelmonitor.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withRequestTemplate != nil {
_spec.Node.AddColumnOnce(channelmonitor.FieldTemplateID)
}
}
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 *ChannelMonitorQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(channelmonitor.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = channelmonitor.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 *ChannelMonitorQuery) ForUpdate(opts ...sql.LockOption) *ChannelMonitorQuery {
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 *ChannelMonitorQuery) ForShare(opts ...sql.LockOption) *ChannelMonitorQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// ChannelMonitorGroupBy is the group-by builder for ChannelMonitor entities.
type ChannelMonitorGroupBy struct {
selector
build *ChannelMonitorQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *ChannelMonitorGroupBy) Aggregate(fns ...AggregateFunc) *ChannelMonitorGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *ChannelMonitorGroupBy) 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[*ChannelMonitorQuery, *ChannelMonitorGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *ChannelMonitorGroupBy) sqlScan(ctx context.Context, root *ChannelMonitorQuery, 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)
}
// ChannelMonitorSelect is the builder for selecting fields of ChannelMonitor entities.
type ChannelMonitorSelect struct {
*ChannelMonitorQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *ChannelMonitorSelect) Aggregate(fns ...AggregateFunc) *ChannelMonitorSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *ChannelMonitorSelect) 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[*ChannelMonitorQuery, *ChannelMonitorSelect](ctx, _s.ChannelMonitorQuery, _s, _s.inters, v)
}
func (_s *ChannelMonitorSelect) sqlScan(ctx context.Context, root *ChannelMonitorQuery, 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,278 @@
// 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
)
// ChannelMonitorDailyRollup is the model entity for the ChannelMonitorDailyRollup schema.
type ChannelMonitorDailyRollup struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// MonitorID holds the value of the "monitor_id" field.
MonitorID int64 `json:"monitor_id,omitempty"`
// Model holds the value of the "model" field.
Model string `json:"model,omitempty"`
// BucketDate holds the value of the "bucket_date" field.
BucketDate time.Time `json:"bucket_date,omitempty"`
// TotalChecks holds the value of the "total_checks" field.
TotalChecks int `json:"total_checks,omitempty"`
// OkCount holds the value of the "ok_count" field.
OkCount int `json:"ok_count,omitempty"`
// OperationalCount holds the value of the "operational_count" field.
OperationalCount int `json:"operational_count,omitempty"`
// DegradedCount holds the value of the "degraded_count" field.
DegradedCount int `json:"degraded_count,omitempty"`
// FailedCount holds the value of the "failed_count" field.
FailedCount int `json:"failed_count,omitempty"`
// ErrorCount holds the value of the "error_count" field.
ErrorCount int `json:"error_count,omitempty"`
// SumLatencyMs holds the value of the "sum_latency_ms" field.
SumLatencyMs int64 `json:"sum_latency_ms,omitempty"`
// CountLatency holds the value of the "count_latency" field.
CountLatency int `json:"count_latency,omitempty"`
// SumPingLatencyMs holds the value of the "sum_ping_latency_ms" field.
SumPingLatencyMs int64 `json:"sum_ping_latency_ms,omitempty"`
// CountPingLatency holds the value of the "count_ping_latency" field.
CountPingLatency int `json:"count_ping_latency,omitempty"`
// ComputedAt holds the value of the "computed_at" field.
ComputedAt time.Time `json:"computed_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ChannelMonitorDailyRollupQuery when eager-loading is set.
Edges ChannelMonitorDailyRollupEdges `json:"edges"`
selectValues sql.SelectValues
}
// ChannelMonitorDailyRollupEdges holds the relations/edges for other nodes in the graph.
type ChannelMonitorDailyRollupEdges struct {
// Monitor holds the value of the monitor edge.
Monitor *ChannelMonitor `json:"monitor,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// MonitorOrErr returns the Monitor value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e ChannelMonitorDailyRollupEdges) MonitorOrErr() (*ChannelMonitor, error) {
if e.Monitor != nil {
return e.Monitor, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: channelmonitor.Label}
}
return nil, &NotLoadedError{edge: "monitor"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*ChannelMonitorDailyRollup) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case channelmonitordailyrollup.FieldID, channelmonitordailyrollup.FieldMonitorID, channelmonitordailyrollup.FieldTotalChecks, channelmonitordailyrollup.FieldOkCount, channelmonitordailyrollup.FieldOperationalCount, channelmonitordailyrollup.FieldDegradedCount, channelmonitordailyrollup.FieldFailedCount, channelmonitordailyrollup.FieldErrorCount, channelmonitordailyrollup.FieldSumLatencyMs, channelmonitordailyrollup.FieldCountLatency, channelmonitordailyrollup.FieldSumPingLatencyMs, channelmonitordailyrollup.FieldCountPingLatency:
values[i] = new(sql.NullInt64)
case channelmonitordailyrollup.FieldModel:
values[i] = new(sql.NullString)
case channelmonitordailyrollup.FieldBucketDate, channelmonitordailyrollup.FieldComputedAt:
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 ChannelMonitorDailyRollup fields.
func (_m *ChannelMonitorDailyRollup) 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 channelmonitordailyrollup.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 channelmonitordailyrollup.FieldMonitorID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field monitor_id", values[i])
} else if value.Valid {
_m.MonitorID = value.Int64
}
case channelmonitordailyrollup.FieldModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field model", values[i])
} else if value.Valid {
_m.Model = value.String
}
case channelmonitordailyrollup.FieldBucketDate:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field bucket_date", values[i])
} else if value.Valid {
_m.BucketDate = value.Time
}
case channelmonitordailyrollup.FieldTotalChecks:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field total_checks", values[i])
} else if value.Valid {
_m.TotalChecks = int(value.Int64)
}
case channelmonitordailyrollup.FieldOkCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field ok_count", values[i])
} else if value.Valid {
_m.OkCount = int(value.Int64)
}
case channelmonitordailyrollup.FieldOperationalCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field operational_count", values[i])
} else if value.Valid {
_m.OperationalCount = int(value.Int64)
}
case channelmonitordailyrollup.FieldDegradedCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field degraded_count", values[i])
} else if value.Valid {
_m.DegradedCount = int(value.Int64)
}
case channelmonitordailyrollup.FieldFailedCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field failed_count", values[i])
} else if value.Valid {
_m.FailedCount = int(value.Int64)
}
case channelmonitordailyrollup.FieldErrorCount:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field error_count", values[i])
} else if value.Valid {
_m.ErrorCount = int(value.Int64)
}
case channelmonitordailyrollup.FieldSumLatencyMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field sum_latency_ms", values[i])
} else if value.Valid {
_m.SumLatencyMs = value.Int64
}
case channelmonitordailyrollup.FieldCountLatency:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field count_latency", values[i])
} else if value.Valid {
_m.CountLatency = int(value.Int64)
}
case channelmonitordailyrollup.FieldSumPingLatencyMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field sum_ping_latency_ms", values[i])
} else if value.Valid {
_m.SumPingLatencyMs = value.Int64
}
case channelmonitordailyrollup.FieldCountPingLatency:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field count_ping_latency", values[i])
} else if value.Valid {
_m.CountPingLatency = int(value.Int64)
}
case channelmonitordailyrollup.FieldComputedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field computed_at", values[i])
} else if value.Valid {
_m.ComputedAt = 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 ChannelMonitorDailyRollup.
// This includes values selected through modifiers, order, etc.
func (_m *ChannelMonitorDailyRollup) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryMonitor queries the "monitor" edge of the ChannelMonitorDailyRollup entity.
func (_m *ChannelMonitorDailyRollup) QueryMonitor() *ChannelMonitorQuery {
return NewChannelMonitorDailyRollupClient(_m.config).QueryMonitor(_m)
}
// Update returns a builder for updating this ChannelMonitorDailyRollup.
// Note that you need to call ChannelMonitorDailyRollup.Unwrap() before calling this method if this ChannelMonitorDailyRollup
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *ChannelMonitorDailyRollup) Update() *ChannelMonitorDailyRollupUpdateOne {
return NewChannelMonitorDailyRollupClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the ChannelMonitorDailyRollup 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 *ChannelMonitorDailyRollup) Unwrap() *ChannelMonitorDailyRollup {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: ChannelMonitorDailyRollup is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *ChannelMonitorDailyRollup) String() string {
var builder strings.Builder
builder.WriteString("ChannelMonitorDailyRollup(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("monitor_id=")
builder.WriteString(fmt.Sprintf("%v", _m.MonitorID))
builder.WriteString(", ")
builder.WriteString("model=")
builder.WriteString(_m.Model)
builder.WriteString(", ")
builder.WriteString("bucket_date=")
builder.WriteString(_m.BucketDate.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("total_checks=")
builder.WriteString(fmt.Sprintf("%v", _m.TotalChecks))
builder.WriteString(", ")
builder.WriteString("ok_count=")
builder.WriteString(fmt.Sprintf("%v", _m.OkCount))
builder.WriteString(", ")
builder.WriteString("operational_count=")
builder.WriteString(fmt.Sprintf("%v", _m.OperationalCount))
builder.WriteString(", ")
builder.WriteString("degraded_count=")
builder.WriteString(fmt.Sprintf("%v", _m.DegradedCount))
builder.WriteString(", ")
builder.WriteString("failed_count=")
builder.WriteString(fmt.Sprintf("%v", _m.FailedCount))
builder.WriteString(", ")
builder.WriteString("error_count=")
builder.WriteString(fmt.Sprintf("%v", _m.ErrorCount))
builder.WriteString(", ")
builder.WriteString("sum_latency_ms=")
builder.WriteString(fmt.Sprintf("%v", _m.SumLatencyMs))
builder.WriteString(", ")
builder.WriteString("count_latency=")
builder.WriteString(fmt.Sprintf("%v", _m.CountLatency))
builder.WriteString(", ")
builder.WriteString("sum_ping_latency_ms=")
builder.WriteString(fmt.Sprintf("%v", _m.SumPingLatencyMs))
builder.WriteString(", ")
builder.WriteString("count_ping_latency=")
builder.WriteString(fmt.Sprintf("%v", _m.CountPingLatency))
builder.WriteString(", ")
builder.WriteString("computed_at=")
builder.WriteString(_m.ComputedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// ChannelMonitorDailyRollups is a parsable slice of ChannelMonitorDailyRollup.
type ChannelMonitorDailyRollups []*ChannelMonitorDailyRollup

View File

@@ -0,0 +1,206 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitordailyrollup
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the channelmonitordailyrollup type in the database.
Label = "channel_monitor_daily_rollup"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldMonitorID holds the string denoting the monitor_id field in the database.
FieldMonitorID = "monitor_id"
// FieldModel holds the string denoting the model field in the database.
FieldModel = "model"
// FieldBucketDate holds the string denoting the bucket_date field in the database.
FieldBucketDate = "bucket_date"
// FieldTotalChecks holds the string denoting the total_checks field in the database.
FieldTotalChecks = "total_checks"
// FieldOkCount holds the string denoting the ok_count field in the database.
FieldOkCount = "ok_count"
// FieldOperationalCount holds the string denoting the operational_count field in the database.
FieldOperationalCount = "operational_count"
// FieldDegradedCount holds the string denoting the degraded_count field in the database.
FieldDegradedCount = "degraded_count"
// FieldFailedCount holds the string denoting the failed_count field in the database.
FieldFailedCount = "failed_count"
// FieldErrorCount holds the string denoting the error_count field in the database.
FieldErrorCount = "error_count"
// FieldSumLatencyMs holds the string denoting the sum_latency_ms field in the database.
FieldSumLatencyMs = "sum_latency_ms"
// FieldCountLatency holds the string denoting the count_latency field in the database.
FieldCountLatency = "count_latency"
// FieldSumPingLatencyMs holds the string denoting the sum_ping_latency_ms field in the database.
FieldSumPingLatencyMs = "sum_ping_latency_ms"
// FieldCountPingLatency holds the string denoting the count_ping_latency field in the database.
FieldCountPingLatency = "count_ping_latency"
// FieldComputedAt holds the string denoting the computed_at field in the database.
FieldComputedAt = "computed_at"
// EdgeMonitor holds the string denoting the monitor edge name in mutations.
EdgeMonitor = "monitor"
// Table holds the table name of the channelmonitordailyrollup in the database.
Table = "channel_monitor_daily_rollups"
// MonitorTable is the table that holds the monitor relation/edge.
MonitorTable = "channel_monitor_daily_rollups"
// MonitorInverseTable is the table name for the ChannelMonitor entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitor" package.
MonitorInverseTable = "channel_monitors"
// MonitorColumn is the table column denoting the monitor relation/edge.
MonitorColumn = "monitor_id"
)
// Columns holds all SQL columns for channelmonitordailyrollup fields.
var Columns = []string{
FieldID,
FieldMonitorID,
FieldModel,
FieldBucketDate,
FieldTotalChecks,
FieldOkCount,
FieldOperationalCount,
FieldDegradedCount,
FieldFailedCount,
FieldErrorCount,
FieldSumLatencyMs,
FieldCountLatency,
FieldSumPingLatencyMs,
FieldCountPingLatency,
FieldComputedAt,
}
// 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 (
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
ModelValidator func(string) error
// DefaultTotalChecks holds the default value on creation for the "total_checks" field.
DefaultTotalChecks int
// DefaultOkCount holds the default value on creation for the "ok_count" field.
DefaultOkCount int
// DefaultOperationalCount holds the default value on creation for the "operational_count" field.
DefaultOperationalCount int
// DefaultDegradedCount holds the default value on creation for the "degraded_count" field.
DefaultDegradedCount int
// DefaultFailedCount holds the default value on creation for the "failed_count" field.
DefaultFailedCount int
// DefaultErrorCount holds the default value on creation for the "error_count" field.
DefaultErrorCount int
// DefaultSumLatencyMs holds the default value on creation for the "sum_latency_ms" field.
DefaultSumLatencyMs int64
// DefaultCountLatency holds the default value on creation for the "count_latency" field.
DefaultCountLatency int
// DefaultSumPingLatencyMs holds the default value on creation for the "sum_ping_latency_ms" field.
DefaultSumPingLatencyMs int64
// DefaultCountPingLatency holds the default value on creation for the "count_ping_latency" field.
DefaultCountPingLatency int
// DefaultComputedAt holds the default value on creation for the "computed_at" field.
DefaultComputedAt func() time.Time
// UpdateDefaultComputedAt holds the default value on update for the "computed_at" field.
UpdateDefaultComputedAt func() time.Time
)
// OrderOption defines the ordering options for the ChannelMonitorDailyRollup 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()
}
// ByMonitorID orders the results by the monitor_id field.
func ByMonitorID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMonitorID, opts...).ToFunc()
}
// ByModel orders the results by the model field.
func ByModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldModel, opts...).ToFunc()
}
// ByBucketDate orders the results by the bucket_date field.
func ByBucketDate(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBucketDate, opts...).ToFunc()
}
// ByTotalChecks orders the results by the total_checks field.
func ByTotalChecks(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTotalChecks, opts...).ToFunc()
}
// ByOkCount orders the results by the ok_count field.
func ByOkCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOkCount, opts...).ToFunc()
}
// ByOperationalCount orders the results by the operational_count field.
func ByOperationalCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOperationalCount, opts...).ToFunc()
}
// ByDegradedCount orders the results by the degraded_count field.
func ByDegradedCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDegradedCount, opts...).ToFunc()
}
// ByFailedCount orders the results by the failed_count field.
func ByFailedCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailedCount, opts...).ToFunc()
}
// ByErrorCount orders the results by the error_count field.
func ByErrorCount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldErrorCount, opts...).ToFunc()
}
// BySumLatencyMs orders the results by the sum_latency_ms field.
func BySumLatencyMs(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSumLatencyMs, opts...).ToFunc()
}
// ByCountLatency orders the results by the count_latency field.
func ByCountLatency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCountLatency, opts...).ToFunc()
}
// BySumPingLatencyMs orders the results by the sum_ping_latency_ms field.
func BySumPingLatencyMs(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSumPingLatencyMs, opts...).ToFunc()
}
// ByCountPingLatency orders the results by the count_ping_latency field.
func ByCountPingLatency(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCountPingLatency, opts...).ToFunc()
}
// ByComputedAt orders the results by the computed_at field.
func ByComputedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldComputedAt, opts...).ToFunc()
}
// ByMonitorField orders the results by monitor field.
func ByMonitorField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newMonitorStep(), sql.OrderByField(field, opts...))
}
}
func newMonitorStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(MonitorInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
)
}

View File

@@ -0,0 +1,729 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitordailyrollup
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldID, id))
}
// MonitorID applies equality check predicate on the "monitor_id" field. It's identical to MonitorIDEQ.
func MonitorID(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldMonitorID, v))
}
// Model applies equality check predicate on the "model" field. It's identical to ModelEQ.
func Model(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldModel, v))
}
// BucketDate applies equality check predicate on the "bucket_date" field. It's identical to BucketDateEQ.
func BucketDate(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldBucketDate, v))
}
// TotalChecks applies equality check predicate on the "total_checks" field. It's identical to TotalChecksEQ.
func TotalChecks(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldTotalChecks, v))
}
// OkCount applies equality check predicate on the "ok_count" field. It's identical to OkCountEQ.
func OkCount(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldOkCount, v))
}
// OperationalCount applies equality check predicate on the "operational_count" field. It's identical to OperationalCountEQ.
func OperationalCount(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldOperationalCount, v))
}
// DegradedCount applies equality check predicate on the "degraded_count" field. It's identical to DegradedCountEQ.
func DegradedCount(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldDegradedCount, v))
}
// FailedCount applies equality check predicate on the "failed_count" field. It's identical to FailedCountEQ.
func FailedCount(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldFailedCount, v))
}
// ErrorCount applies equality check predicate on the "error_count" field. It's identical to ErrorCountEQ.
func ErrorCount(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldErrorCount, v))
}
// SumLatencyMs applies equality check predicate on the "sum_latency_ms" field. It's identical to SumLatencyMsEQ.
func SumLatencyMs(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldSumLatencyMs, v))
}
// CountLatency applies equality check predicate on the "count_latency" field. It's identical to CountLatencyEQ.
func CountLatency(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldCountLatency, v))
}
// SumPingLatencyMs applies equality check predicate on the "sum_ping_latency_ms" field. It's identical to SumPingLatencyMsEQ.
func SumPingLatencyMs(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldSumPingLatencyMs, v))
}
// CountPingLatency applies equality check predicate on the "count_ping_latency" field. It's identical to CountPingLatencyEQ.
func CountPingLatency(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldCountPingLatency, v))
}
// ComputedAt applies equality check predicate on the "computed_at" field. It's identical to ComputedAtEQ.
func ComputedAt(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldComputedAt, v))
}
// MonitorIDEQ applies the EQ predicate on the "monitor_id" field.
func MonitorIDEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldMonitorID, v))
}
// MonitorIDNEQ applies the NEQ predicate on the "monitor_id" field.
func MonitorIDNEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldMonitorID, v))
}
// MonitorIDIn applies the In predicate on the "monitor_id" field.
func MonitorIDIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldMonitorID, vs...))
}
// MonitorIDNotIn applies the NotIn predicate on the "monitor_id" field.
func MonitorIDNotIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldMonitorID, vs...))
}
// ModelEQ applies the EQ predicate on the "model" field.
func ModelEQ(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldModel, v))
}
// ModelNEQ applies the NEQ predicate on the "model" field.
func ModelNEQ(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldModel, v))
}
// ModelIn applies the In predicate on the "model" field.
func ModelIn(vs ...string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldModel, vs...))
}
// ModelNotIn applies the NotIn predicate on the "model" field.
func ModelNotIn(vs ...string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldModel, vs...))
}
// ModelGT applies the GT predicate on the "model" field.
func ModelGT(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldModel, v))
}
// ModelGTE applies the GTE predicate on the "model" field.
func ModelGTE(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldModel, v))
}
// ModelLT applies the LT predicate on the "model" field.
func ModelLT(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldModel, v))
}
// ModelLTE applies the LTE predicate on the "model" field.
func ModelLTE(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldModel, v))
}
// ModelContains applies the Contains predicate on the "model" field.
func ModelContains(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldContains(FieldModel, v))
}
// ModelHasPrefix applies the HasPrefix predicate on the "model" field.
func ModelHasPrefix(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldHasPrefix(FieldModel, v))
}
// ModelHasSuffix applies the HasSuffix predicate on the "model" field.
func ModelHasSuffix(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldHasSuffix(FieldModel, v))
}
// ModelEqualFold applies the EqualFold predicate on the "model" field.
func ModelEqualFold(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEqualFold(FieldModel, v))
}
// ModelContainsFold applies the ContainsFold predicate on the "model" field.
func ModelContainsFold(v string) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldContainsFold(FieldModel, v))
}
// BucketDateEQ applies the EQ predicate on the "bucket_date" field.
func BucketDateEQ(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldBucketDate, v))
}
// BucketDateNEQ applies the NEQ predicate on the "bucket_date" field.
func BucketDateNEQ(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldBucketDate, v))
}
// BucketDateIn applies the In predicate on the "bucket_date" field.
func BucketDateIn(vs ...time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldBucketDate, vs...))
}
// BucketDateNotIn applies the NotIn predicate on the "bucket_date" field.
func BucketDateNotIn(vs ...time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldBucketDate, vs...))
}
// BucketDateGT applies the GT predicate on the "bucket_date" field.
func BucketDateGT(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldBucketDate, v))
}
// BucketDateGTE applies the GTE predicate on the "bucket_date" field.
func BucketDateGTE(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldBucketDate, v))
}
// BucketDateLT applies the LT predicate on the "bucket_date" field.
func BucketDateLT(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldBucketDate, v))
}
// BucketDateLTE applies the LTE predicate on the "bucket_date" field.
func BucketDateLTE(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldBucketDate, v))
}
// TotalChecksEQ applies the EQ predicate on the "total_checks" field.
func TotalChecksEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldTotalChecks, v))
}
// TotalChecksNEQ applies the NEQ predicate on the "total_checks" field.
func TotalChecksNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldTotalChecks, v))
}
// TotalChecksIn applies the In predicate on the "total_checks" field.
func TotalChecksIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldTotalChecks, vs...))
}
// TotalChecksNotIn applies the NotIn predicate on the "total_checks" field.
func TotalChecksNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldTotalChecks, vs...))
}
// TotalChecksGT applies the GT predicate on the "total_checks" field.
func TotalChecksGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldTotalChecks, v))
}
// TotalChecksGTE applies the GTE predicate on the "total_checks" field.
func TotalChecksGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldTotalChecks, v))
}
// TotalChecksLT applies the LT predicate on the "total_checks" field.
func TotalChecksLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldTotalChecks, v))
}
// TotalChecksLTE applies the LTE predicate on the "total_checks" field.
func TotalChecksLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldTotalChecks, v))
}
// OkCountEQ applies the EQ predicate on the "ok_count" field.
func OkCountEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldOkCount, v))
}
// OkCountNEQ applies the NEQ predicate on the "ok_count" field.
func OkCountNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldOkCount, v))
}
// OkCountIn applies the In predicate on the "ok_count" field.
func OkCountIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldOkCount, vs...))
}
// OkCountNotIn applies the NotIn predicate on the "ok_count" field.
func OkCountNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldOkCount, vs...))
}
// OkCountGT applies the GT predicate on the "ok_count" field.
func OkCountGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldOkCount, v))
}
// OkCountGTE applies the GTE predicate on the "ok_count" field.
func OkCountGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldOkCount, v))
}
// OkCountLT applies the LT predicate on the "ok_count" field.
func OkCountLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldOkCount, v))
}
// OkCountLTE applies the LTE predicate on the "ok_count" field.
func OkCountLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldOkCount, v))
}
// OperationalCountEQ applies the EQ predicate on the "operational_count" field.
func OperationalCountEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldOperationalCount, v))
}
// OperationalCountNEQ applies the NEQ predicate on the "operational_count" field.
func OperationalCountNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldOperationalCount, v))
}
// OperationalCountIn applies the In predicate on the "operational_count" field.
func OperationalCountIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldOperationalCount, vs...))
}
// OperationalCountNotIn applies the NotIn predicate on the "operational_count" field.
func OperationalCountNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldOperationalCount, vs...))
}
// OperationalCountGT applies the GT predicate on the "operational_count" field.
func OperationalCountGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldOperationalCount, v))
}
// OperationalCountGTE applies the GTE predicate on the "operational_count" field.
func OperationalCountGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldOperationalCount, v))
}
// OperationalCountLT applies the LT predicate on the "operational_count" field.
func OperationalCountLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldOperationalCount, v))
}
// OperationalCountLTE applies the LTE predicate on the "operational_count" field.
func OperationalCountLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldOperationalCount, v))
}
// DegradedCountEQ applies the EQ predicate on the "degraded_count" field.
func DegradedCountEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldDegradedCount, v))
}
// DegradedCountNEQ applies the NEQ predicate on the "degraded_count" field.
func DegradedCountNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldDegradedCount, v))
}
// DegradedCountIn applies the In predicate on the "degraded_count" field.
func DegradedCountIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldDegradedCount, vs...))
}
// DegradedCountNotIn applies the NotIn predicate on the "degraded_count" field.
func DegradedCountNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldDegradedCount, vs...))
}
// DegradedCountGT applies the GT predicate on the "degraded_count" field.
func DegradedCountGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldDegradedCount, v))
}
// DegradedCountGTE applies the GTE predicate on the "degraded_count" field.
func DegradedCountGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldDegradedCount, v))
}
// DegradedCountLT applies the LT predicate on the "degraded_count" field.
func DegradedCountLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldDegradedCount, v))
}
// DegradedCountLTE applies the LTE predicate on the "degraded_count" field.
func DegradedCountLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldDegradedCount, v))
}
// FailedCountEQ applies the EQ predicate on the "failed_count" field.
func FailedCountEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldFailedCount, v))
}
// FailedCountNEQ applies the NEQ predicate on the "failed_count" field.
func FailedCountNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldFailedCount, v))
}
// FailedCountIn applies the In predicate on the "failed_count" field.
func FailedCountIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldFailedCount, vs...))
}
// FailedCountNotIn applies the NotIn predicate on the "failed_count" field.
func FailedCountNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldFailedCount, vs...))
}
// FailedCountGT applies the GT predicate on the "failed_count" field.
func FailedCountGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldFailedCount, v))
}
// FailedCountGTE applies the GTE predicate on the "failed_count" field.
func FailedCountGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldFailedCount, v))
}
// FailedCountLT applies the LT predicate on the "failed_count" field.
func FailedCountLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldFailedCount, v))
}
// FailedCountLTE applies the LTE predicate on the "failed_count" field.
func FailedCountLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldFailedCount, v))
}
// ErrorCountEQ applies the EQ predicate on the "error_count" field.
func ErrorCountEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldErrorCount, v))
}
// ErrorCountNEQ applies the NEQ predicate on the "error_count" field.
func ErrorCountNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldErrorCount, v))
}
// ErrorCountIn applies the In predicate on the "error_count" field.
func ErrorCountIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldErrorCount, vs...))
}
// ErrorCountNotIn applies the NotIn predicate on the "error_count" field.
func ErrorCountNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldErrorCount, vs...))
}
// ErrorCountGT applies the GT predicate on the "error_count" field.
func ErrorCountGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldErrorCount, v))
}
// ErrorCountGTE applies the GTE predicate on the "error_count" field.
func ErrorCountGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldErrorCount, v))
}
// ErrorCountLT applies the LT predicate on the "error_count" field.
func ErrorCountLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldErrorCount, v))
}
// ErrorCountLTE applies the LTE predicate on the "error_count" field.
func ErrorCountLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldErrorCount, v))
}
// SumLatencyMsEQ applies the EQ predicate on the "sum_latency_ms" field.
func SumLatencyMsEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldSumLatencyMs, v))
}
// SumLatencyMsNEQ applies the NEQ predicate on the "sum_latency_ms" field.
func SumLatencyMsNEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldSumLatencyMs, v))
}
// SumLatencyMsIn applies the In predicate on the "sum_latency_ms" field.
func SumLatencyMsIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldSumLatencyMs, vs...))
}
// SumLatencyMsNotIn applies the NotIn predicate on the "sum_latency_ms" field.
func SumLatencyMsNotIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldSumLatencyMs, vs...))
}
// SumLatencyMsGT applies the GT predicate on the "sum_latency_ms" field.
func SumLatencyMsGT(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldSumLatencyMs, v))
}
// SumLatencyMsGTE applies the GTE predicate on the "sum_latency_ms" field.
func SumLatencyMsGTE(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldSumLatencyMs, v))
}
// SumLatencyMsLT applies the LT predicate on the "sum_latency_ms" field.
func SumLatencyMsLT(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldSumLatencyMs, v))
}
// SumLatencyMsLTE applies the LTE predicate on the "sum_latency_ms" field.
func SumLatencyMsLTE(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldSumLatencyMs, v))
}
// CountLatencyEQ applies the EQ predicate on the "count_latency" field.
func CountLatencyEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldCountLatency, v))
}
// CountLatencyNEQ applies the NEQ predicate on the "count_latency" field.
func CountLatencyNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldCountLatency, v))
}
// CountLatencyIn applies the In predicate on the "count_latency" field.
func CountLatencyIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldCountLatency, vs...))
}
// CountLatencyNotIn applies the NotIn predicate on the "count_latency" field.
func CountLatencyNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldCountLatency, vs...))
}
// CountLatencyGT applies the GT predicate on the "count_latency" field.
func CountLatencyGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldCountLatency, v))
}
// CountLatencyGTE applies the GTE predicate on the "count_latency" field.
func CountLatencyGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldCountLatency, v))
}
// CountLatencyLT applies the LT predicate on the "count_latency" field.
func CountLatencyLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldCountLatency, v))
}
// CountLatencyLTE applies the LTE predicate on the "count_latency" field.
func CountLatencyLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldCountLatency, v))
}
// SumPingLatencyMsEQ applies the EQ predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldSumPingLatencyMs, v))
}
// SumPingLatencyMsNEQ applies the NEQ predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsNEQ(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldSumPingLatencyMs, v))
}
// SumPingLatencyMsIn applies the In predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldSumPingLatencyMs, vs...))
}
// SumPingLatencyMsNotIn applies the NotIn predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsNotIn(vs ...int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldSumPingLatencyMs, vs...))
}
// SumPingLatencyMsGT applies the GT predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsGT(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldSumPingLatencyMs, v))
}
// SumPingLatencyMsGTE applies the GTE predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsGTE(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldSumPingLatencyMs, v))
}
// SumPingLatencyMsLT applies the LT predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsLT(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldSumPingLatencyMs, v))
}
// SumPingLatencyMsLTE applies the LTE predicate on the "sum_ping_latency_ms" field.
func SumPingLatencyMsLTE(v int64) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldSumPingLatencyMs, v))
}
// CountPingLatencyEQ applies the EQ predicate on the "count_ping_latency" field.
func CountPingLatencyEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldCountPingLatency, v))
}
// CountPingLatencyNEQ applies the NEQ predicate on the "count_ping_latency" field.
func CountPingLatencyNEQ(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldCountPingLatency, v))
}
// CountPingLatencyIn applies the In predicate on the "count_ping_latency" field.
func CountPingLatencyIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldCountPingLatency, vs...))
}
// CountPingLatencyNotIn applies the NotIn predicate on the "count_ping_latency" field.
func CountPingLatencyNotIn(vs ...int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldCountPingLatency, vs...))
}
// CountPingLatencyGT applies the GT predicate on the "count_ping_latency" field.
func CountPingLatencyGT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldCountPingLatency, v))
}
// CountPingLatencyGTE applies the GTE predicate on the "count_ping_latency" field.
func CountPingLatencyGTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldCountPingLatency, v))
}
// CountPingLatencyLT applies the LT predicate on the "count_ping_latency" field.
func CountPingLatencyLT(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldCountPingLatency, v))
}
// CountPingLatencyLTE applies the LTE predicate on the "count_ping_latency" field.
func CountPingLatencyLTE(v int) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldCountPingLatency, v))
}
// ComputedAtEQ applies the EQ predicate on the "computed_at" field.
func ComputedAtEQ(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldEQ(FieldComputedAt, v))
}
// ComputedAtNEQ applies the NEQ predicate on the "computed_at" field.
func ComputedAtNEQ(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNEQ(FieldComputedAt, v))
}
// ComputedAtIn applies the In predicate on the "computed_at" field.
func ComputedAtIn(vs ...time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldIn(FieldComputedAt, vs...))
}
// ComputedAtNotIn applies the NotIn predicate on the "computed_at" field.
func ComputedAtNotIn(vs ...time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldNotIn(FieldComputedAt, vs...))
}
// ComputedAtGT applies the GT predicate on the "computed_at" field.
func ComputedAtGT(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGT(FieldComputedAt, v))
}
// ComputedAtGTE applies the GTE predicate on the "computed_at" field.
func ComputedAtGTE(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldGTE(FieldComputedAt, v))
}
// ComputedAtLT applies the LT predicate on the "computed_at" field.
func ComputedAtLT(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLT(FieldComputedAt, v))
}
// ComputedAtLTE applies the LTE predicate on the "computed_at" field.
func ComputedAtLTE(v time.Time) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.FieldLTE(FieldComputedAt, v))
}
// HasMonitor applies the HasEdge predicate on the "monitor" edge.
func HasMonitor() predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasMonitorWith applies the HasEdge predicate on the "monitor" edge with a given conditions (other predicates).
func HasMonitorWith(preds ...predicate.ChannelMonitor) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(func(s *sql.Selector) {
step := newMonitorStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.ChannelMonitorDailyRollup) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.ChannelMonitorDailyRollup) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.ChannelMonitorDailyRollup) predicate.ChannelMonitorDailyRollup {
return predicate.ChannelMonitorDailyRollup(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/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorDailyRollupDelete is the builder for deleting a ChannelMonitorDailyRollup entity.
type ChannelMonitorDailyRollupDelete struct {
config
hooks []Hook
mutation *ChannelMonitorDailyRollupMutation
}
// Where appends a list predicates to the ChannelMonitorDailyRollupDelete builder.
func (_d *ChannelMonitorDailyRollupDelete) Where(ps ...predicate.ChannelMonitorDailyRollup) *ChannelMonitorDailyRollupDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *ChannelMonitorDailyRollupDelete) 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 *ChannelMonitorDailyRollupDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *ChannelMonitorDailyRollupDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(channelmonitordailyrollup.Table, sqlgraph.NewFieldSpec(channelmonitordailyrollup.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
}
// ChannelMonitorDailyRollupDeleteOne is the builder for deleting a single ChannelMonitorDailyRollup entity.
type ChannelMonitorDailyRollupDeleteOne struct {
_d *ChannelMonitorDailyRollupDelete
}
// Where appends a list predicates to the ChannelMonitorDailyRollupDelete builder.
func (_d *ChannelMonitorDailyRollupDeleteOne) Where(ps ...predicate.ChannelMonitorDailyRollup) *ChannelMonitorDailyRollupDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *ChannelMonitorDailyRollupDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{channelmonitordailyrollup.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *ChannelMonitorDailyRollupDeleteOne) 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorDailyRollupQuery is the builder for querying ChannelMonitorDailyRollup entities.
type ChannelMonitorDailyRollupQuery struct {
config
ctx *QueryContext
order []channelmonitordailyrollup.OrderOption
inters []Interceptor
predicates []predicate.ChannelMonitorDailyRollup
withMonitor *ChannelMonitorQuery
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 ChannelMonitorDailyRollupQuery builder.
func (_q *ChannelMonitorDailyRollupQuery) Where(ps ...predicate.ChannelMonitorDailyRollup) *ChannelMonitorDailyRollupQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *ChannelMonitorDailyRollupQuery) Limit(limit int) *ChannelMonitorDailyRollupQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *ChannelMonitorDailyRollupQuery) Offset(offset int) *ChannelMonitorDailyRollupQuery {
_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 *ChannelMonitorDailyRollupQuery) Unique(unique bool) *ChannelMonitorDailyRollupQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *ChannelMonitorDailyRollupQuery) Order(o ...channelmonitordailyrollup.OrderOption) *ChannelMonitorDailyRollupQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryMonitor chains the current query on the "monitor" edge.
func (_q *ChannelMonitorDailyRollupQuery) QueryMonitor() *ChannelMonitorQuery {
query := (&ChannelMonitorClient{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(channelmonitordailyrollup.Table, channelmonitordailyrollup.FieldID, selector),
sqlgraph.To(channelmonitor.Table, channelmonitor.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, channelmonitordailyrollup.MonitorTable, channelmonitordailyrollup.MonitorColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first ChannelMonitorDailyRollup entity from the query.
// Returns a *NotFoundError when no ChannelMonitorDailyRollup was found.
func (_q *ChannelMonitorDailyRollupQuery) First(ctx context.Context) (*ChannelMonitorDailyRollup, 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{channelmonitordailyrollup.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) FirstX(ctx context.Context) *ChannelMonitorDailyRollup {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first ChannelMonitorDailyRollup ID from the query.
// Returns a *NotFoundError when no ChannelMonitorDailyRollup ID was found.
func (_q *ChannelMonitorDailyRollupQuery) 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{channelmonitordailyrollup.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single ChannelMonitorDailyRollup entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one ChannelMonitorDailyRollup entity is found.
// Returns a *NotFoundError when no ChannelMonitorDailyRollup entities are found.
func (_q *ChannelMonitorDailyRollupQuery) Only(ctx context.Context) (*ChannelMonitorDailyRollup, 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{channelmonitordailyrollup.Label}
default:
return nil, &NotSingularError{channelmonitordailyrollup.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) OnlyX(ctx context.Context) *ChannelMonitorDailyRollup {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only ChannelMonitorDailyRollup ID in the query.
// Returns a *NotSingularError when more than one ChannelMonitorDailyRollup ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *ChannelMonitorDailyRollupQuery) 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{channelmonitordailyrollup.Label}
default:
err = &NotSingularError{channelmonitordailyrollup.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) 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 ChannelMonitorDailyRollups.
func (_q *ChannelMonitorDailyRollupQuery) All(ctx context.Context) ([]*ChannelMonitorDailyRollup, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*ChannelMonitorDailyRollup, *ChannelMonitorDailyRollupQuery]()
return withInterceptors[[]*ChannelMonitorDailyRollup](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) AllX(ctx context.Context) []*ChannelMonitorDailyRollup {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of ChannelMonitorDailyRollup IDs.
func (_q *ChannelMonitorDailyRollupQuery) 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(channelmonitordailyrollup.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) 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 *ChannelMonitorDailyRollupQuery) 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[*ChannelMonitorDailyRollupQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *ChannelMonitorDailyRollupQuery) 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 *ChannelMonitorDailyRollupQuery) 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 *ChannelMonitorDailyRollupQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the ChannelMonitorDailyRollupQuery 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 *ChannelMonitorDailyRollupQuery) Clone() *ChannelMonitorDailyRollupQuery {
if _q == nil {
return nil
}
return &ChannelMonitorDailyRollupQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]channelmonitordailyrollup.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.ChannelMonitorDailyRollup{}, _q.predicates...),
withMonitor: _q.withMonitor.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithMonitor tells the query-builder to eager-load the nodes that are connected to
// the "monitor" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ChannelMonitorDailyRollupQuery) WithMonitor(opts ...func(*ChannelMonitorQuery)) *ChannelMonitorDailyRollupQuery {
query := (&ChannelMonitorClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withMonitor = 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 {
// MonitorID int64 `json:"monitor_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.ChannelMonitorDailyRollup.Query().
// GroupBy(channelmonitordailyrollup.FieldMonitorID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *ChannelMonitorDailyRollupQuery) GroupBy(field string, fields ...string) *ChannelMonitorDailyRollupGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &ChannelMonitorDailyRollupGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = channelmonitordailyrollup.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 {
// MonitorID int64 `json:"monitor_id,omitempty"`
// }
//
// client.ChannelMonitorDailyRollup.Query().
// Select(channelmonitordailyrollup.FieldMonitorID).
// Scan(ctx, &v)
func (_q *ChannelMonitorDailyRollupQuery) Select(fields ...string) *ChannelMonitorDailyRollupSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &ChannelMonitorDailyRollupSelect{ChannelMonitorDailyRollupQuery: _q}
sbuild.label = channelmonitordailyrollup.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a ChannelMonitorDailyRollupSelect configured with the given aggregations.
func (_q *ChannelMonitorDailyRollupQuery) Aggregate(fns ...AggregateFunc) *ChannelMonitorDailyRollupSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *ChannelMonitorDailyRollupQuery) 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 !channelmonitordailyrollup.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 *ChannelMonitorDailyRollupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ChannelMonitorDailyRollup, error) {
var (
nodes = []*ChannelMonitorDailyRollup{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withMonitor != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*ChannelMonitorDailyRollup).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &ChannelMonitorDailyRollup{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.withMonitor; query != nil {
if err := _q.loadMonitor(ctx, query, nodes, nil,
func(n *ChannelMonitorDailyRollup, e *ChannelMonitor) { n.Edges.Monitor = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *ChannelMonitorDailyRollupQuery) loadMonitor(ctx context.Context, query *ChannelMonitorQuery, nodes []*ChannelMonitorDailyRollup, init func(*ChannelMonitorDailyRollup), assign func(*ChannelMonitorDailyRollup, *ChannelMonitor)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*ChannelMonitorDailyRollup)
for i := range nodes {
fk := nodes[i].MonitorID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(channelmonitor.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 "monitor_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *ChannelMonitorDailyRollupQuery) 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 *ChannelMonitorDailyRollupQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(channelmonitordailyrollup.Table, channelmonitordailyrollup.Columns, sqlgraph.NewFieldSpec(channelmonitordailyrollup.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, channelmonitordailyrollup.FieldID)
for i := range fields {
if fields[i] != channelmonitordailyrollup.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withMonitor != nil {
_spec.Node.AddColumnOnce(channelmonitordailyrollup.FieldMonitorID)
}
}
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 *ChannelMonitorDailyRollupQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(channelmonitordailyrollup.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = channelmonitordailyrollup.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 *ChannelMonitorDailyRollupQuery) ForUpdate(opts ...sql.LockOption) *ChannelMonitorDailyRollupQuery {
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 *ChannelMonitorDailyRollupQuery) ForShare(opts ...sql.LockOption) *ChannelMonitorDailyRollupQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// ChannelMonitorDailyRollupGroupBy is the group-by builder for ChannelMonitorDailyRollup entities.
type ChannelMonitorDailyRollupGroupBy struct {
selector
build *ChannelMonitorDailyRollupQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *ChannelMonitorDailyRollupGroupBy) Aggregate(fns ...AggregateFunc) *ChannelMonitorDailyRollupGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *ChannelMonitorDailyRollupGroupBy) 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[*ChannelMonitorDailyRollupQuery, *ChannelMonitorDailyRollupGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *ChannelMonitorDailyRollupGroupBy) sqlScan(ctx context.Context, root *ChannelMonitorDailyRollupQuery, 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)
}
// ChannelMonitorDailyRollupSelect is the builder for selecting fields of ChannelMonitorDailyRollup entities.
type ChannelMonitorDailyRollupSelect struct {
*ChannelMonitorDailyRollupQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *ChannelMonitorDailyRollupSelect) Aggregate(fns ...AggregateFunc) *ChannelMonitorDailyRollupSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *ChannelMonitorDailyRollupSelect) 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[*ChannelMonitorDailyRollupQuery, *ChannelMonitorDailyRollupSelect](ctx, _s.ChannelMonitorDailyRollupQuery, _s, _s.inters, v)
}
func (_s *ChannelMonitorDailyRollupSelect) sqlScan(ctx context.Context, root *ChannelMonitorDailyRollupQuery, 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,961 @@
// 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorDailyRollupUpdate is the builder for updating ChannelMonitorDailyRollup entities.
type ChannelMonitorDailyRollupUpdate struct {
config
hooks []Hook
mutation *ChannelMonitorDailyRollupMutation
}
// Where appends a list predicates to the ChannelMonitorDailyRollupUpdate builder.
func (_u *ChannelMonitorDailyRollupUpdate) Where(ps ...predicate.ChannelMonitorDailyRollup) *ChannelMonitorDailyRollupUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetMonitorID sets the "monitor_id" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetMonitorID(v int64) *ChannelMonitorDailyRollupUpdate {
_u.mutation.SetMonitorID(v)
return _u
}
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableMonitorID(v *int64) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetMonitorID(*v)
}
return _u
}
// SetModel sets the "model" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetModel(v string) *ChannelMonitorDailyRollupUpdate {
_u.mutation.SetModel(v)
return _u
}
// SetNillableModel sets the "model" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableModel(v *string) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetModel(*v)
}
return _u
}
// SetBucketDate sets the "bucket_date" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetBucketDate(v time.Time) *ChannelMonitorDailyRollupUpdate {
_u.mutation.SetBucketDate(v)
return _u
}
// SetNillableBucketDate sets the "bucket_date" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableBucketDate(v *time.Time) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetBucketDate(*v)
}
return _u
}
// SetTotalChecks sets the "total_checks" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetTotalChecks(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetTotalChecks()
_u.mutation.SetTotalChecks(v)
return _u
}
// SetNillableTotalChecks sets the "total_checks" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableTotalChecks(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetTotalChecks(*v)
}
return _u
}
// AddTotalChecks adds value to the "total_checks" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddTotalChecks(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddTotalChecks(v)
return _u
}
// SetOkCount sets the "ok_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetOkCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetOkCount()
_u.mutation.SetOkCount(v)
return _u
}
// SetNillableOkCount sets the "ok_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableOkCount(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetOkCount(*v)
}
return _u
}
// AddOkCount adds value to the "ok_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddOkCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddOkCount(v)
return _u
}
// SetOperationalCount sets the "operational_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetOperationalCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetOperationalCount()
_u.mutation.SetOperationalCount(v)
return _u
}
// SetNillableOperationalCount sets the "operational_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableOperationalCount(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetOperationalCount(*v)
}
return _u
}
// AddOperationalCount adds value to the "operational_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddOperationalCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddOperationalCount(v)
return _u
}
// SetDegradedCount sets the "degraded_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetDegradedCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetDegradedCount()
_u.mutation.SetDegradedCount(v)
return _u
}
// SetNillableDegradedCount sets the "degraded_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableDegradedCount(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetDegradedCount(*v)
}
return _u
}
// AddDegradedCount adds value to the "degraded_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddDegradedCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddDegradedCount(v)
return _u
}
// SetFailedCount sets the "failed_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetFailedCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetFailedCount()
_u.mutation.SetFailedCount(v)
return _u
}
// SetNillableFailedCount sets the "failed_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableFailedCount(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetFailedCount(*v)
}
return _u
}
// AddFailedCount adds value to the "failed_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddFailedCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddFailedCount(v)
return _u
}
// SetErrorCount sets the "error_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetErrorCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetErrorCount()
_u.mutation.SetErrorCount(v)
return _u
}
// SetNillableErrorCount sets the "error_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableErrorCount(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetErrorCount(*v)
}
return _u
}
// AddErrorCount adds value to the "error_count" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddErrorCount(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddErrorCount(v)
return _u
}
// SetSumLatencyMs sets the "sum_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetSumLatencyMs(v int64) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetSumLatencyMs()
_u.mutation.SetSumLatencyMs(v)
return _u
}
// SetNillableSumLatencyMs sets the "sum_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableSumLatencyMs(v *int64) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetSumLatencyMs(*v)
}
return _u
}
// AddSumLatencyMs adds value to the "sum_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddSumLatencyMs(v int64) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddSumLatencyMs(v)
return _u
}
// SetCountLatency sets the "count_latency" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetCountLatency(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetCountLatency()
_u.mutation.SetCountLatency(v)
return _u
}
// SetNillableCountLatency sets the "count_latency" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableCountLatency(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetCountLatency(*v)
}
return _u
}
// AddCountLatency adds value to the "count_latency" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddCountLatency(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddCountLatency(v)
return _u
}
// SetSumPingLatencyMs sets the "sum_ping_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetSumPingLatencyMs(v int64) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetSumPingLatencyMs()
_u.mutation.SetSumPingLatencyMs(v)
return _u
}
// SetNillableSumPingLatencyMs sets the "sum_ping_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableSumPingLatencyMs(v *int64) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetSumPingLatencyMs(*v)
}
return _u
}
// AddSumPingLatencyMs adds value to the "sum_ping_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddSumPingLatencyMs(v int64) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddSumPingLatencyMs(v)
return _u
}
// SetCountPingLatency sets the "count_ping_latency" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetCountPingLatency(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.ResetCountPingLatency()
_u.mutation.SetCountPingLatency(v)
return _u
}
// SetNillableCountPingLatency sets the "count_ping_latency" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdate) SetNillableCountPingLatency(v *int) *ChannelMonitorDailyRollupUpdate {
if v != nil {
_u.SetCountPingLatency(*v)
}
return _u
}
// AddCountPingLatency adds value to the "count_ping_latency" field.
func (_u *ChannelMonitorDailyRollupUpdate) AddCountPingLatency(v int) *ChannelMonitorDailyRollupUpdate {
_u.mutation.AddCountPingLatency(v)
return _u
}
// SetComputedAt sets the "computed_at" field.
func (_u *ChannelMonitorDailyRollupUpdate) SetComputedAt(v time.Time) *ChannelMonitorDailyRollupUpdate {
_u.mutation.SetComputedAt(v)
return _u
}
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorDailyRollupUpdate) SetMonitor(v *ChannelMonitor) *ChannelMonitorDailyRollupUpdate {
return _u.SetMonitorID(v.ID)
}
// Mutation returns the ChannelMonitorDailyRollupMutation object of the builder.
func (_u *ChannelMonitorDailyRollupUpdate) Mutation() *ChannelMonitorDailyRollupMutation {
return _u.mutation
}
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorDailyRollupUpdate) ClearMonitor() *ChannelMonitorDailyRollupUpdate {
_u.mutation.ClearMonitor()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *ChannelMonitorDailyRollupUpdate) 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 *ChannelMonitorDailyRollupUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *ChannelMonitorDailyRollupUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *ChannelMonitorDailyRollupUpdate) 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 *ChannelMonitorDailyRollupUpdate) defaults() {
if _, ok := _u.mutation.ComputedAt(); !ok {
v := channelmonitordailyrollup.UpdateDefaultComputedAt()
_u.mutation.SetComputedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *ChannelMonitorDailyRollupUpdate) check() error {
if v, ok := _u.mutation.Model(); ok {
if err := channelmonitordailyrollup.ModelValidator(v); err != nil {
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorDailyRollup.model": %w`, err)}
}
}
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "ChannelMonitorDailyRollup.monitor"`)
}
return nil
}
func (_u *ChannelMonitorDailyRollupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(channelmonitordailyrollup.Table, channelmonitordailyrollup.Columns, sqlgraph.NewFieldSpec(channelmonitordailyrollup.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.Model(); ok {
_spec.SetField(channelmonitordailyrollup.FieldModel, field.TypeString, value)
}
if value, ok := _u.mutation.BucketDate(); ok {
_spec.SetField(channelmonitordailyrollup.FieldBucketDate, field.TypeTime, value)
}
if value, ok := _u.mutation.TotalChecks(); ok {
_spec.SetField(channelmonitordailyrollup.FieldTotalChecks, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedTotalChecks(); ok {
_spec.AddField(channelmonitordailyrollup.FieldTotalChecks, field.TypeInt, value)
}
if value, ok := _u.mutation.OkCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldOkCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedOkCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldOkCount, field.TypeInt, value)
}
if value, ok := _u.mutation.OperationalCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldOperationalCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedOperationalCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldOperationalCount, field.TypeInt, value)
}
if value, ok := _u.mutation.DegradedCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldDegradedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedDegradedCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldDegradedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.FailedCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldFailedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedFailedCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldFailedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.ErrorCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldErrorCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedErrorCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldErrorCount, field.TypeInt, value)
}
if value, ok := _u.mutation.SumLatencyMs(); ok {
_spec.SetField(channelmonitordailyrollup.FieldSumLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedSumLatencyMs(); ok {
_spec.AddField(channelmonitordailyrollup.FieldSumLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.CountLatency(); ok {
_spec.SetField(channelmonitordailyrollup.FieldCountLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedCountLatency(); ok {
_spec.AddField(channelmonitordailyrollup.FieldCountLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.SumPingLatencyMs(); ok {
_spec.SetField(channelmonitordailyrollup.FieldSumPingLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedSumPingLatencyMs(); ok {
_spec.AddField(channelmonitordailyrollup.FieldSumPingLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.CountPingLatency(); ok {
_spec.SetField(channelmonitordailyrollup.FieldCountPingLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedCountPingLatency(); ok {
_spec.AddField(channelmonitordailyrollup.FieldCountPingLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.ComputedAt(); ok {
_spec.SetField(channelmonitordailyrollup.FieldComputedAt, field.TypeTime, value)
}
if _u.mutation.MonitorCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitordailyrollup.MonitorTable,
Columns: []string{channelmonitordailyrollup.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitordailyrollup.MonitorTable,
Columns: []string{channelmonitordailyrollup.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{channelmonitordailyrollup.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// ChannelMonitorDailyRollupUpdateOne is the builder for updating a single ChannelMonitorDailyRollup entity.
type ChannelMonitorDailyRollupUpdateOne struct {
config
fields []string
hooks []Hook
mutation *ChannelMonitorDailyRollupMutation
}
// SetMonitorID sets the "monitor_id" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetMonitorID(v int64) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.SetMonitorID(v)
return _u
}
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableMonitorID(v *int64) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetMonitorID(*v)
}
return _u
}
// SetModel sets the "model" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetModel(v string) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.SetModel(v)
return _u
}
// SetNillableModel sets the "model" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableModel(v *string) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetModel(*v)
}
return _u
}
// SetBucketDate sets the "bucket_date" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetBucketDate(v time.Time) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.SetBucketDate(v)
return _u
}
// SetNillableBucketDate sets the "bucket_date" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableBucketDate(v *time.Time) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetBucketDate(*v)
}
return _u
}
// SetTotalChecks sets the "total_checks" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetTotalChecks(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetTotalChecks()
_u.mutation.SetTotalChecks(v)
return _u
}
// SetNillableTotalChecks sets the "total_checks" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableTotalChecks(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetTotalChecks(*v)
}
return _u
}
// AddTotalChecks adds value to the "total_checks" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddTotalChecks(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddTotalChecks(v)
return _u
}
// SetOkCount sets the "ok_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetOkCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetOkCount()
_u.mutation.SetOkCount(v)
return _u
}
// SetNillableOkCount sets the "ok_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableOkCount(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetOkCount(*v)
}
return _u
}
// AddOkCount adds value to the "ok_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddOkCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddOkCount(v)
return _u
}
// SetOperationalCount sets the "operational_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetOperationalCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetOperationalCount()
_u.mutation.SetOperationalCount(v)
return _u
}
// SetNillableOperationalCount sets the "operational_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableOperationalCount(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetOperationalCount(*v)
}
return _u
}
// AddOperationalCount adds value to the "operational_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddOperationalCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddOperationalCount(v)
return _u
}
// SetDegradedCount sets the "degraded_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetDegradedCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetDegradedCount()
_u.mutation.SetDegradedCount(v)
return _u
}
// SetNillableDegradedCount sets the "degraded_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableDegradedCount(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetDegradedCount(*v)
}
return _u
}
// AddDegradedCount adds value to the "degraded_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddDegradedCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddDegradedCount(v)
return _u
}
// SetFailedCount sets the "failed_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetFailedCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetFailedCount()
_u.mutation.SetFailedCount(v)
return _u
}
// SetNillableFailedCount sets the "failed_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableFailedCount(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetFailedCount(*v)
}
return _u
}
// AddFailedCount adds value to the "failed_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddFailedCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddFailedCount(v)
return _u
}
// SetErrorCount sets the "error_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetErrorCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetErrorCount()
_u.mutation.SetErrorCount(v)
return _u
}
// SetNillableErrorCount sets the "error_count" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableErrorCount(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetErrorCount(*v)
}
return _u
}
// AddErrorCount adds value to the "error_count" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddErrorCount(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddErrorCount(v)
return _u
}
// SetSumLatencyMs sets the "sum_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetSumLatencyMs(v int64) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetSumLatencyMs()
_u.mutation.SetSumLatencyMs(v)
return _u
}
// SetNillableSumLatencyMs sets the "sum_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableSumLatencyMs(v *int64) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetSumLatencyMs(*v)
}
return _u
}
// AddSumLatencyMs adds value to the "sum_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddSumLatencyMs(v int64) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddSumLatencyMs(v)
return _u
}
// SetCountLatency sets the "count_latency" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetCountLatency(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetCountLatency()
_u.mutation.SetCountLatency(v)
return _u
}
// SetNillableCountLatency sets the "count_latency" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableCountLatency(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetCountLatency(*v)
}
return _u
}
// AddCountLatency adds value to the "count_latency" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddCountLatency(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddCountLatency(v)
return _u
}
// SetSumPingLatencyMs sets the "sum_ping_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetSumPingLatencyMs(v int64) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetSumPingLatencyMs()
_u.mutation.SetSumPingLatencyMs(v)
return _u
}
// SetNillableSumPingLatencyMs sets the "sum_ping_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableSumPingLatencyMs(v *int64) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetSumPingLatencyMs(*v)
}
return _u
}
// AddSumPingLatencyMs adds value to the "sum_ping_latency_ms" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddSumPingLatencyMs(v int64) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddSumPingLatencyMs(v)
return _u
}
// SetCountPingLatency sets the "count_ping_latency" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetCountPingLatency(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ResetCountPingLatency()
_u.mutation.SetCountPingLatency(v)
return _u
}
// SetNillableCountPingLatency sets the "count_ping_latency" field if the given value is not nil.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetNillableCountPingLatency(v *int) *ChannelMonitorDailyRollupUpdateOne {
if v != nil {
_u.SetCountPingLatency(*v)
}
return _u
}
// AddCountPingLatency adds value to the "count_ping_latency" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) AddCountPingLatency(v int) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.AddCountPingLatency(v)
return _u
}
// SetComputedAt sets the "computed_at" field.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetComputedAt(v time.Time) *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.SetComputedAt(v)
return _u
}
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorDailyRollupUpdateOne) SetMonitor(v *ChannelMonitor) *ChannelMonitorDailyRollupUpdateOne {
return _u.SetMonitorID(v.ID)
}
// Mutation returns the ChannelMonitorDailyRollupMutation object of the builder.
func (_u *ChannelMonitorDailyRollupUpdateOne) Mutation() *ChannelMonitorDailyRollupMutation {
return _u.mutation
}
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorDailyRollupUpdateOne) ClearMonitor() *ChannelMonitorDailyRollupUpdateOne {
_u.mutation.ClearMonitor()
return _u
}
// Where appends a list predicates to the ChannelMonitorDailyRollupUpdate builder.
func (_u *ChannelMonitorDailyRollupUpdateOne) Where(ps ...predicate.ChannelMonitorDailyRollup) *ChannelMonitorDailyRollupUpdateOne {
_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 *ChannelMonitorDailyRollupUpdateOne) Select(field string, fields ...string) *ChannelMonitorDailyRollupUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated ChannelMonitorDailyRollup entity.
func (_u *ChannelMonitorDailyRollupUpdateOne) Save(ctx context.Context) (*ChannelMonitorDailyRollup, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *ChannelMonitorDailyRollupUpdateOne) SaveX(ctx context.Context) *ChannelMonitorDailyRollup {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *ChannelMonitorDailyRollupUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *ChannelMonitorDailyRollupUpdateOne) 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 *ChannelMonitorDailyRollupUpdateOne) defaults() {
if _, ok := _u.mutation.ComputedAt(); !ok {
v := channelmonitordailyrollup.UpdateDefaultComputedAt()
_u.mutation.SetComputedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *ChannelMonitorDailyRollupUpdateOne) check() error {
if v, ok := _u.mutation.Model(); ok {
if err := channelmonitordailyrollup.ModelValidator(v); err != nil {
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorDailyRollup.model": %w`, err)}
}
}
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "ChannelMonitorDailyRollup.monitor"`)
}
return nil
}
func (_u *ChannelMonitorDailyRollupUpdateOne) sqlSave(ctx context.Context) (_node *ChannelMonitorDailyRollup, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(channelmonitordailyrollup.Table, channelmonitordailyrollup.Columns, sqlgraph.NewFieldSpec(channelmonitordailyrollup.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ChannelMonitorDailyRollup.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, channelmonitordailyrollup.FieldID)
for _, f := range fields {
if !channelmonitordailyrollup.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != channelmonitordailyrollup.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.Model(); ok {
_spec.SetField(channelmonitordailyrollup.FieldModel, field.TypeString, value)
}
if value, ok := _u.mutation.BucketDate(); ok {
_spec.SetField(channelmonitordailyrollup.FieldBucketDate, field.TypeTime, value)
}
if value, ok := _u.mutation.TotalChecks(); ok {
_spec.SetField(channelmonitordailyrollup.FieldTotalChecks, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedTotalChecks(); ok {
_spec.AddField(channelmonitordailyrollup.FieldTotalChecks, field.TypeInt, value)
}
if value, ok := _u.mutation.OkCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldOkCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedOkCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldOkCount, field.TypeInt, value)
}
if value, ok := _u.mutation.OperationalCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldOperationalCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedOperationalCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldOperationalCount, field.TypeInt, value)
}
if value, ok := _u.mutation.DegradedCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldDegradedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedDegradedCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldDegradedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.FailedCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldFailedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedFailedCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldFailedCount, field.TypeInt, value)
}
if value, ok := _u.mutation.ErrorCount(); ok {
_spec.SetField(channelmonitordailyrollup.FieldErrorCount, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedErrorCount(); ok {
_spec.AddField(channelmonitordailyrollup.FieldErrorCount, field.TypeInt, value)
}
if value, ok := _u.mutation.SumLatencyMs(); ok {
_spec.SetField(channelmonitordailyrollup.FieldSumLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedSumLatencyMs(); ok {
_spec.AddField(channelmonitordailyrollup.FieldSumLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.CountLatency(); ok {
_spec.SetField(channelmonitordailyrollup.FieldCountLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedCountLatency(); ok {
_spec.AddField(channelmonitordailyrollup.FieldCountLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.SumPingLatencyMs(); ok {
_spec.SetField(channelmonitordailyrollup.FieldSumPingLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedSumPingLatencyMs(); ok {
_spec.AddField(channelmonitordailyrollup.FieldSumPingLatencyMs, field.TypeInt64, value)
}
if value, ok := _u.mutation.CountPingLatency(); ok {
_spec.SetField(channelmonitordailyrollup.FieldCountPingLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedCountPingLatency(); ok {
_spec.AddField(channelmonitordailyrollup.FieldCountPingLatency, field.TypeInt, value)
}
if value, ok := _u.mutation.ComputedAt(); ok {
_spec.SetField(channelmonitordailyrollup.FieldComputedAt, field.TypeTime, value)
}
if _u.mutation.MonitorCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitordailyrollup.MonitorTable,
Columns: []string{channelmonitordailyrollup.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitordailyrollup.MonitorTable,
Columns: []string{channelmonitordailyrollup.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &ChannelMonitorDailyRollup{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{channelmonitordailyrollup.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

@@ -0,0 +1,207 @@
// 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
)
// ChannelMonitorHistory is the model entity for the ChannelMonitorHistory schema.
type ChannelMonitorHistory struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// MonitorID holds the value of the "monitor_id" field.
MonitorID int64 `json:"monitor_id,omitempty"`
// Model holds the value of the "model" field.
Model string `json:"model,omitempty"`
// Status holds the value of the "status" field.
Status channelmonitorhistory.Status `json:"status,omitempty"`
// LatencyMs holds the value of the "latency_ms" field.
LatencyMs *int `json:"latency_ms,omitempty"`
// PingLatencyMs holds the value of the "ping_latency_ms" field.
PingLatencyMs *int `json:"ping_latency_ms,omitempty"`
// Message holds the value of the "message" field.
Message string `json:"message,omitempty"`
// CheckedAt holds the value of the "checked_at" field.
CheckedAt time.Time `json:"checked_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ChannelMonitorHistoryQuery when eager-loading is set.
Edges ChannelMonitorHistoryEdges `json:"edges"`
selectValues sql.SelectValues
}
// ChannelMonitorHistoryEdges holds the relations/edges for other nodes in the graph.
type ChannelMonitorHistoryEdges struct {
// Monitor holds the value of the monitor edge.
Monitor *ChannelMonitor `json:"monitor,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// MonitorOrErr returns the Monitor value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e ChannelMonitorHistoryEdges) MonitorOrErr() (*ChannelMonitor, error) {
if e.Monitor != nil {
return e.Monitor, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: channelmonitor.Label}
}
return nil, &NotLoadedError{edge: "monitor"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*ChannelMonitorHistory) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case channelmonitorhistory.FieldID, channelmonitorhistory.FieldMonitorID, channelmonitorhistory.FieldLatencyMs, channelmonitorhistory.FieldPingLatencyMs:
values[i] = new(sql.NullInt64)
case channelmonitorhistory.FieldModel, channelmonitorhistory.FieldStatus, channelmonitorhistory.FieldMessage:
values[i] = new(sql.NullString)
case channelmonitorhistory.FieldCheckedAt:
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 ChannelMonitorHistory fields.
func (_m *ChannelMonitorHistory) 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 channelmonitorhistory.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 channelmonitorhistory.FieldMonitorID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field monitor_id", values[i])
} else if value.Valid {
_m.MonitorID = value.Int64
}
case channelmonitorhistory.FieldModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field model", values[i])
} else if value.Valid {
_m.Model = value.String
}
case channelmonitorhistory.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 = channelmonitorhistory.Status(value.String)
}
case channelmonitorhistory.FieldLatencyMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field latency_ms", values[i])
} else if value.Valid {
_m.LatencyMs = new(int)
*_m.LatencyMs = int(value.Int64)
}
case channelmonitorhistory.FieldPingLatencyMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field ping_latency_ms", values[i])
} else if value.Valid {
_m.PingLatencyMs = new(int)
*_m.PingLatencyMs = int(value.Int64)
}
case channelmonitorhistory.FieldMessage:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field message", values[i])
} else if value.Valid {
_m.Message = value.String
}
case channelmonitorhistory.FieldCheckedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field checked_at", values[i])
} else if value.Valid {
_m.CheckedAt = 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 ChannelMonitorHistory.
// This includes values selected through modifiers, order, etc.
func (_m *ChannelMonitorHistory) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryMonitor queries the "monitor" edge of the ChannelMonitorHistory entity.
func (_m *ChannelMonitorHistory) QueryMonitor() *ChannelMonitorQuery {
return NewChannelMonitorHistoryClient(_m.config).QueryMonitor(_m)
}
// Update returns a builder for updating this ChannelMonitorHistory.
// Note that you need to call ChannelMonitorHistory.Unwrap() before calling this method if this ChannelMonitorHistory
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *ChannelMonitorHistory) Update() *ChannelMonitorHistoryUpdateOne {
return NewChannelMonitorHistoryClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the ChannelMonitorHistory 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 *ChannelMonitorHistory) Unwrap() *ChannelMonitorHistory {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: ChannelMonitorHistory is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *ChannelMonitorHistory) String() string {
var builder strings.Builder
builder.WriteString("ChannelMonitorHistory(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("monitor_id=")
builder.WriteString(fmt.Sprintf("%v", _m.MonitorID))
builder.WriteString(", ")
builder.WriteString("model=")
builder.WriteString(_m.Model)
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(fmt.Sprintf("%v", _m.Status))
builder.WriteString(", ")
if v := _m.LatencyMs; v != nil {
builder.WriteString("latency_ms=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.PingLatencyMs; v != nil {
builder.WriteString("ping_latency_ms=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("message=")
builder.WriteString(_m.Message)
builder.WriteString(", ")
builder.WriteString("checked_at=")
builder.WriteString(_m.CheckedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// ChannelMonitorHistories is a parsable slice of ChannelMonitorHistory.
type ChannelMonitorHistories []*ChannelMonitorHistory

View File

@@ -0,0 +1,158 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitorhistory
import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the channelmonitorhistory type in the database.
Label = "channel_monitor_history"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldMonitorID holds the string denoting the monitor_id field in the database.
FieldMonitorID = "monitor_id"
// FieldModel holds the string denoting the model field in the database.
FieldModel = "model"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldLatencyMs holds the string denoting the latency_ms field in the database.
FieldLatencyMs = "latency_ms"
// FieldPingLatencyMs holds the string denoting the ping_latency_ms field in the database.
FieldPingLatencyMs = "ping_latency_ms"
// FieldMessage holds the string denoting the message field in the database.
FieldMessage = "message"
// FieldCheckedAt holds the string denoting the checked_at field in the database.
FieldCheckedAt = "checked_at"
// EdgeMonitor holds the string denoting the monitor edge name in mutations.
EdgeMonitor = "monitor"
// Table holds the table name of the channelmonitorhistory in the database.
Table = "channel_monitor_histories"
// MonitorTable is the table that holds the monitor relation/edge.
MonitorTable = "channel_monitor_histories"
// MonitorInverseTable is the table name for the ChannelMonitor entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitor" package.
MonitorInverseTable = "channel_monitors"
// MonitorColumn is the table column denoting the monitor relation/edge.
MonitorColumn = "monitor_id"
)
// Columns holds all SQL columns for channelmonitorhistory fields.
var Columns = []string{
FieldID,
FieldMonitorID,
FieldModel,
FieldStatus,
FieldLatencyMs,
FieldPingLatencyMs,
FieldMessage,
FieldCheckedAt,
}
// 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 (
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
ModelValidator func(string) error
// DefaultMessage holds the default value on creation for the "message" field.
DefaultMessage string
// MessageValidator is a validator for the "message" field. It is called by the builders before save.
MessageValidator func(string) error
// DefaultCheckedAt holds the default value on creation for the "checked_at" field.
DefaultCheckedAt func() time.Time
)
// Status defines the type for the "status" enum field.
type Status string
// Status values.
const (
StatusOperational Status = "operational"
StatusDegraded Status = "degraded"
StatusFailed Status = "failed"
StatusError Status = "error"
)
func (s Status) String() string {
return string(s)
}
// StatusValidator is a validator for the "status" field enum values. It is called by the builders before save.
func StatusValidator(s Status) error {
switch s {
case StatusOperational, StatusDegraded, StatusFailed, StatusError:
return nil
default:
return fmt.Errorf("channelmonitorhistory: invalid enum value for status field: %q", s)
}
}
// OrderOption defines the ordering options for the ChannelMonitorHistory 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()
}
// ByMonitorID orders the results by the monitor_id field.
func ByMonitorID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMonitorID, opts...).ToFunc()
}
// ByModel orders the results by the model field.
func ByModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldModel, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByLatencyMs orders the results by the latency_ms field.
func ByLatencyMs(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLatencyMs, opts...).ToFunc()
}
// ByPingLatencyMs orders the results by the ping_latency_ms field.
func ByPingLatencyMs(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPingLatencyMs, opts...).ToFunc()
}
// ByMessage orders the results by the message field.
func ByMessage(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMessage, opts...).ToFunc()
}
// ByCheckedAt orders the results by the checked_at field.
func ByCheckedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCheckedAt, opts...).ToFunc()
}
// ByMonitorField orders the results by monitor field.
func ByMonitorField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newMonitorStep(), sql.OrderByField(field, opts...))
}
}
func newMonitorStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(MonitorInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
)
}

View File

@@ -0,0 +1,444 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitorhistory
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldID, id))
}
// MonitorID applies equality check predicate on the "monitor_id" field. It's identical to MonitorIDEQ.
func MonitorID(v int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMonitorID, v))
}
// Model applies equality check predicate on the "model" field. It's identical to ModelEQ.
func Model(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldModel, v))
}
// LatencyMs applies equality check predicate on the "latency_ms" field. It's identical to LatencyMsEQ.
func LatencyMs(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldLatencyMs, v))
}
// PingLatencyMs applies equality check predicate on the "ping_latency_ms" field. It's identical to PingLatencyMsEQ.
func PingLatencyMs(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldPingLatencyMs, v))
}
// Message applies equality check predicate on the "message" field. It's identical to MessageEQ.
func Message(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMessage, v))
}
// CheckedAt applies equality check predicate on the "checked_at" field. It's identical to CheckedAtEQ.
func CheckedAt(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldCheckedAt, v))
}
// MonitorIDEQ applies the EQ predicate on the "monitor_id" field.
func MonitorIDEQ(v int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMonitorID, v))
}
// MonitorIDNEQ applies the NEQ predicate on the "monitor_id" field.
func MonitorIDNEQ(v int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldMonitorID, v))
}
// MonitorIDIn applies the In predicate on the "monitor_id" field.
func MonitorIDIn(vs ...int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldMonitorID, vs...))
}
// MonitorIDNotIn applies the NotIn predicate on the "monitor_id" field.
func MonitorIDNotIn(vs ...int64) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldMonitorID, vs...))
}
// ModelEQ applies the EQ predicate on the "model" field.
func ModelEQ(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldModel, v))
}
// ModelNEQ applies the NEQ predicate on the "model" field.
func ModelNEQ(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldModel, v))
}
// ModelIn applies the In predicate on the "model" field.
func ModelIn(vs ...string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldModel, vs...))
}
// ModelNotIn applies the NotIn predicate on the "model" field.
func ModelNotIn(vs ...string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldModel, vs...))
}
// ModelGT applies the GT predicate on the "model" field.
func ModelGT(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldModel, v))
}
// ModelGTE applies the GTE predicate on the "model" field.
func ModelGTE(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldModel, v))
}
// ModelLT applies the LT predicate on the "model" field.
func ModelLT(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldModel, v))
}
// ModelLTE applies the LTE predicate on the "model" field.
func ModelLTE(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldModel, v))
}
// ModelContains applies the Contains predicate on the "model" field.
func ModelContains(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldContains(FieldModel, v))
}
// ModelHasPrefix applies the HasPrefix predicate on the "model" field.
func ModelHasPrefix(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldHasPrefix(FieldModel, v))
}
// ModelHasSuffix applies the HasSuffix predicate on the "model" field.
func ModelHasSuffix(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldHasSuffix(FieldModel, v))
}
// ModelEqualFold applies the EqualFold predicate on the "model" field.
func ModelEqualFold(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEqualFold(FieldModel, v))
}
// ModelContainsFold applies the ContainsFold predicate on the "model" field.
func ModelContainsFold(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldContainsFold(FieldModel, v))
}
// StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v Status) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldStatus, v))
}
// StatusNEQ applies the NEQ predicate on the "status" field.
func StatusNEQ(v Status) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldStatus, v))
}
// StatusIn applies the In predicate on the "status" field.
func StatusIn(vs ...Status) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldStatus, vs...))
}
// StatusNotIn applies the NotIn predicate on the "status" field.
func StatusNotIn(vs ...Status) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldStatus, vs...))
}
// LatencyMsEQ applies the EQ predicate on the "latency_ms" field.
func LatencyMsEQ(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldLatencyMs, v))
}
// LatencyMsNEQ applies the NEQ predicate on the "latency_ms" field.
func LatencyMsNEQ(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldLatencyMs, v))
}
// LatencyMsIn applies the In predicate on the "latency_ms" field.
func LatencyMsIn(vs ...int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldLatencyMs, vs...))
}
// LatencyMsNotIn applies the NotIn predicate on the "latency_ms" field.
func LatencyMsNotIn(vs ...int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldLatencyMs, vs...))
}
// LatencyMsGT applies the GT predicate on the "latency_ms" field.
func LatencyMsGT(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldLatencyMs, v))
}
// LatencyMsGTE applies the GTE predicate on the "latency_ms" field.
func LatencyMsGTE(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldLatencyMs, v))
}
// LatencyMsLT applies the LT predicate on the "latency_ms" field.
func LatencyMsLT(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldLatencyMs, v))
}
// LatencyMsLTE applies the LTE predicate on the "latency_ms" field.
func LatencyMsLTE(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldLatencyMs, v))
}
// LatencyMsIsNil applies the IsNil predicate on the "latency_ms" field.
func LatencyMsIsNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldLatencyMs))
}
// LatencyMsNotNil applies the NotNil predicate on the "latency_ms" field.
func LatencyMsNotNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldLatencyMs))
}
// PingLatencyMsEQ applies the EQ predicate on the "ping_latency_ms" field.
func PingLatencyMsEQ(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldPingLatencyMs, v))
}
// PingLatencyMsNEQ applies the NEQ predicate on the "ping_latency_ms" field.
func PingLatencyMsNEQ(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldPingLatencyMs, v))
}
// PingLatencyMsIn applies the In predicate on the "ping_latency_ms" field.
func PingLatencyMsIn(vs ...int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldPingLatencyMs, vs...))
}
// PingLatencyMsNotIn applies the NotIn predicate on the "ping_latency_ms" field.
func PingLatencyMsNotIn(vs ...int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldPingLatencyMs, vs...))
}
// PingLatencyMsGT applies the GT predicate on the "ping_latency_ms" field.
func PingLatencyMsGT(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldPingLatencyMs, v))
}
// PingLatencyMsGTE applies the GTE predicate on the "ping_latency_ms" field.
func PingLatencyMsGTE(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldPingLatencyMs, v))
}
// PingLatencyMsLT applies the LT predicate on the "ping_latency_ms" field.
func PingLatencyMsLT(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldPingLatencyMs, v))
}
// PingLatencyMsLTE applies the LTE predicate on the "ping_latency_ms" field.
func PingLatencyMsLTE(v int) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldPingLatencyMs, v))
}
// PingLatencyMsIsNil applies the IsNil predicate on the "ping_latency_ms" field.
func PingLatencyMsIsNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldPingLatencyMs))
}
// PingLatencyMsNotNil applies the NotNil predicate on the "ping_latency_ms" field.
func PingLatencyMsNotNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldPingLatencyMs))
}
// MessageEQ applies the EQ predicate on the "message" field.
func MessageEQ(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMessage, v))
}
// MessageNEQ applies the NEQ predicate on the "message" field.
func MessageNEQ(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldMessage, v))
}
// MessageIn applies the In predicate on the "message" field.
func MessageIn(vs ...string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldMessage, vs...))
}
// MessageNotIn applies the NotIn predicate on the "message" field.
func MessageNotIn(vs ...string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldMessage, vs...))
}
// MessageGT applies the GT predicate on the "message" field.
func MessageGT(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldMessage, v))
}
// MessageGTE applies the GTE predicate on the "message" field.
func MessageGTE(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldMessage, v))
}
// MessageLT applies the LT predicate on the "message" field.
func MessageLT(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldMessage, v))
}
// MessageLTE applies the LTE predicate on the "message" field.
func MessageLTE(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldMessage, v))
}
// MessageContains applies the Contains predicate on the "message" field.
func MessageContains(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldContains(FieldMessage, v))
}
// MessageHasPrefix applies the HasPrefix predicate on the "message" field.
func MessageHasPrefix(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldHasPrefix(FieldMessage, v))
}
// MessageHasSuffix applies the HasSuffix predicate on the "message" field.
func MessageHasSuffix(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldHasSuffix(FieldMessage, v))
}
// MessageIsNil applies the IsNil predicate on the "message" field.
func MessageIsNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldMessage))
}
// MessageNotNil applies the NotNil predicate on the "message" field.
func MessageNotNil() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldMessage))
}
// MessageEqualFold applies the EqualFold predicate on the "message" field.
func MessageEqualFold(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEqualFold(FieldMessage, v))
}
// MessageContainsFold applies the ContainsFold predicate on the "message" field.
func MessageContainsFold(v string) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldContainsFold(FieldMessage, v))
}
// CheckedAtEQ applies the EQ predicate on the "checked_at" field.
func CheckedAtEQ(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldCheckedAt, v))
}
// CheckedAtNEQ applies the NEQ predicate on the "checked_at" field.
func CheckedAtNEQ(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldCheckedAt, v))
}
// CheckedAtIn applies the In predicate on the "checked_at" field.
func CheckedAtIn(vs ...time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldCheckedAt, vs...))
}
// CheckedAtNotIn applies the NotIn predicate on the "checked_at" field.
func CheckedAtNotIn(vs ...time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldCheckedAt, vs...))
}
// CheckedAtGT applies the GT predicate on the "checked_at" field.
func CheckedAtGT(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldCheckedAt, v))
}
// CheckedAtGTE applies the GTE predicate on the "checked_at" field.
func CheckedAtGTE(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldCheckedAt, v))
}
// CheckedAtLT applies the LT predicate on the "checked_at" field.
func CheckedAtLT(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldCheckedAt, v))
}
// CheckedAtLTE applies the LTE predicate on the "checked_at" field.
func CheckedAtLTE(v time.Time) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldCheckedAt, v))
}
// HasMonitor applies the HasEdge predicate on the "monitor" edge.
func HasMonitor() predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasMonitorWith applies the HasEdge predicate on the "monitor" edge with a given conditions (other predicates).
func HasMonitorWith(preds ...predicate.ChannelMonitor) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(func(s *sql.Selector) {
step := newMonitorStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
return predicate.ChannelMonitorHistory(sql.NotPredicates(p))
}

View File

@@ -0,0 +1,947 @@
// 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
)
// ChannelMonitorHistoryCreate is the builder for creating a ChannelMonitorHistory entity.
type ChannelMonitorHistoryCreate struct {
config
mutation *ChannelMonitorHistoryMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetMonitorID sets the "monitor_id" field.
func (_c *ChannelMonitorHistoryCreate) SetMonitorID(v int64) *ChannelMonitorHistoryCreate {
_c.mutation.SetMonitorID(v)
return _c
}
// SetModel sets the "model" field.
func (_c *ChannelMonitorHistoryCreate) SetModel(v string) *ChannelMonitorHistoryCreate {
_c.mutation.SetModel(v)
return _c
}
// SetStatus sets the "status" field.
func (_c *ChannelMonitorHistoryCreate) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryCreate {
_c.mutation.SetStatus(v)
return _c
}
// SetLatencyMs sets the "latency_ms" field.
func (_c *ChannelMonitorHistoryCreate) SetLatencyMs(v int) *ChannelMonitorHistoryCreate {
_c.mutation.SetLatencyMs(v)
return _c
}
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
func (_c *ChannelMonitorHistoryCreate) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryCreate {
if v != nil {
_c.SetLatencyMs(*v)
}
return _c
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (_c *ChannelMonitorHistoryCreate) SetPingLatencyMs(v int) *ChannelMonitorHistoryCreate {
_c.mutation.SetPingLatencyMs(v)
return _c
}
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
func (_c *ChannelMonitorHistoryCreate) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryCreate {
if v != nil {
_c.SetPingLatencyMs(*v)
}
return _c
}
// SetMessage sets the "message" field.
func (_c *ChannelMonitorHistoryCreate) SetMessage(v string) *ChannelMonitorHistoryCreate {
_c.mutation.SetMessage(v)
return _c
}
// SetNillableMessage sets the "message" field if the given value is not nil.
func (_c *ChannelMonitorHistoryCreate) SetNillableMessage(v *string) *ChannelMonitorHistoryCreate {
if v != nil {
_c.SetMessage(*v)
}
return _c
}
// SetCheckedAt sets the "checked_at" field.
func (_c *ChannelMonitorHistoryCreate) SetCheckedAt(v time.Time) *ChannelMonitorHistoryCreate {
_c.mutation.SetCheckedAt(v)
return _c
}
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
func (_c *ChannelMonitorHistoryCreate) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryCreate {
if v != nil {
_c.SetCheckedAt(*v)
}
return _c
}
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
func (_c *ChannelMonitorHistoryCreate) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryCreate {
return _c.SetMonitorID(v.ID)
}
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
func (_c *ChannelMonitorHistoryCreate) Mutation() *ChannelMonitorHistoryMutation {
return _c.mutation
}
// Save creates the ChannelMonitorHistory in the database.
func (_c *ChannelMonitorHistoryCreate) Save(ctx context.Context) (*ChannelMonitorHistory, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *ChannelMonitorHistoryCreate) SaveX(ctx context.Context) *ChannelMonitorHistory {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *ChannelMonitorHistoryCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *ChannelMonitorHistoryCreate) 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 *ChannelMonitorHistoryCreate) defaults() {
if _, ok := _c.mutation.Message(); !ok {
v := channelmonitorhistory.DefaultMessage
_c.mutation.SetMessage(v)
}
if _, ok := _c.mutation.CheckedAt(); !ok {
v := channelmonitorhistory.DefaultCheckedAt()
_c.mutation.SetCheckedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *ChannelMonitorHistoryCreate) check() error {
if _, ok := _c.mutation.MonitorID(); !ok {
return &ValidationError{Name: "monitor_id", err: errors.New(`ent: missing required field "ChannelMonitorHistory.monitor_id"`)}
}
if _, ok := _c.mutation.Model(); !ok {
return &ValidationError{Name: "model", err: errors.New(`ent: missing required field "ChannelMonitorHistory.model"`)}
}
if v, ok := _c.mutation.Model(); ok {
if err := channelmonitorhistory.ModelValidator(v); err != nil {
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
}
}
if _, ok := _c.mutation.Status(); !ok {
return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "ChannelMonitorHistory.status"`)}
}
if v, ok := _c.mutation.Status(); ok {
if err := channelmonitorhistory.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
}
}
if v, ok := _c.mutation.Message(); ok {
if err := channelmonitorhistory.MessageValidator(v); err != nil {
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
}
}
if _, ok := _c.mutation.CheckedAt(); !ok {
return &ValidationError{Name: "checked_at", err: errors.New(`ent: missing required field "ChannelMonitorHistory.checked_at"`)}
}
if len(_c.mutation.MonitorIDs()) == 0 {
return &ValidationError{Name: "monitor", err: errors.New(`ent: missing required edge "ChannelMonitorHistory.monitor"`)}
}
return nil
}
func (_c *ChannelMonitorHistoryCreate) sqlSave(ctx context.Context) (*ChannelMonitorHistory, 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 *ChannelMonitorHistoryCreate) createSpec() (*ChannelMonitorHistory, *sqlgraph.CreateSpec) {
var (
_node = &ChannelMonitorHistory{config: _c.config}
_spec = sqlgraph.NewCreateSpec(channelmonitorhistory.Table, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.Model(); ok {
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
_node.Model = value
}
if value, ok := _c.mutation.Status(); ok {
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
_node.Status = value
}
if value, ok := _c.mutation.LatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
_node.LatencyMs = &value
}
if value, ok := _c.mutation.PingLatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
_node.PingLatencyMs = &value
}
if value, ok := _c.mutation.Message(); ok {
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
_node.Message = value
}
if value, ok := _c.mutation.CheckedAt(); ok {
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
_node.CheckedAt = value
}
if nodes := _c.mutation.MonitorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitorhistory.MonitorTable,
Columns: []string{channelmonitorhistory.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_node.MonitorID = nodes[0]
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.ChannelMonitorHistory.Create().
// SetMonitorID(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.ChannelMonitorHistoryUpsert) {
// SetMonitorID(v+v).
// }).
// Exec(ctx)
func (_c *ChannelMonitorHistoryCreate) OnConflict(opts ...sql.ConflictOption) *ChannelMonitorHistoryUpsertOne {
_c.conflict = opts
return &ChannelMonitorHistoryUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *ChannelMonitorHistoryCreate) OnConflictColumns(columns ...string) *ChannelMonitorHistoryUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &ChannelMonitorHistoryUpsertOne{
create: _c,
}
}
type (
// ChannelMonitorHistoryUpsertOne is the builder for "upsert"-ing
// one ChannelMonitorHistory node.
ChannelMonitorHistoryUpsertOne struct {
create *ChannelMonitorHistoryCreate
}
// ChannelMonitorHistoryUpsert is the "OnConflict" setter.
ChannelMonitorHistoryUpsert struct {
*sql.UpdateSet
}
)
// SetMonitorID sets the "monitor_id" field.
func (u *ChannelMonitorHistoryUpsert) SetMonitorID(v int64) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldMonitorID, v)
return u
}
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateMonitorID() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldMonitorID)
return u
}
// SetModel sets the "model" field.
func (u *ChannelMonitorHistoryUpsert) SetModel(v string) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldModel, v)
return u
}
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateModel() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldModel)
return u
}
// SetStatus sets the "status" field.
func (u *ChannelMonitorHistoryUpsert) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldStatus, v)
return u
}
// UpdateStatus sets the "status" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateStatus() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldStatus)
return u
}
// SetLatencyMs sets the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) SetLatencyMs(v int) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldLatencyMs, v)
return u
}
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateLatencyMs() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldLatencyMs)
return u
}
// AddLatencyMs adds v to the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) AddLatencyMs(v int) *ChannelMonitorHistoryUpsert {
u.Add(channelmonitorhistory.FieldLatencyMs, v)
return u
}
// ClearLatencyMs clears the value of the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) ClearLatencyMs() *ChannelMonitorHistoryUpsert {
u.SetNull(channelmonitorhistory.FieldLatencyMs)
return u
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldPingLatencyMs, v)
return u
}
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldPingLatencyMs)
return u
}
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsert {
u.Add(channelmonitorhistory.FieldPingLatencyMs, v)
return u
}
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsert) ClearPingLatencyMs() *ChannelMonitorHistoryUpsert {
u.SetNull(channelmonitorhistory.FieldPingLatencyMs)
return u
}
// SetMessage sets the "message" field.
func (u *ChannelMonitorHistoryUpsert) SetMessage(v string) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldMessage, v)
return u
}
// UpdateMessage sets the "message" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateMessage() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldMessage)
return u
}
// ClearMessage clears the value of the "message" field.
func (u *ChannelMonitorHistoryUpsert) ClearMessage() *ChannelMonitorHistoryUpsert {
u.SetNull(channelmonitorhistory.FieldMessage)
return u
}
// SetCheckedAt sets the "checked_at" field.
func (u *ChannelMonitorHistoryUpsert) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsert {
u.Set(channelmonitorhistory.FieldCheckedAt, v)
return u
}
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsert) UpdateCheckedAt() *ChannelMonitorHistoryUpsert {
u.SetExcluded(channelmonitorhistory.FieldCheckedAt)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *ChannelMonitorHistoryUpsertOne) UpdateNewValues() *ChannelMonitorHistoryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *ChannelMonitorHistoryUpsertOne) Ignore() *ChannelMonitorHistoryUpsertOne {
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 *ChannelMonitorHistoryUpsertOne) DoNothing() *ChannelMonitorHistoryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the ChannelMonitorHistoryCreate.OnConflict
// documentation for more info.
func (u *ChannelMonitorHistoryUpsertOne) Update(set func(*ChannelMonitorHistoryUpsert)) *ChannelMonitorHistoryUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&ChannelMonitorHistoryUpsert{UpdateSet: update})
}))
return u
}
// SetMonitorID sets the "monitor_id" field.
func (u *ChannelMonitorHistoryUpsertOne) SetMonitorID(v int64) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetMonitorID(v)
})
}
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateMonitorID() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateMonitorID()
})
}
// SetModel sets the "model" field.
func (u *ChannelMonitorHistoryUpsertOne) SetModel(v string) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetModel(v)
})
}
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateModel() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateModel()
})
}
// SetStatus sets the "status" field.
func (u *ChannelMonitorHistoryUpsertOne) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetStatus(v)
})
}
// UpdateStatus sets the "status" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateStatus() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateStatus()
})
}
// SetLatencyMs sets the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) SetLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetLatencyMs(v)
})
}
// AddLatencyMs adds v to the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) AddLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.AddLatencyMs(v)
})
}
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateLatencyMs() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateLatencyMs()
})
}
// ClearLatencyMs clears the value of the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) ClearLatencyMs() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearLatencyMs()
})
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetPingLatencyMs(v)
})
}
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.AddPingLatencyMs(v)
})
}
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdatePingLatencyMs()
})
}
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertOne) ClearPingLatencyMs() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearPingLatencyMs()
})
}
// SetMessage sets the "message" field.
func (u *ChannelMonitorHistoryUpsertOne) SetMessage(v string) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetMessage(v)
})
}
// UpdateMessage sets the "message" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateMessage() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateMessage()
})
}
// ClearMessage clears the value of the "message" field.
func (u *ChannelMonitorHistoryUpsertOne) ClearMessage() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearMessage()
})
}
// SetCheckedAt sets the "checked_at" field.
func (u *ChannelMonitorHistoryUpsertOne) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetCheckedAt(v)
})
}
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertOne) UpdateCheckedAt() *ChannelMonitorHistoryUpsertOne {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateCheckedAt()
})
}
// Exec executes the query.
func (u *ChannelMonitorHistoryUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for ChannelMonitorHistoryCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *ChannelMonitorHistoryUpsertOne) 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 *ChannelMonitorHistoryUpsertOne) 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 *ChannelMonitorHistoryUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// ChannelMonitorHistoryCreateBulk is the builder for creating many ChannelMonitorHistory entities in bulk.
type ChannelMonitorHistoryCreateBulk struct {
config
err error
builders []*ChannelMonitorHistoryCreate
conflict []sql.ConflictOption
}
// Save creates the ChannelMonitorHistory entities in the database.
func (_c *ChannelMonitorHistoryCreateBulk) Save(ctx context.Context) ([]*ChannelMonitorHistory, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*ChannelMonitorHistory, 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.(*ChannelMonitorHistoryMutation)
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 *ChannelMonitorHistoryCreateBulk) SaveX(ctx context.Context) []*ChannelMonitorHistory {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *ChannelMonitorHistoryCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *ChannelMonitorHistoryCreateBulk) 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.ChannelMonitorHistory.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.ChannelMonitorHistoryUpsert) {
// SetMonitorID(v+v).
// }).
// Exec(ctx)
func (_c *ChannelMonitorHistoryCreateBulk) OnConflict(opts ...sql.ConflictOption) *ChannelMonitorHistoryUpsertBulk {
_c.conflict = opts
return &ChannelMonitorHistoryUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *ChannelMonitorHistoryCreateBulk) OnConflictColumns(columns ...string) *ChannelMonitorHistoryUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &ChannelMonitorHistoryUpsertBulk{
create: _c,
}
}
// ChannelMonitorHistoryUpsertBulk is the builder for "upsert"-ing
// a bulk of ChannelMonitorHistory nodes.
type ChannelMonitorHistoryUpsertBulk struct {
create *ChannelMonitorHistoryCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *ChannelMonitorHistoryUpsertBulk) UpdateNewValues() *ChannelMonitorHistoryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.ChannelMonitorHistory.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *ChannelMonitorHistoryUpsertBulk) Ignore() *ChannelMonitorHistoryUpsertBulk {
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 *ChannelMonitorHistoryUpsertBulk) DoNothing() *ChannelMonitorHistoryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the ChannelMonitorHistoryCreateBulk.OnConflict
// documentation for more info.
func (u *ChannelMonitorHistoryUpsertBulk) Update(set func(*ChannelMonitorHistoryUpsert)) *ChannelMonitorHistoryUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&ChannelMonitorHistoryUpsert{UpdateSet: update})
}))
return u
}
// SetMonitorID sets the "monitor_id" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetMonitorID(v int64) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetMonitorID(v)
})
}
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateMonitorID() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateMonitorID()
})
}
// SetModel sets the "model" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetModel(v string) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetModel(v)
})
}
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateModel() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateModel()
})
}
// SetStatus sets the "status" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetStatus(v)
})
}
// UpdateStatus sets the "status" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateStatus() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateStatus()
})
}
// SetLatencyMs sets the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetLatencyMs(v)
})
}
// AddLatencyMs adds v to the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) AddLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.AddLatencyMs(v)
})
}
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateLatencyMs() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateLatencyMs()
})
}
// ClearLatencyMs clears the value of the "latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) ClearLatencyMs() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearLatencyMs()
})
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetPingLatencyMs(v)
})
}
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.AddPingLatencyMs(v)
})
}
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdatePingLatencyMs()
})
}
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
func (u *ChannelMonitorHistoryUpsertBulk) ClearPingLatencyMs() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearPingLatencyMs()
})
}
// SetMessage sets the "message" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetMessage(v string) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetMessage(v)
})
}
// UpdateMessage sets the "message" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateMessage() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateMessage()
})
}
// ClearMessage clears the value of the "message" field.
func (u *ChannelMonitorHistoryUpsertBulk) ClearMessage() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.ClearMessage()
})
}
// SetCheckedAt sets the "checked_at" field.
func (u *ChannelMonitorHistoryUpsertBulk) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.SetCheckedAt(v)
})
}
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
func (u *ChannelMonitorHistoryUpsertBulk) UpdateCheckedAt() *ChannelMonitorHistoryUpsertBulk {
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
s.UpdateCheckedAt()
})
}
// Exec executes the query.
func (u *ChannelMonitorHistoryUpsertBulk) 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 ChannelMonitorHistoryCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for ChannelMonitorHistoryCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *ChannelMonitorHistoryUpsertBulk) 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/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorHistoryDelete is the builder for deleting a ChannelMonitorHistory entity.
type ChannelMonitorHistoryDelete struct {
config
hooks []Hook
mutation *ChannelMonitorHistoryMutation
}
// Where appends a list predicates to the ChannelMonitorHistoryDelete builder.
func (_d *ChannelMonitorHistoryDelete) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *ChannelMonitorHistoryDelete) 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 *ChannelMonitorHistoryDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *ChannelMonitorHistoryDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(channelmonitorhistory.Table, sqlgraph.NewFieldSpec(channelmonitorhistory.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
}
// ChannelMonitorHistoryDeleteOne is the builder for deleting a single ChannelMonitorHistory entity.
type ChannelMonitorHistoryDeleteOne struct {
_d *ChannelMonitorHistoryDelete
}
// Where appends a list predicates to the ChannelMonitorHistoryDelete builder.
func (_d *ChannelMonitorHistoryDeleteOne) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *ChannelMonitorHistoryDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{channelmonitorhistory.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *ChannelMonitorHistoryDeleteOne) 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorHistoryQuery is the builder for querying ChannelMonitorHistory entities.
type ChannelMonitorHistoryQuery struct {
config
ctx *QueryContext
order []channelmonitorhistory.OrderOption
inters []Interceptor
predicates []predicate.ChannelMonitorHistory
withMonitor *ChannelMonitorQuery
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 ChannelMonitorHistoryQuery builder.
func (_q *ChannelMonitorHistoryQuery) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *ChannelMonitorHistoryQuery) Limit(limit int) *ChannelMonitorHistoryQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *ChannelMonitorHistoryQuery) Offset(offset int) *ChannelMonitorHistoryQuery {
_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 *ChannelMonitorHistoryQuery) Unique(unique bool) *ChannelMonitorHistoryQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *ChannelMonitorHistoryQuery) Order(o ...channelmonitorhistory.OrderOption) *ChannelMonitorHistoryQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryMonitor chains the current query on the "monitor" edge.
func (_q *ChannelMonitorHistoryQuery) QueryMonitor() *ChannelMonitorQuery {
query := (&ChannelMonitorClient{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(channelmonitorhistory.Table, channelmonitorhistory.FieldID, selector),
sqlgraph.To(channelmonitor.Table, channelmonitor.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, channelmonitorhistory.MonitorTable, channelmonitorhistory.MonitorColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first ChannelMonitorHistory entity from the query.
// Returns a *NotFoundError when no ChannelMonitorHistory was found.
func (_q *ChannelMonitorHistoryQuery) First(ctx context.Context) (*ChannelMonitorHistory, 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{channelmonitorhistory.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) FirstX(ctx context.Context) *ChannelMonitorHistory {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first ChannelMonitorHistory ID from the query.
// Returns a *NotFoundError when no ChannelMonitorHistory ID was found.
func (_q *ChannelMonitorHistoryQuery) 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{channelmonitorhistory.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single ChannelMonitorHistory entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one ChannelMonitorHistory entity is found.
// Returns a *NotFoundError when no ChannelMonitorHistory entities are found.
func (_q *ChannelMonitorHistoryQuery) Only(ctx context.Context) (*ChannelMonitorHistory, 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{channelmonitorhistory.Label}
default:
return nil, &NotSingularError{channelmonitorhistory.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) OnlyX(ctx context.Context) *ChannelMonitorHistory {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only ChannelMonitorHistory ID in the query.
// Returns a *NotSingularError when more than one ChannelMonitorHistory ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *ChannelMonitorHistoryQuery) 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{channelmonitorhistory.Label}
default:
err = &NotSingularError{channelmonitorhistory.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) 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 ChannelMonitorHistories.
func (_q *ChannelMonitorHistoryQuery) All(ctx context.Context) ([]*ChannelMonitorHistory, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*ChannelMonitorHistory, *ChannelMonitorHistoryQuery]()
return withInterceptors[[]*ChannelMonitorHistory](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) AllX(ctx context.Context) []*ChannelMonitorHistory {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of ChannelMonitorHistory IDs.
func (_q *ChannelMonitorHistoryQuery) 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(channelmonitorhistory.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) 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 *ChannelMonitorHistoryQuery) 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[*ChannelMonitorHistoryQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *ChannelMonitorHistoryQuery) 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 *ChannelMonitorHistoryQuery) 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 *ChannelMonitorHistoryQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the ChannelMonitorHistoryQuery 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 *ChannelMonitorHistoryQuery) Clone() *ChannelMonitorHistoryQuery {
if _q == nil {
return nil
}
return &ChannelMonitorHistoryQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]channelmonitorhistory.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.ChannelMonitorHistory{}, _q.predicates...),
withMonitor: _q.withMonitor.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithMonitor tells the query-builder to eager-load the nodes that are connected to
// the "monitor" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ChannelMonitorHistoryQuery) WithMonitor(opts ...func(*ChannelMonitorQuery)) *ChannelMonitorHistoryQuery {
query := (&ChannelMonitorClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withMonitor = 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 {
// MonitorID int64 `json:"monitor_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.ChannelMonitorHistory.Query().
// GroupBy(channelmonitorhistory.FieldMonitorID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *ChannelMonitorHistoryQuery) GroupBy(field string, fields ...string) *ChannelMonitorHistoryGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &ChannelMonitorHistoryGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = channelmonitorhistory.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 {
// MonitorID int64 `json:"monitor_id,omitempty"`
// }
//
// client.ChannelMonitorHistory.Query().
// Select(channelmonitorhistory.FieldMonitorID).
// Scan(ctx, &v)
func (_q *ChannelMonitorHistoryQuery) Select(fields ...string) *ChannelMonitorHistorySelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &ChannelMonitorHistorySelect{ChannelMonitorHistoryQuery: _q}
sbuild.label = channelmonitorhistory.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a ChannelMonitorHistorySelect configured with the given aggregations.
func (_q *ChannelMonitorHistoryQuery) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistorySelect {
return _q.Select().Aggregate(fns...)
}
func (_q *ChannelMonitorHistoryQuery) 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 !channelmonitorhistory.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 *ChannelMonitorHistoryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ChannelMonitorHistory, error) {
var (
nodes = []*ChannelMonitorHistory{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withMonitor != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*ChannelMonitorHistory).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &ChannelMonitorHistory{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.withMonitor; query != nil {
if err := _q.loadMonitor(ctx, query, nodes, nil,
func(n *ChannelMonitorHistory, e *ChannelMonitor) { n.Edges.Monitor = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *ChannelMonitorHistoryQuery) loadMonitor(ctx context.Context, query *ChannelMonitorQuery, nodes []*ChannelMonitorHistory, init func(*ChannelMonitorHistory), assign func(*ChannelMonitorHistory, *ChannelMonitor)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*ChannelMonitorHistory)
for i := range nodes {
fk := nodes[i].MonitorID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(channelmonitor.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 "monitor_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *ChannelMonitorHistoryQuery) 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 *ChannelMonitorHistoryQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.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, channelmonitorhistory.FieldID)
for i := range fields {
if fields[i] != channelmonitorhistory.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withMonitor != nil {
_spec.Node.AddColumnOnce(channelmonitorhistory.FieldMonitorID)
}
}
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 *ChannelMonitorHistoryQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(channelmonitorhistory.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = channelmonitorhistory.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 *ChannelMonitorHistoryQuery) ForUpdate(opts ...sql.LockOption) *ChannelMonitorHistoryQuery {
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 *ChannelMonitorHistoryQuery) ForShare(opts ...sql.LockOption) *ChannelMonitorHistoryQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// ChannelMonitorHistoryGroupBy is the group-by builder for ChannelMonitorHistory entities.
type ChannelMonitorHistoryGroupBy struct {
selector
build *ChannelMonitorHistoryQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *ChannelMonitorHistoryGroupBy) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistoryGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *ChannelMonitorHistoryGroupBy) 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[*ChannelMonitorHistoryQuery, *ChannelMonitorHistoryGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *ChannelMonitorHistoryGroupBy) sqlScan(ctx context.Context, root *ChannelMonitorHistoryQuery, 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)
}
// ChannelMonitorHistorySelect is the builder for selecting fields of ChannelMonitorHistory entities.
type ChannelMonitorHistorySelect struct {
*ChannelMonitorHistoryQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *ChannelMonitorHistorySelect) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistorySelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *ChannelMonitorHistorySelect) 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[*ChannelMonitorHistoryQuery, *ChannelMonitorHistorySelect](ctx, _s.ChannelMonitorHistoryQuery, _s, _s.inters, v)
}
func (_s *ChannelMonitorHistorySelect) sqlScan(ctx context.Context, root *ChannelMonitorHistoryQuery, 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,635 @@
// 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/channelmonitor"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ChannelMonitorHistoryUpdate is the builder for updating ChannelMonitorHistory entities.
type ChannelMonitorHistoryUpdate struct {
config
hooks []Hook
mutation *ChannelMonitorHistoryMutation
}
// Where appends a list predicates to the ChannelMonitorHistoryUpdate builder.
func (_u *ChannelMonitorHistoryUpdate) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetMonitorID sets the "monitor_id" field.
func (_u *ChannelMonitorHistoryUpdate) SetMonitorID(v int64) *ChannelMonitorHistoryUpdate {
_u.mutation.SetMonitorID(v)
return _u
}
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableMonitorID(v *int64) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetMonitorID(*v)
}
return _u
}
// SetModel sets the "model" field.
func (_u *ChannelMonitorHistoryUpdate) SetModel(v string) *ChannelMonitorHistoryUpdate {
_u.mutation.SetModel(v)
return _u
}
// SetNillableModel sets the "model" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableModel(v *string) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetModel(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *ChannelMonitorHistoryUpdate) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpdate {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableStatus(v *channelmonitorhistory.Status) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetStatus(*v)
}
return _u
}
// SetLatencyMs sets the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) SetLatencyMs(v int) *ChannelMonitorHistoryUpdate {
_u.mutation.ResetLatencyMs()
_u.mutation.SetLatencyMs(v)
return _u
}
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetLatencyMs(*v)
}
return _u
}
// AddLatencyMs adds value to the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) AddLatencyMs(v int) *ChannelMonitorHistoryUpdate {
_u.mutation.AddLatencyMs(v)
return _u
}
// ClearLatencyMs clears the value of the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) ClearLatencyMs() *ChannelMonitorHistoryUpdate {
_u.mutation.ClearLatencyMs()
return _u
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpdate {
_u.mutation.ResetPingLatencyMs()
_u.mutation.SetPingLatencyMs(v)
return _u
}
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetPingLatencyMs(*v)
}
return _u
}
// AddPingLatencyMs adds value to the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpdate {
_u.mutation.AddPingLatencyMs(v)
return _u
}
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdate) ClearPingLatencyMs() *ChannelMonitorHistoryUpdate {
_u.mutation.ClearPingLatencyMs()
return _u
}
// SetMessage sets the "message" field.
func (_u *ChannelMonitorHistoryUpdate) SetMessage(v string) *ChannelMonitorHistoryUpdate {
_u.mutation.SetMessage(v)
return _u
}
// SetNillableMessage sets the "message" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableMessage(v *string) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetMessage(*v)
}
return _u
}
// ClearMessage clears the value of the "message" field.
func (_u *ChannelMonitorHistoryUpdate) ClearMessage() *ChannelMonitorHistoryUpdate {
_u.mutation.ClearMessage()
return _u
}
// SetCheckedAt sets the "checked_at" field.
func (_u *ChannelMonitorHistoryUpdate) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpdate {
_u.mutation.SetCheckedAt(v)
return _u
}
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdate) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryUpdate {
if v != nil {
_u.SetCheckedAt(*v)
}
return _u
}
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorHistoryUpdate) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryUpdate {
return _u.SetMonitorID(v.ID)
}
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
func (_u *ChannelMonitorHistoryUpdate) Mutation() *ChannelMonitorHistoryMutation {
return _u.mutation
}
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorHistoryUpdate) ClearMonitor() *ChannelMonitorHistoryUpdate {
_u.mutation.ClearMonitor()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *ChannelMonitorHistoryUpdate) 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 *ChannelMonitorHistoryUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *ChannelMonitorHistoryUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *ChannelMonitorHistoryUpdate) 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 *ChannelMonitorHistoryUpdate) check() error {
if v, ok := _u.mutation.Model(); ok {
if err := channelmonitorhistory.ModelValidator(v); err != nil {
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := channelmonitorhistory.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
}
}
if v, ok := _u.mutation.Message(); ok {
if err := channelmonitorhistory.MessageValidator(v); err != nil {
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
}
}
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "ChannelMonitorHistory.monitor"`)
}
return nil
}
func (_u *ChannelMonitorHistoryUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.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.Model(); ok {
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
}
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
}
if value, ok := _u.mutation.LatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedLatencyMs(); ok {
_spec.AddField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
}
if _u.mutation.LatencyMsCleared() {
_spec.ClearField(channelmonitorhistory.FieldLatencyMs, field.TypeInt)
}
if value, ok := _u.mutation.PingLatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedPingLatencyMs(); ok {
_spec.AddField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
}
if _u.mutation.PingLatencyMsCleared() {
_spec.ClearField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt)
}
if value, ok := _u.mutation.Message(); ok {
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
}
if _u.mutation.MessageCleared() {
_spec.ClearField(channelmonitorhistory.FieldMessage, field.TypeString)
}
if value, ok := _u.mutation.CheckedAt(); ok {
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
}
if _u.mutation.MonitorCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitorhistory.MonitorTable,
Columns: []string{channelmonitorhistory.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitorhistory.MonitorTable,
Columns: []string{channelmonitorhistory.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{channelmonitorhistory.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// ChannelMonitorHistoryUpdateOne is the builder for updating a single ChannelMonitorHistory entity.
type ChannelMonitorHistoryUpdateOne struct {
config
fields []string
hooks []Hook
mutation *ChannelMonitorHistoryMutation
}
// SetMonitorID sets the "monitor_id" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetMonitorID(v int64) *ChannelMonitorHistoryUpdateOne {
_u.mutation.SetMonitorID(v)
return _u
}
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableMonitorID(v *int64) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetMonitorID(*v)
}
return _u
}
// SetModel sets the "model" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetModel(v string) *ChannelMonitorHistoryUpdateOne {
_u.mutation.SetModel(v)
return _u
}
// SetNillableModel sets the "model" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableModel(v *string) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetModel(*v)
}
return _u
}
// SetStatus sets the "status" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpdateOne {
_u.mutation.SetStatus(v)
return _u
}
// SetNillableStatus sets the "status" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableStatus(v *channelmonitorhistory.Status) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetStatus(*v)
}
return _u
}
// SetLatencyMs sets the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
_u.mutation.ResetLatencyMs()
_u.mutation.SetLatencyMs(v)
return _u
}
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetLatencyMs(*v)
}
return _u
}
// AddLatencyMs adds value to the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) AddLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
_u.mutation.AddLatencyMs(v)
return _u
}
// ClearLatencyMs clears the value of the "latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) ClearLatencyMs() *ChannelMonitorHistoryUpdateOne {
_u.mutation.ClearLatencyMs()
return _u
}
// SetPingLatencyMs sets the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
_u.mutation.ResetPingLatencyMs()
_u.mutation.SetPingLatencyMs(v)
return _u
}
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetPingLatencyMs(*v)
}
return _u
}
// AddPingLatencyMs adds value to the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
_u.mutation.AddPingLatencyMs(v)
return _u
}
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
func (_u *ChannelMonitorHistoryUpdateOne) ClearPingLatencyMs() *ChannelMonitorHistoryUpdateOne {
_u.mutation.ClearPingLatencyMs()
return _u
}
// SetMessage sets the "message" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetMessage(v string) *ChannelMonitorHistoryUpdateOne {
_u.mutation.SetMessage(v)
return _u
}
// SetNillableMessage sets the "message" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableMessage(v *string) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetMessage(*v)
}
return _u
}
// ClearMessage clears the value of the "message" field.
func (_u *ChannelMonitorHistoryUpdateOne) ClearMessage() *ChannelMonitorHistoryUpdateOne {
_u.mutation.ClearMessage()
return _u
}
// SetCheckedAt sets the "checked_at" field.
func (_u *ChannelMonitorHistoryUpdateOne) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpdateOne {
_u.mutation.SetCheckedAt(v)
return _u
}
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryUpdateOne {
if v != nil {
_u.SetCheckedAt(*v)
}
return _u
}
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorHistoryUpdateOne) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryUpdateOne {
return _u.SetMonitorID(v.ID)
}
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
func (_u *ChannelMonitorHistoryUpdateOne) Mutation() *ChannelMonitorHistoryMutation {
return _u.mutation
}
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
func (_u *ChannelMonitorHistoryUpdateOne) ClearMonitor() *ChannelMonitorHistoryUpdateOne {
_u.mutation.ClearMonitor()
return _u
}
// Where appends a list predicates to the ChannelMonitorHistoryUpdate builder.
func (_u *ChannelMonitorHistoryUpdateOne) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryUpdateOne {
_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 *ChannelMonitorHistoryUpdateOne) Select(field string, fields ...string) *ChannelMonitorHistoryUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated ChannelMonitorHistory entity.
func (_u *ChannelMonitorHistoryUpdateOne) Save(ctx context.Context) (*ChannelMonitorHistory, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *ChannelMonitorHistoryUpdateOne) SaveX(ctx context.Context) *ChannelMonitorHistory {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *ChannelMonitorHistoryUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *ChannelMonitorHistoryUpdateOne) 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 *ChannelMonitorHistoryUpdateOne) check() error {
if v, ok := _u.mutation.Model(); ok {
if err := channelmonitorhistory.ModelValidator(v); err != nil {
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
}
}
if v, ok := _u.mutation.Status(); ok {
if err := channelmonitorhistory.StatusValidator(v); err != nil {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
}
}
if v, ok := _u.mutation.Message(); ok {
if err := channelmonitorhistory.MessageValidator(v); err != nil {
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
}
}
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "ChannelMonitorHistory.monitor"`)
}
return nil
}
func (_u *ChannelMonitorHistoryUpdateOne) sqlSave(ctx context.Context) (_node *ChannelMonitorHistory, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ChannelMonitorHistory.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, channelmonitorhistory.FieldID)
for _, f := range fields {
if !channelmonitorhistory.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != channelmonitorhistory.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.Model(); ok {
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
}
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
}
if value, ok := _u.mutation.LatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedLatencyMs(); ok {
_spec.AddField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
}
if _u.mutation.LatencyMsCleared() {
_spec.ClearField(channelmonitorhistory.FieldLatencyMs, field.TypeInt)
}
if value, ok := _u.mutation.PingLatencyMs(); ok {
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedPingLatencyMs(); ok {
_spec.AddField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
}
if _u.mutation.PingLatencyMsCleared() {
_spec.ClearField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt)
}
if value, ok := _u.mutation.Message(); ok {
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
}
if _u.mutation.MessageCleared() {
_spec.ClearField(channelmonitorhistory.FieldMessage, field.TypeString)
}
if value, ok := _u.mutation.CheckedAt(); ok {
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
}
if _u.mutation.MonitorCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitorhistory.MonitorTable,
Columns: []string{channelmonitorhistory.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: true,
Table: channelmonitorhistory.MonitorTable,
Columns: []string{channelmonitorhistory.MonitorColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &ChannelMonitorHistory{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{channelmonitorhistory.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

@@ -0,0 +1,216 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
)
// ChannelMonitorRequestTemplate is the model entity for the ChannelMonitorRequestTemplate schema.
type ChannelMonitorRequestTemplate 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"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Provider holds the value of the "provider" field.
Provider channelmonitorrequesttemplate.Provider `json:"provider,omitempty"`
// Description holds the value of the "description" field.
Description string `json:"description,omitempty"`
// ExtraHeaders holds the value of the "extra_headers" field.
ExtraHeaders map[string]string `json:"extra_headers,omitempty"`
// BodyOverrideMode holds the value of the "body_override_mode" field.
BodyOverrideMode string `json:"body_override_mode,omitempty"`
// BodyOverride holds the value of the "body_override" field.
BodyOverride map[string]interface{} `json:"body_override,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ChannelMonitorRequestTemplateQuery when eager-loading is set.
Edges ChannelMonitorRequestTemplateEdges `json:"edges"`
selectValues sql.SelectValues
}
// ChannelMonitorRequestTemplateEdges holds the relations/edges for other nodes in the graph.
type ChannelMonitorRequestTemplateEdges struct {
// Monitors holds the value of the monitors edge.
Monitors []*ChannelMonitor `json:"monitors,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// MonitorsOrErr returns the Monitors value or an error if the edge
// was not loaded in eager-loading.
func (e ChannelMonitorRequestTemplateEdges) MonitorsOrErr() ([]*ChannelMonitor, error) {
if e.loadedTypes[0] {
return e.Monitors, nil
}
return nil, &NotLoadedError{edge: "monitors"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*ChannelMonitorRequestTemplate) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case channelmonitorrequesttemplate.FieldExtraHeaders, channelmonitorrequesttemplate.FieldBodyOverride:
values[i] = new([]byte)
case channelmonitorrequesttemplate.FieldID:
values[i] = new(sql.NullInt64)
case channelmonitorrequesttemplate.FieldName, channelmonitorrequesttemplate.FieldProvider, channelmonitorrequesttemplate.FieldDescription, channelmonitorrequesttemplate.FieldBodyOverrideMode:
values[i] = new(sql.NullString)
case channelmonitorrequesttemplate.FieldCreatedAt, channelmonitorrequesttemplate.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 ChannelMonitorRequestTemplate fields.
func (_m *ChannelMonitorRequestTemplate) 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 channelmonitorrequesttemplate.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 channelmonitorrequesttemplate.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 channelmonitorrequesttemplate.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 channelmonitorrequesttemplate.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 channelmonitorrequesttemplate.FieldProvider:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider", values[i])
} else if value.Valid {
_m.Provider = channelmonitorrequesttemplate.Provider(value.String)
}
case channelmonitorrequesttemplate.FieldDescription:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field description", values[i])
} else if value.Valid {
_m.Description = value.String
}
case channelmonitorrequesttemplate.FieldExtraHeaders:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field extra_headers", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.ExtraHeaders); err != nil {
return fmt.Errorf("unmarshal field extra_headers: %w", err)
}
}
case channelmonitorrequesttemplate.FieldBodyOverrideMode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field body_override_mode", values[i])
} else if value.Valid {
_m.BodyOverrideMode = value.String
}
case channelmonitorrequesttemplate.FieldBodyOverride:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field body_override", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &_m.BodyOverride); err != nil {
return fmt.Errorf("unmarshal field body_override: %w", err)
}
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the ChannelMonitorRequestTemplate.
// This includes values selected through modifiers, order, etc.
func (_m *ChannelMonitorRequestTemplate) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryMonitors queries the "monitors" edge of the ChannelMonitorRequestTemplate entity.
func (_m *ChannelMonitorRequestTemplate) QueryMonitors() *ChannelMonitorQuery {
return NewChannelMonitorRequestTemplateClient(_m.config).QueryMonitors(_m)
}
// Update returns a builder for updating this ChannelMonitorRequestTemplate.
// Note that you need to call ChannelMonitorRequestTemplate.Unwrap() before calling this method if this ChannelMonitorRequestTemplate
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *ChannelMonitorRequestTemplate) Update() *ChannelMonitorRequestTemplateUpdateOne {
return NewChannelMonitorRequestTemplateClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the ChannelMonitorRequestTemplate 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 *ChannelMonitorRequestTemplate) Unwrap() *ChannelMonitorRequestTemplate {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: ChannelMonitorRequestTemplate is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *ChannelMonitorRequestTemplate) String() string {
var builder strings.Builder
builder.WriteString("ChannelMonitorRequestTemplate(")
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("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("provider=")
builder.WriteString(fmt.Sprintf("%v", _m.Provider))
builder.WriteString(", ")
builder.WriteString("description=")
builder.WriteString(_m.Description)
builder.WriteString(", ")
builder.WriteString("extra_headers=")
builder.WriteString(fmt.Sprintf("%v", _m.ExtraHeaders))
builder.WriteString(", ")
builder.WriteString("body_override_mode=")
builder.WriteString(_m.BodyOverrideMode)
builder.WriteString(", ")
builder.WriteString("body_override=")
builder.WriteString(fmt.Sprintf("%v", _m.BodyOverride))
builder.WriteByte(')')
return builder.String()
}
// ChannelMonitorRequestTemplates is a parsable slice of ChannelMonitorRequestTemplate.
type ChannelMonitorRequestTemplates []*ChannelMonitorRequestTemplate

View File

@@ -0,0 +1,172 @@
// Code generated by ent, DO NOT EDIT.
package channelmonitorrequesttemplate
import (
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the channelmonitorrequesttemplate type in the database.
Label = "channel_monitor_request_template"
// 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"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldProvider holds the string denoting the provider field in the database.
FieldProvider = "provider"
// FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description"
// FieldExtraHeaders holds the string denoting the extra_headers field in the database.
FieldExtraHeaders = "extra_headers"
// FieldBodyOverrideMode holds the string denoting the body_override_mode field in the database.
FieldBodyOverrideMode = "body_override_mode"
// FieldBodyOverride holds the string denoting the body_override field in the database.
FieldBodyOverride = "body_override"
// EdgeMonitors holds the string denoting the monitors edge name in mutations.
EdgeMonitors = "monitors"
// Table holds the table name of the channelmonitorrequesttemplate in the database.
Table = "channel_monitor_request_templates"
// MonitorsTable is the table that holds the monitors relation/edge.
MonitorsTable = "channel_monitors"
// MonitorsInverseTable is the table name for the ChannelMonitor entity.
// It exists in this package in order to avoid circular dependency with the "channelmonitor" package.
MonitorsInverseTable = "channel_monitors"
// MonitorsColumn is the table column denoting the monitors relation/edge.
MonitorsColumn = "template_id"
)
// Columns holds all SQL columns for channelmonitorrequesttemplate fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldName,
FieldProvider,
FieldDescription,
FieldExtraHeaders,
FieldBodyOverrideMode,
FieldBodyOverride,
}
// 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
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultDescription holds the default value on creation for the "description" field.
DefaultDescription string
// DescriptionValidator is a validator for the "description" field. It is called by the builders before save.
DescriptionValidator func(string) error
// DefaultExtraHeaders holds the default value on creation for the "extra_headers" field.
DefaultExtraHeaders map[string]string
// DefaultBodyOverrideMode holds the default value on creation for the "body_override_mode" field.
DefaultBodyOverrideMode string
// BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
BodyOverrideModeValidator func(string) error
)
// Provider defines the type for the "provider" enum field.
type Provider string
// Provider values.
const (
ProviderOpenai Provider = "openai"
ProviderAnthropic Provider = "anthropic"
ProviderGemini Provider = "gemini"
)
func (pr Provider) String() string {
return string(pr)
}
// ProviderValidator is a validator for the "provider" field enum values. It is called by the builders before save.
func ProviderValidator(pr Provider) error {
switch pr {
case ProviderOpenai, ProviderAnthropic, ProviderGemini:
return nil
default:
return fmt.Errorf("channelmonitorrequesttemplate: invalid enum value for provider field: %q", pr)
}
}
// OrderOption defines the ordering options for the ChannelMonitorRequestTemplate 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()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByProvider orders the results by the provider field.
func ByProvider(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProvider, opts...).ToFunc()
}
// ByDescription orders the results by the description field.
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDescription, opts...).ToFunc()
}
// ByBodyOverrideMode orders the results by the body_override_mode field.
func ByBodyOverrideMode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldBodyOverrideMode, opts...).ToFunc()
}
// ByMonitorsCount orders the results by monitors count.
func ByMonitorsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newMonitorsStep(), opts...)
}
}
// ByMonitors orders the results by monitors terms.
func ByMonitors(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newMonitorsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newMonitorsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(MonitorsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, true, MonitorsTable, MonitorsColumn),
)
}

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