mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-04 07:22:13 +08:00
Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cce0a8877 | ||
|
|
225fd035ae | ||
|
|
fb7d1346b5 | ||
|
|
491a744481 | ||
|
|
f366026435 | ||
|
|
1a0d4ed668 | ||
|
|
63a8c76946 | ||
|
|
f355a68bc9 | ||
|
|
c87e6526c1 | ||
|
|
af3a5076d6 | ||
|
|
18f2e21414 | ||
|
|
8a8cdeebb4 | ||
|
|
12b33f4ea4 | ||
|
|
01b3a09d7d | ||
|
|
0d6c1c7790 | ||
|
|
95e366b6c6 | ||
|
|
77701143bf | ||
|
|
02dea7b09b | ||
|
|
c26f93c4a0 | ||
|
|
c826ac28ef | ||
|
|
1893b0eb30 | ||
|
|
05527b13db | ||
|
|
ae5d9c8bfc | ||
|
|
9117c2a4ec | ||
|
|
bab4bb9904 | ||
|
|
33bae6f49b | ||
|
|
32d619a56b | ||
|
|
642432cf2a | ||
|
|
61e9598b08 | ||
|
|
d4e34c7514 | ||
|
|
bfe7a5e452 | ||
|
|
77d916ffec | ||
|
|
831abf7977 | ||
|
|
817a491087 | ||
|
|
9a8dacc514 | ||
|
|
8adf80d98b | ||
|
|
62686a6213 | ||
|
|
3a089242f8 | ||
|
|
9d70c38504 | ||
|
|
aeb464f3ca | ||
|
|
7076717b20 | ||
|
|
c0a4fcea0a | ||
|
|
aa2b195c86 | ||
|
|
1d0872e7ca | ||
|
|
33988637b5 | ||
|
|
d4f6ad7225 | ||
|
|
078fefed03 | ||
|
|
5b10af85b4 | ||
|
|
4caf95e5dd | ||
|
|
8e1bcf53bb | ||
|
|
064f9be7e4 | ||
|
|
adcfb44cb7 | ||
|
|
3d79773ba2 | ||
|
|
6aa8cbbf20 | ||
|
|
742e73c9c2 | ||
|
|
f8de2bdedc | ||
|
|
59879b7fa7 | ||
|
|
27abae21b8 | ||
|
|
0819c8a51a | ||
|
|
9dcd3cd491 | ||
|
|
49767cccd2 | ||
|
|
29fb447daa | ||
|
|
f6fe5b552d | ||
|
|
bd0801a887 | ||
|
|
05b1c66aa8 | ||
|
|
80ae592c23 | ||
|
|
ba6de4c4d4 | ||
|
|
46ea9170cb | ||
|
|
7d318aeefa | ||
|
|
0aa3cf677a | ||
|
|
72961c5858 | ||
|
|
a05711a37a | ||
|
|
efc9e1d673 | ||
|
|
a11ac188c2 | ||
|
|
60350d298a | ||
|
|
838dad8759 | ||
|
|
a728dfe0c6 | ||
|
|
0c7cbe3566 | ||
|
|
832b0185c7 | ||
|
|
b1719b26d1 | ||
|
|
ccf6a921c7 | ||
|
|
197c570baa | ||
|
|
0fe09f1d40 | ||
|
|
4a91954532 | ||
|
|
b8b5cec35c | ||
|
|
43c203333e | ||
|
|
1c6393b131 | ||
|
|
22f04e72e5 | ||
|
|
5f3debf65b | ||
|
|
fd8ef27535 | ||
|
|
a80ec5d8bb | ||
|
|
530a16291c | ||
|
|
7be8f4dc6e | ||
|
|
9792b17597 | ||
|
|
99f1e3ff35 | ||
|
|
5ba71cd2f1 | ||
|
|
b7df7ce5d5 | ||
|
|
405829dc30 | ||
|
|
451a851118 | ||
|
|
e97c376681 | ||
|
|
7541e243bc | ||
|
|
50a8116ae9 | ||
|
|
bf6fe5e962 | ||
|
|
e4f8799323 | ||
|
|
1f95524996 | ||
|
|
a50d5d351b | ||
|
|
067810fa98 | ||
|
|
a9285b8a94 | ||
|
|
ec6bcfeb83 | ||
|
|
7abec1888f | ||
|
|
fdcbf7aacf | ||
|
|
445bfdf242 | ||
|
|
0fba1901c8 | ||
|
|
fc5b9c8235 | ||
|
|
f490f44501 | ||
|
|
7e02082209 | ||
|
|
d869ac95fa | ||
|
|
5c856460a6 | ||
|
|
3613695f91 | ||
|
|
8fb7d476b8 | ||
|
|
dd8df483cd | ||
|
|
65459a99b6 | ||
|
|
2129584fd6 | ||
|
|
2da9c216c3 | ||
|
|
c6e26c5a16 | ||
|
|
fd57fa4913 | ||
|
|
8c4d22b3f9 | ||
|
|
c221774c51 | ||
|
|
23686b1391 | ||
|
|
0fffba5423 | ||
|
|
0e0eb747b5 | ||
|
|
f6f8695a8e | ||
|
|
b2141a96e2 | ||
|
|
4280aca82c | ||
|
|
c08889b021 | ||
|
|
57ebe382f9 | ||
|
|
73089bbfdf | ||
|
|
3a04552f98 | ||
|
|
b67bf2227e | ||
|
|
dde3b59e7b | ||
|
|
947800b95f | ||
|
|
7aa4c083a9 | ||
|
|
fcc77d1383 | ||
|
|
997cd1e332 | ||
|
|
2e88e23002 | ||
|
|
39ca192c41 | ||
|
|
f7fa71bc28 | ||
|
|
fbfbb26fd2 | ||
|
|
493bd188d5 | ||
|
|
9fd95df5cf | ||
|
|
54de3bf27a | ||
|
|
4587c3e53e | ||
|
|
be18bc6fc3 | ||
|
|
212cbbd3a2 | ||
|
|
6f9e690345 | ||
|
|
115d06edf0 | ||
|
|
e135435ce2 | ||
|
|
cd09adc3cc | ||
|
|
2491e9b5ad | ||
|
|
e63c83955a | ||
|
|
4b72aa33f3 | ||
|
|
ff9683b0fc | ||
|
|
607237571f | ||
|
|
28ca7df297 | ||
|
|
856c955386 | ||
|
|
e1c9016d90 | ||
|
|
953c5036bf | ||
|
|
37fa980565 | ||
|
|
f648b8e026 | ||
|
|
678c3ae132 | ||
|
|
c1c31ed9b2 | ||
|
|
777be05348 | ||
|
|
0bb3e4a98c | ||
|
|
9a91815b94 | ||
|
|
000e621eb6 | ||
|
|
093d7ba858 | ||
|
|
ce006a7a91 | ||
|
|
9d795061af | ||
|
|
1d1fc019dc | ||
|
|
bb664d9bbf | ||
|
|
bfc7b339f7 | ||
|
|
f30f8905ec |
10
.github/workflows/backend-ci.yml
vendored
10
.github/workflows/backend-ci.yml
vendored
@@ -11,8 +11,8 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: backend/go.mod
|
||||
check-latest: false
|
||||
@@ -30,8 +30,8 @@ jobs:
|
||||
golangci-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: backend/go.mod
|
||||
check-latest: false
|
||||
@@ -43,5 +43,5 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.7
|
||||
args: --timeout=5m
|
||||
args: --timeout=30m
|
||||
working-directory: backend
|
||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Update VERSION file
|
||||
run: |
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
echo "Updated VERSION file to: $VERSION"
|
||||
|
||||
- name: Upload VERSION artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: version-file
|
||||
path: backend/cmd/server/VERSION
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
|
||||
- name: Upload frontend artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: backend/internal/web/dist/
|
||||
@@ -89,25 +89,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
- name: Download VERSION artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: version-file
|
||||
path: backend/cmd/server/
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: backend/internal/web/dist/
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: backend/go.mod
|
||||
check-latest: false
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
version: '~> v2'
|
||||
args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }}
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
# Update DockerHub description
|
||||
- name: Update DockerHub description
|
||||
if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }}
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
with:
|
||||
|
||||
14
.github/workflows/security-scan.yml
vendored
14
.github/workflows/security-scan.yml
vendored
@@ -12,10 +12,11 @@ permissions:
|
||||
jobs:
|
||||
backend-security:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: backend/go.mod
|
||||
check-latest: false
|
||||
@@ -28,22 +29,17 @@ jobs:
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
- name: Run gosec
|
||||
working-directory: backend
|
||||
run: |
|
||||
go install github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
gosec -conf .gosec.json -severity high -confidence high ./...
|
||||
|
||||
frontend-security:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -116,13 +116,12 @@ backend/.installed
|
||||
# ===================
|
||||
tests
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
.claude
|
||||
scripts
|
||||
.code-review-state
|
||||
openspec/
|
||||
#openspec/
|
||||
code-reviews/
|
||||
AGENTS.md
|
||||
#AGENTS.md
|
||||
backend/cmd/server/server
|
||||
deploy/docker-compose.override.yml
|
||||
.gocache/
|
||||
@@ -132,4 +131,5 @@ docs/*
|
||||
.codex/
|
||||
frontend/coverage/
|
||||
aicodex
|
||||
output/
|
||||
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -8,7 +8,7 @@
|
||||
|
||||
ARG NODE_IMAGE=node:24-alpine
|
||||
ARG GOLANG_IMAGE=golang:1.25.7-alpine
|
||||
ARG ALPINE_IMAGE=alpine:3.20
|
||||
ARG ALPINE_IMAGE=alpine:3.21
|
||||
ARG GOPROXY=https://goproxy.cn,direct
|
||||
ARG GOSUMDB=sum.golang.google.cn
|
||||
|
||||
@@ -68,6 +68,7 @@ RUN VERSION_VALUE="${VERSION}" && \
|
||||
CGO_ENABLED=0 GOOS=linux go build \
|
||||
-tags embed \
|
||||
-ldflags="-s -w -X main.Version=${VERSION_VALUE} -X main.Commit=${COMMIT} -X main.Date=${DATE_VALUE} -X main.BuildType=release" \
|
||||
-trimpath \
|
||||
-o /app/sub2api \
|
||||
./cmd/server
|
||||
|
||||
@@ -85,7 +86,6 @@ LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
curl \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Create non-root user
|
||||
@@ -95,11 +95,12 @@ RUN addgroup -g 1000 sub2api && \
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=backend-builder /app/sub2api /app/sub2api
|
||||
# Copy binary/resources with ownership to avoid extra full-layer chown copy
|
||||
COPY --from=backend-builder --chown=sub2api:sub2api /app/sub2api /app/sub2api
|
||||
COPY --from=backend-builder --chown=sub2api:sub2api /app/backend/resources /app/resources
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
|
||||
RUN mkdir -p /app/data && chown sub2api:sub2api /app/data
|
||||
|
||||
# Switch to non-root user
|
||||
USER sub2api
|
||||
@@ -109,7 +110,7 @@ EXPOSE 8080
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
||||
CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["/app/sub2api"]
|
||||
|
||||
9
Makefile
9
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: build build-backend build-frontend test test-backend test-frontend secret-scan
|
||||
.PHONY: build build-backend build-frontend build-datamanagementd test test-backend test-frontend test-datamanagementd secret-scan
|
||||
|
||||
# 一键编译前后端
|
||||
build: build-backend build-frontend
|
||||
@@ -11,6 +11,10 @@ build-backend:
|
||||
build-frontend:
|
||||
@pnpm --dir frontend run build
|
||||
|
||||
# 编译 datamanagementd(宿主机数据管理进程)
|
||||
build-datamanagementd:
|
||||
@cd datamanagement && go build -o datamanagementd ./cmd/datamanagementd
|
||||
|
||||
# 运行测试(后端 + 前端)
|
||||
test: test-backend test-frontend
|
||||
|
||||
@@ -21,5 +25,8 @@ test-frontend:
|
||||
@pnpm --dir frontend run lint:check
|
||||
@pnpm --dir frontend run typecheck
|
||||
|
||||
test-datamanagementd:
|
||||
@cd datamanagement && go test ./...
|
||||
|
||||
secret-scan:
|
||||
@python3 tools/secret_scan.py
|
||||
|
||||
@@ -54,6 +54,7 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
|
||||
## Documentation
|
||||
|
||||
- Dependency Security: `docs/dependency-security.md`
|
||||
- Admin Payment Integration API: `docs/ADMIN_PAYMENT_INTEGRATION_API.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
16
README_CN.md
16
README_CN.md
@@ -62,8 +62,6 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
|
||||
- 当请求包含 `function_call_output` 时,需要携带 `previous_response_id`,或在 `input` 中包含带 `call_id` 的 `tool_call`/`function_call`,或带非空 `id` 且与 `function_call_output.call_id` 匹配的 `item_reference`。
|
||||
- 若依赖上游历史记录,网关会强制 `store=true` 并需要复用 `previous_response_id`,以避免出现 “No tool call found for function call output” 错误。
|
||||
|
||||
---
|
||||
|
||||
## 部署方式
|
||||
|
||||
### 方式一:脚本安装(推荐)
|
||||
@@ -139,8 +137,6 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
|
||||
|
||||
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
|
||||
|
||||
如果你的服务器是 **Ubuntu 24.04**,建议直接参考:`deploy/ubuntu24-docker-compose-aicodex.md`,其中包含「安装最新版 Docker + docker-compose-aicodex.yml 部署」的完整步骤。
|
||||
|
||||
#### 前置条件
|
||||
|
||||
- Docker 20.10+
|
||||
@@ -246,6 +242,18 @@ docker-compose -f docker-compose.local.yml logs -f sub2api
|
||||
|
||||
**推荐:** 使用 `docker-compose.local.yml`(脚本部署)以便更轻松地管理数据。
|
||||
|
||||
#### 启用“数据管理”功能(datamanagementd)
|
||||
|
||||
如需启用管理后台“数据管理”,需要额外部署宿主机数据管理进程 `datamanagementd`。
|
||||
|
||||
关键点:
|
||||
|
||||
- 主进程固定探测:`/tmp/sub2api-datamanagement.sock`
|
||||
- 只有该 Socket 可连通时,数据管理功能才会开启
|
||||
- Docker 场景需将宿主机 Socket 挂载到容器同路径
|
||||
|
||||
详细部署步骤见:`deploy/DATAMANAGEMENTD_CN.md`
|
||||
|
||||
#### 访问
|
||||
|
||||
在浏览器中打开 `http://你的服务器IP:8080`
|
||||
|
||||
@@ -5,6 +5,7 @@ linters:
|
||||
enable:
|
||||
- depguard
|
||||
- errcheck
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
@@ -42,6 +43,22 @@ linters:
|
||||
desc: "handler must not import gorm"
|
||||
- pkg: github.com/redis/go-redis/v9
|
||||
desc: "handler must not import redis"
|
||||
gosec:
|
||||
excludes:
|
||||
- G101
|
||||
- G103
|
||||
- G104
|
||||
- G109
|
||||
- G115
|
||||
- G201
|
||||
- G202
|
||||
- G301
|
||||
- G302
|
||||
- G304
|
||||
- G306
|
||||
- G404
|
||||
severity: high
|
||||
confidence: high
|
||||
errcheck:
|
||||
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
|
||||
# Such cases aren't reported by default.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"global": {
|
||||
"exclude": "G704"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
.PHONY: build test test-unit test-integration test-e2e
|
||||
.PHONY: build generate test test-unit test-integration test-e2e
|
||||
|
||||
VERSION ?= $(shell tr -d '\r\n' < ./cmd/server/VERSION)
|
||||
LDFLAGS ?= -s -w -X main.Version=$(VERSION)
|
||||
|
||||
build:
|
||||
go build -o bin/server ./cmd/server
|
||||
CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -trimpath -o bin/server ./cmd/server
|
||||
|
||||
generate:
|
||||
go generate ./ent
|
||||
go generate ./cmd/server
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
}()
|
||||
|
||||
userRepo := repository.NewUserRepository(client, sqlDB)
|
||||
authService := service.NewAuthService(userRepo, nil, nil, cfg, nil, nil, nil, nil, nil)
|
||||
authService := service.NewAuthService(userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.1.85
|
||||
0.1.88
|
||||
@@ -100,7 +100,7 @@ func runSetupServer() {
|
||||
r := gin.New()
|
||||
r.Use(middleware.Recovery())
|
||||
r.Use(middleware.CORS(config.CORSConfig{}))
|
||||
r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}))
|
||||
r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}, nil))
|
||||
|
||||
// Register setup routes
|
||||
setup.RegisterRoutes(r)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/ent"
|
||||
@@ -84,16 +85,20 @@ func provideCleanup(
|
||||
openaiOAuth *service.OpenAIOAuthService,
|
||||
geminiOAuth *service.GeminiOAuthService,
|
||||
antigravityOAuth *service.AntigravityOAuthService,
|
||||
openAIGateway *service.OpenAIGatewayService,
|
||||
scheduledTestRunner *service.ScheduledTestRunnerService,
|
||||
) func() {
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Cleanup steps in reverse dependency order
|
||||
cleanupSteps := []struct {
|
||||
type cleanupStep struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
}
|
||||
|
||||
// 应用层清理步骤可并行执行,基础设施资源(Redis/Ent)最后按顺序关闭。
|
||||
parallelSteps := []cleanupStep{
|
||||
{"OpsScheduledReportService", func() error {
|
||||
if opsScheduledReport != nil {
|
||||
opsScheduledReport.Stop()
|
||||
@@ -206,23 +211,66 @@ func provideCleanup(
|
||||
antigravityOAuth.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"OpenAIWSPool", func() error {
|
||||
if openAIGateway != nil {
|
||||
openAIGateway.CloseOpenAIWSPool()
|
||||
}
|
||||
return nil
|
||||
}},
|
||||
{"ScheduledTestRunnerService", func() error {
|
||||
if scheduledTestRunner != nil {
|
||||
scheduledTestRunner.Stop()
|
||||
}
|
||||
return nil
|
||||
}},
|
||||
}
|
||||
|
||||
infraSteps := []cleanupStep{
|
||||
{"Redis", func() error {
|
||||
if rdb == nil {
|
||||
return nil
|
||||
}
|
||||
return rdb.Close()
|
||||
}},
|
||||
{"Ent", func() error {
|
||||
if entClient == nil {
|
||||
return nil
|
||||
}
|
||||
return entClient.Close()
|
||||
}},
|
||||
}
|
||||
|
||||
for _, step := range cleanupSteps {
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
// Continue with remaining cleanup steps even if one fails
|
||||
} else {
|
||||
runParallel := func(steps []cleanupStep) {
|
||||
var wg sync.WaitGroup
|
||||
for i := range steps {
|
||||
step := steps[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
return
|
||||
}
|
||||
log.Printf("[Cleanup] %s succeeded", step.name)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
runSequential := func(steps []cleanupStep) {
|
||||
for i := range steps {
|
||||
step := steps[i]
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[Cleanup] %s succeeded", step.name)
|
||||
}
|
||||
}
|
||||
|
||||
runParallel(parallelSteps)
|
||||
runSequential(infraSteps)
|
||||
|
||||
// Check if context timed out
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -47,7 +48,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
redisClient := repository.ProvideRedis(configConfig)
|
||||
refreshTokenCache := repository.NewRefreshTokenCache(redisClient)
|
||||
settingRepository := repository.NewSettingRepository(client)
|
||||
settingService := service.NewSettingService(settingRepository, configConfig)
|
||||
groupRepository := repository.NewGroupRepository(client, db)
|
||||
settingService := service.ProvideSettingService(settingRepository, groupRepository, configConfig)
|
||||
emailCache := repository.NewEmailCache(redisClient)
|
||||
emailService := service.NewEmailService(settingRepository, emailCache)
|
||||
turnstileVerifier := repository.NewTurnstileVerifier()
|
||||
@@ -56,17 +58,17 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
promoCodeRepository := repository.NewPromoCodeRepository(client)
|
||||
billingCache := repository.NewBillingCache(redisClient)
|
||||
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
|
||||
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
|
||||
apiKeyRepository := repository.NewAPIKeyRepository(client)
|
||||
groupRepository := repository.NewGroupRepository(client, db)
|
||||
apiKeyRepository := repository.NewAPIKeyRepository(client, db)
|
||||
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, configConfig)
|
||||
userGroupRateRepository := repository.NewUserGroupRateRepository(db)
|
||||
apiKeyCache := repository.NewAPIKeyCache(redisClient)
|
||||
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig)
|
||||
apiKeyService.SetRateLimitCacheInvalidator(billingCache)
|
||||
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
|
||||
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||
authService := service.NewAuthService(userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService)
|
||||
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache)
|
||||
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
|
||||
authService := service.NewAuthService(userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService, subscriptionService)
|
||||
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache)
|
||||
redeemCache := repository.NewRedeemCache(redisClient)
|
||||
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||
secretEncryptor, err := repository.NewAESEncryptor(configConfig)
|
||||
@@ -102,7 +104,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
proxyRepository := repository.NewProxyRepository(client, db)
|
||||
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
|
||||
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
|
||||
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, soraAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator)
|
||||
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, soraAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService)
|
||||
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
|
||||
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
|
||||
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService)
|
||||
@@ -137,14 +139,17 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
|
||||
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
|
||||
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
|
||||
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, compositeTokenCacheInvalidator)
|
||||
rpmCache := repository.NewRPMCache(redisClient)
|
||||
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)
|
||||
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
||||
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
||||
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
||||
antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService)
|
||||
proxyHandler := admin.NewProxyHandler(adminService)
|
||||
adminRedeemHandler := admin.NewRedeemHandler(adminService)
|
||||
adminRedeemHandler := admin.NewRedeemHandler(adminService, redeemService)
|
||||
promoHandler := admin.NewPromoHandler(promoService)
|
||||
opsRepository := repository.NewOpsRepository(db)
|
||||
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
|
||||
@@ -157,13 +162,18 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
||||
claudeTokenProvider := service.NewClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService)
|
||||
digestSessionStore := service.NewDigestSessionStore()
|
||||
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, digestSessionStore)
|
||||
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore)
|
||||
openAITokenProvider := service.NewOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService)
|
||||
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider)
|
||||
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
||||
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
|
||||
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
|
||||
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
|
||||
soraS3Storage := service.NewSoraS3Storage(settingService)
|
||||
settingService.SetOnS3UpdateCallback(soraS3Storage.RefreshClient)
|
||||
soraGenerationRepository := repository.NewSoraGenerationRepository(db)
|
||||
soraQuotaService := service.NewSoraQuotaService(userRepository, groupRepository, settingService)
|
||||
soraGenerationService := service.NewSoraGenerationService(soraGenerationRepository, soraS3Storage, soraQuotaService)
|
||||
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, soraS3Storage)
|
||||
opsHandler := admin.NewOpsHandler(opsService)
|
||||
updateCache := repository.NewUpdateCache(redisClient)
|
||||
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
|
||||
@@ -184,19 +194,27 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient)
|
||||
errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache)
|
||||
errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService)
|
||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler)
|
||||
adminAPIKeyHandler := admin.NewAdminAPIKeyHandler(adminService)
|
||||
scheduledTestPlanRepository := repository.NewScheduledTestPlanRepository(db)
|
||||
scheduledTestResultRepository := repository.NewScheduledTestResultRepository(db)
|
||||
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
|
||||
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
|
||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, adminAPIKeyHandler, scheduledTestHandler)
|
||||
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
|
||||
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, 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)
|
||||
soraSDKClient := service.ProvideSoraSDKClient(configConfig, httpUpstream, openAITokenProvider, accountRepository, soraAccountRepository)
|
||||
soraMediaStorage := service.ProvideSoraMediaStorage(configConfig)
|
||||
soraGatewayService := service.NewSoraGatewayService(soraSDKClient, soraMediaStorage, rateLimitService, configConfig)
|
||||
soraGatewayService := service.NewSoraGatewayService(soraSDKClient, rateLimitService, httpUpstream, configConfig)
|
||||
soraClientHandler := handler.NewSoraClientHandler(soraGenerationService, soraQuotaService, soraS3Storage, soraGatewayService, gatewayService, soraMediaStorage, apiKeyService)
|
||||
soraGatewayHandler := handler.NewSoraGatewayHandler(gatewayService, soraGatewayService, concurrencyService, billingCacheService, usageRecordWorkerPool, configConfig)
|
||||
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
|
||||
totpHandler := handler.NewTotpHandler(totpService)
|
||||
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
|
||||
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
|
||||
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, soraGatewayHandler, handlerSettingHandler, totpHandler, idempotencyCoordinator, idempotencyCleanupService)
|
||||
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, soraGatewayHandler, soraClientHandler, handlerSettingHandler, totpHandler, idempotencyCoordinator, idempotencyCleanupService)
|
||||
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
||||
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
||||
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
||||
@@ -208,10 +226,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig)
|
||||
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
|
||||
soraMediaCleanupService := service.ProvideSoraMediaCleanupService(soraMediaStorage, configConfig)
|
||||
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, soraAccountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig)
|
||||
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, soraAccountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache)
|
||||
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
||||
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
|
||||
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, soraMediaCleanupService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService)
|
||||
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, configConfig)
|
||||
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, soraMediaCleanupService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService)
|
||||
application := &Application{
|
||||
Server: httpServer,
|
||||
Cleanup: v,
|
||||
@@ -258,15 +277,19 @@ func provideCleanup(
|
||||
openaiOAuth *service.OpenAIOAuthService,
|
||||
geminiOAuth *service.GeminiOAuthService,
|
||||
antigravityOAuth *service.AntigravityOAuthService,
|
||||
openAIGateway *service.OpenAIGatewayService,
|
||||
scheduledTestRunner *service.ScheduledTestRunnerService,
|
||||
) func() {
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cleanupSteps := []struct {
|
||||
type cleanupStep struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
}
|
||||
|
||||
parallelSteps := []cleanupStep{
|
||||
{"OpsScheduledReportService", func() error {
|
||||
if opsScheduledReport != nil {
|
||||
opsScheduledReport.Stop()
|
||||
@@ -379,23 +402,66 @@ func provideCleanup(
|
||||
antigravityOAuth.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"OpenAIWSPool", func() error {
|
||||
if openAIGateway != nil {
|
||||
openAIGateway.CloseOpenAIWSPool()
|
||||
}
|
||||
return nil
|
||||
}},
|
||||
{"ScheduledTestRunnerService", func() error {
|
||||
if scheduledTestRunner != nil {
|
||||
scheduledTestRunner.Stop()
|
||||
}
|
||||
return nil
|
||||
}},
|
||||
}
|
||||
|
||||
infraSteps := []cleanupStep{
|
||||
{"Redis", func() error {
|
||||
if rdb == nil {
|
||||
return nil
|
||||
}
|
||||
return rdb.Close()
|
||||
}},
|
||||
{"Ent", func() error {
|
||||
if entClient == nil {
|
||||
return nil
|
||||
}
|
||||
return entClient.Close()
|
||||
}},
|
||||
}
|
||||
|
||||
for _, step := range cleanupSteps {
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
runParallel := func(steps []cleanupStep) {
|
||||
var wg sync.WaitGroup
|
||||
for i := range steps {
|
||||
step := steps[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
return
|
||||
}
|
||||
log.Printf("[Cleanup] %s succeeded", step.name)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
} else {
|
||||
runSequential := func(steps []cleanupStep) {
|
||||
for i := range steps {
|
||||
step := steps[i]
|
||||
if err := step.fn(); err != nil {
|
||||
log.Printf("[Cleanup] %s failed: %v", step.name, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[Cleanup] %s succeeded", step.name)
|
||||
}
|
||||
}
|
||||
|
||||
runParallel(parallelSteps)
|
||||
runSequential(infraSteps)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds")
|
||||
|
||||
83
backend/cmd/server/wire_gen_test.go
Normal file
83
backend/cmd/server/wire_gen_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProvideServiceBuildInfo(t *testing.T) {
|
||||
in := handler.BuildInfo{
|
||||
Version: "v-test",
|
||||
BuildType: "release",
|
||||
}
|
||||
out := provideServiceBuildInfo(in)
|
||||
require.Equal(t, in.Version, out.Version)
|
||||
require.Equal(t, in.BuildType, out.BuildType)
|
||||
}
|
||||
|
||||
func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
|
||||
oauthSvc := service.NewOAuthService(nil, nil)
|
||||
openAIOAuthSvc := service.NewOpenAIOAuthService(nil, nil)
|
||||
geminiOAuthSvc := service.NewGeminiOAuthService(nil, nil, nil, nil, cfg)
|
||||
antigravityOAuthSvc := service.NewAntigravityOAuthService(nil)
|
||||
|
||||
tokenRefreshSvc := service.NewTokenRefreshService(
|
||||
nil,
|
||||
oauthSvc,
|
||||
openAIOAuthSvc,
|
||||
geminiOAuthSvc,
|
||||
antigravityOAuthSvc,
|
||||
nil,
|
||||
nil,
|
||||
cfg,
|
||||
nil,
|
||||
)
|
||||
accountExpirySvc := service.NewAccountExpiryService(nil, time.Second)
|
||||
subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second)
|
||||
pricingSvc := service.NewPricingService(cfg, nil)
|
||||
emailQueueSvc := service.NewEmailQueueService(nil, 1)
|
||||
billingCacheSvc := service.NewBillingCacheService(nil, nil, nil, nil, cfg)
|
||||
idempotencyCleanupSvc := service.NewIdempotencyCleanupService(nil, cfg)
|
||||
schedulerSnapshotSvc := service.NewSchedulerSnapshotService(nil, nil, nil, nil, cfg)
|
||||
opsSystemLogSinkSvc := service.NewOpsSystemLogSink(nil)
|
||||
|
||||
cleanup := provideCleanup(
|
||||
nil, // entClient
|
||||
nil, // redis
|
||||
&service.OpsMetricsCollector{},
|
||||
&service.OpsAggregationService{},
|
||||
&service.OpsAlertEvaluatorService{},
|
||||
&service.OpsCleanupService{},
|
||||
&service.OpsScheduledReportService{},
|
||||
opsSystemLogSinkSvc,
|
||||
&service.SoraMediaCleanupService{},
|
||||
schedulerSnapshotSvc,
|
||||
tokenRefreshSvc,
|
||||
accountExpirySvc,
|
||||
subscriptionExpirySvc,
|
||||
&service.UsageCleanupService{},
|
||||
idempotencyCleanupSvc,
|
||||
pricingSvc,
|
||||
emailQueueSvc,
|
||||
billingCacheSvc,
|
||||
&service.UsageRecordWorkerPool{},
|
||||
&service.SubscriptionService{},
|
||||
oauthSvc,
|
||||
openAIOAuthSvc,
|
||||
geminiOAuthSvc,
|
||||
antigravityOAuthSvc,
|
||||
nil, // openAIGateway
|
||||
nil, // scheduledTestRunner
|
||||
)
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
cleanup()
|
||||
})
|
||||
}
|
||||
@@ -41,6 +41,8 @@ type Account struct {
|
||||
ProxyID *int64 `json:"proxy_id,omitempty"`
|
||||
// Concurrency holds the value of the "concurrency" field.
|
||||
Concurrency int `json:"concurrency,omitempty"`
|
||||
// LoadFactor holds the value of the "load_factor" field.
|
||||
LoadFactor *int `json:"load_factor,omitempty"`
|
||||
// Priority holds the value of the "priority" field.
|
||||
Priority int `json:"priority,omitempty"`
|
||||
// RateMultiplier holds the value of the "rate_multiplier" field.
|
||||
@@ -63,6 +65,10 @@ type Account struct {
|
||||
RateLimitResetAt *time.Time `json:"rate_limit_reset_at,omitempty"`
|
||||
// OverloadUntil holds the value of the "overload_until" field.
|
||||
OverloadUntil *time.Time `json:"overload_until,omitempty"`
|
||||
// TempUnschedulableUntil holds the value of the "temp_unschedulable_until" field.
|
||||
TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"`
|
||||
// TempUnschedulableReason holds the value of the "temp_unschedulable_reason" field.
|
||||
TempUnschedulableReason *string `json:"temp_unschedulable_reason,omitempty"`
|
||||
// SessionWindowStart holds the value of the "session_window_start" field.
|
||||
SessionWindowStart *time.Time `json:"session_window_start,omitempty"`
|
||||
// SessionWindowEnd holds the value of the "session_window_end" field.
|
||||
@@ -139,11 +145,11 @@ func (*Account) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullBool)
|
||||
case account.FieldRateMultiplier:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
|
||||
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus:
|
||||
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
|
||||
values[i] = new(sql.NullString)
|
||||
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
|
||||
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldTempUnschedulableUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
@@ -239,6 +245,13 @@ func (_m *Account) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.Concurrency = int(value.Int64)
|
||||
}
|
||||
case account.FieldLoadFactor:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field load_factor", values[i])
|
||||
} else if value.Valid {
|
||||
_m.LoadFactor = new(int)
|
||||
*_m.LoadFactor = int(value.Int64)
|
||||
}
|
||||
case account.FieldPriority:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field priority", values[i])
|
||||
@@ -311,6 +324,20 @@ func (_m *Account) assignValues(columns []string, values []any) error {
|
||||
_m.OverloadUntil = new(time.Time)
|
||||
*_m.OverloadUntil = value.Time
|
||||
}
|
||||
case account.FieldTempUnschedulableUntil:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field temp_unschedulable_until", values[i])
|
||||
} else if value.Valid {
|
||||
_m.TempUnschedulableUntil = new(time.Time)
|
||||
*_m.TempUnschedulableUntil = value.Time
|
||||
}
|
||||
case account.FieldTempUnschedulableReason:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field temp_unschedulable_reason", values[i])
|
||||
} else if value.Valid {
|
||||
_m.TempUnschedulableReason = new(string)
|
||||
*_m.TempUnschedulableReason = value.String
|
||||
}
|
||||
case account.FieldSessionWindowStart:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field session_window_start", values[i])
|
||||
@@ -427,6 +454,11 @@ func (_m *Account) String() string {
|
||||
builder.WriteString("concurrency=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.LoadFactor; v != nil {
|
||||
builder.WriteString("load_factor=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("priority=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
|
||||
builder.WriteString(", ")
|
||||
@@ -472,6 +504,16 @@ func (_m *Account) String() string {
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.TempUnschedulableUntil; v != nil {
|
||||
builder.WriteString("temp_unschedulable_until=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.TempUnschedulableReason; v != nil {
|
||||
builder.WriteString("temp_unschedulable_reason=")
|
||||
builder.WriteString(*v)
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.SessionWindowStart; v != nil {
|
||||
builder.WriteString("session_window_start=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
|
||||
@@ -37,6 +37,8 @@ const (
|
||||
FieldProxyID = "proxy_id"
|
||||
// FieldConcurrency holds the string denoting the concurrency field in the database.
|
||||
FieldConcurrency = "concurrency"
|
||||
// FieldLoadFactor holds the string denoting the load_factor field in the database.
|
||||
FieldLoadFactor = "load_factor"
|
||||
// FieldPriority holds the string denoting the priority field in the database.
|
||||
FieldPriority = "priority"
|
||||
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
||||
@@ -59,6 +61,10 @@ const (
|
||||
FieldRateLimitResetAt = "rate_limit_reset_at"
|
||||
// FieldOverloadUntil holds the string denoting the overload_until field in the database.
|
||||
FieldOverloadUntil = "overload_until"
|
||||
// FieldTempUnschedulableUntil holds the string denoting the temp_unschedulable_until field in the database.
|
||||
FieldTempUnschedulableUntil = "temp_unschedulable_until"
|
||||
// FieldTempUnschedulableReason holds the string denoting the temp_unschedulable_reason field in the database.
|
||||
FieldTempUnschedulableReason = "temp_unschedulable_reason"
|
||||
// FieldSessionWindowStart holds the string denoting the session_window_start field in the database.
|
||||
FieldSessionWindowStart = "session_window_start"
|
||||
// FieldSessionWindowEnd holds the string denoting the session_window_end field in the database.
|
||||
@@ -117,6 +123,7 @@ var Columns = []string{
|
||||
FieldExtra,
|
||||
FieldProxyID,
|
||||
FieldConcurrency,
|
||||
FieldLoadFactor,
|
||||
FieldPriority,
|
||||
FieldRateMultiplier,
|
||||
FieldStatus,
|
||||
@@ -128,6 +135,8 @@ var Columns = []string{
|
||||
FieldRateLimitedAt,
|
||||
FieldRateLimitResetAt,
|
||||
FieldOverloadUntil,
|
||||
FieldTempUnschedulableUntil,
|
||||
FieldTempUnschedulableReason,
|
||||
FieldSessionWindowStart,
|
||||
FieldSessionWindowEnd,
|
||||
FieldSessionWindowStatus,
|
||||
@@ -244,6 +253,11 @@ func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByLoadFactor orders the results by the load_factor field.
|
||||
func ByLoadFactor(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldLoadFactor, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByPriority orders the results by the priority field.
|
||||
func ByPriority(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldPriority, opts...).ToFunc()
|
||||
@@ -299,6 +313,16 @@ func ByOverloadUntil(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldOverloadUntil, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByTempUnschedulableUntil orders the results by the temp_unschedulable_until field.
|
||||
func ByTempUnschedulableUntil(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTempUnschedulableUntil, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByTempUnschedulableReason orders the results by the temp_unschedulable_reason field.
|
||||
func ByTempUnschedulableReason(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTempUnschedulableReason, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySessionWindowStart orders the results by the session_window_start field.
|
||||
func BySessionWindowStart(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSessionWindowStart, opts...).ToFunc()
|
||||
|
||||
@@ -100,6 +100,11 @@ func Concurrency(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
|
||||
}
|
||||
|
||||
// LoadFactor applies equality check predicate on the "load_factor" field. It's identical to LoadFactorEQ.
|
||||
func LoadFactor(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
|
||||
func Priority(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
||||
@@ -155,6 +160,16 @@ func OverloadUntil(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldOverloadUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntil applies equality check predicate on the "temp_unschedulable_until" field. It's identical to TempUnschedulableUntilEQ.
|
||||
func TempUnschedulableUntil(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReason applies equality check predicate on the "temp_unschedulable_reason" field. It's identical to TempUnschedulableReasonEQ.
|
||||
func TempUnschedulableReason(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// SessionWindowStart applies equality check predicate on the "session_window_start" field. It's identical to SessionWindowStartEQ.
|
||||
func SessionWindowStart(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))
|
||||
@@ -640,6 +655,56 @@ func ConcurrencyLTE(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldLTE(FieldConcurrency, v))
|
||||
}
|
||||
|
||||
// LoadFactorEQ applies the EQ predicate on the "load_factor" field.
|
||||
func LoadFactorEQ(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorNEQ applies the NEQ predicate on the "load_factor" field.
|
||||
func LoadFactorNEQ(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldNEQ(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorIn applies the In predicate on the "load_factor" field.
|
||||
func LoadFactorIn(vs ...int) predicate.Account {
|
||||
return predicate.Account(sql.FieldIn(FieldLoadFactor, vs...))
|
||||
}
|
||||
|
||||
// LoadFactorNotIn applies the NotIn predicate on the "load_factor" field.
|
||||
func LoadFactorNotIn(vs ...int) predicate.Account {
|
||||
return predicate.Account(sql.FieldNotIn(FieldLoadFactor, vs...))
|
||||
}
|
||||
|
||||
// LoadFactorGT applies the GT predicate on the "load_factor" field.
|
||||
func LoadFactorGT(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldGT(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorGTE applies the GTE predicate on the "load_factor" field.
|
||||
func LoadFactorGTE(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldGTE(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorLT applies the LT predicate on the "load_factor" field.
|
||||
func LoadFactorLT(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldLT(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorLTE applies the LTE predicate on the "load_factor" field.
|
||||
func LoadFactorLTE(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldLTE(FieldLoadFactor, v))
|
||||
}
|
||||
|
||||
// LoadFactorIsNil applies the IsNil predicate on the "load_factor" field.
|
||||
func LoadFactorIsNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldIsNull(FieldLoadFactor))
|
||||
}
|
||||
|
||||
// LoadFactorNotNil applies the NotNil predicate on the "load_factor" field.
|
||||
func LoadFactorNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldLoadFactor))
|
||||
}
|
||||
|
||||
// PriorityEQ applies the EQ predicate on the "priority" field.
|
||||
func PriorityEQ(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
||||
@@ -1130,6 +1195,131 @@ func OverloadUntilNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldOverloadUntil))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilEQ applies the EQ predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilEQ(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilNEQ applies the NEQ predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilNEQ(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldNEQ(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilIn applies the In predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilIn(vs ...time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldIn(FieldTempUnschedulableUntil, vs...))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilNotIn applies the NotIn predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilNotIn(vs ...time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldNotIn(FieldTempUnschedulableUntil, vs...))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilGT applies the GT predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilGT(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldGT(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilGTE applies the GTE predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilGTE(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldGTE(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilLT applies the LT predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilLT(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldLT(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilLTE applies the LTE predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilLTE(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldLTE(FieldTempUnschedulableUntil, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilIsNil applies the IsNil predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilIsNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldIsNull(FieldTempUnschedulableUntil))
|
||||
}
|
||||
|
||||
// TempUnschedulableUntilNotNil applies the NotNil predicate on the "temp_unschedulable_until" field.
|
||||
func TempUnschedulableUntilNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldTempUnschedulableUntil))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonEQ applies the EQ predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonEQ(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonNEQ applies the NEQ predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonNEQ(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldNEQ(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonIn applies the In predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonIn(vs ...string) predicate.Account {
|
||||
return predicate.Account(sql.FieldIn(FieldTempUnschedulableReason, vs...))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonNotIn applies the NotIn predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonNotIn(vs ...string) predicate.Account {
|
||||
return predicate.Account(sql.FieldNotIn(FieldTempUnschedulableReason, vs...))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonGT applies the GT predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonGT(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldGT(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonGTE applies the GTE predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonGTE(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldGTE(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonLT applies the LT predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonLT(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldLT(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonLTE applies the LTE predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonLTE(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldLTE(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonContains applies the Contains predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonContains(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldContains(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonHasPrefix applies the HasPrefix predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonHasPrefix(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldHasPrefix(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonHasSuffix applies the HasSuffix predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonHasSuffix(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldHasSuffix(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonIsNil applies the IsNil predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonIsNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldIsNull(FieldTempUnschedulableReason))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonNotNil applies the NotNil predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldTempUnschedulableReason))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonEqualFold applies the EqualFold predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonEqualFold(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldEqualFold(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// TempUnschedulableReasonContainsFold applies the ContainsFold predicate on the "temp_unschedulable_reason" field.
|
||||
func TempUnschedulableReasonContainsFold(v string) predicate.Account {
|
||||
return predicate.Account(sql.FieldContainsFold(FieldTempUnschedulableReason, v))
|
||||
}
|
||||
|
||||
// SessionWindowStartEQ applies the EQ predicate on the "session_window_start" field.
|
||||
func SessionWindowStartEQ(v time.Time) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldSessionWindowStart, v))
|
||||
|
||||
@@ -139,6 +139,20 @@ func (_c *AccountCreate) SetNillableConcurrency(v *int) *AccountCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (_c *AccountCreate) SetLoadFactor(v int) *AccountCreate {
|
||||
_c.mutation.SetLoadFactor(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||
func (_c *AccountCreate) SetNillableLoadFactor(v *int) *AccountCreate {
|
||||
if v != nil {
|
||||
_c.SetLoadFactor(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (_c *AccountCreate) SetPriority(v int) *AccountCreate {
|
||||
_c.mutation.SetPriority(v)
|
||||
@@ -293,6 +307,34 @@ func (_c *AccountCreate) SetNillableOverloadUntil(v *time.Time) *AccountCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (_c *AccountCreate) SetTempUnschedulableUntil(v time.Time) *AccountCreate {
|
||||
_c.mutation.SetTempUnschedulableUntil(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
|
||||
func (_c *AccountCreate) SetNillableTempUnschedulableUntil(v *time.Time) *AccountCreate {
|
||||
if v != nil {
|
||||
_c.SetTempUnschedulableUntil(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (_c *AccountCreate) SetTempUnschedulableReason(v string) *AccountCreate {
|
||||
_c.mutation.SetTempUnschedulableReason(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
|
||||
func (_c *AccountCreate) SetNillableTempUnschedulableReason(v *string) *AccountCreate {
|
||||
if v != nil {
|
||||
_c.SetTempUnschedulableReason(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (_c *AccountCreate) SetSessionWindowStart(v time.Time) *AccountCreate {
|
||||
_c.mutation.SetSessionWindowStart(v)
|
||||
@@ -595,6 +637,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
||||
_node.Concurrency = value
|
||||
}
|
||||
if value, ok := _c.mutation.LoadFactor(); ok {
|
||||
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||
_node.LoadFactor = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Priority(); ok {
|
||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||
_node.Priority = value
|
||||
@@ -639,6 +685,14 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(account.FieldOverloadUntil, field.TypeTime, value)
|
||||
_node.OverloadUntil = &value
|
||||
}
|
||||
if value, ok := _c.mutation.TempUnschedulableUntil(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
|
||||
_node.TempUnschedulableUntil = &value
|
||||
}
|
||||
if value, ok := _c.mutation.TempUnschedulableReason(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
|
||||
_node.TempUnschedulableReason = &value
|
||||
}
|
||||
if value, ok := _c.mutation.SessionWindowStart(); ok {
|
||||
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
|
||||
_node.SessionWindowStart = &value
|
||||
@@ -900,6 +954,30 @@ func (u *AccountUpsert) AddConcurrency(v int) *AccountUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (u *AccountUpsert) SetLoadFactor(v int) *AccountUpsert {
|
||||
u.Set(account.FieldLoadFactor, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||
func (u *AccountUpsert) UpdateLoadFactor() *AccountUpsert {
|
||||
u.SetExcluded(account.FieldLoadFactor)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddLoadFactor adds v to the "load_factor" field.
|
||||
func (u *AccountUpsert) AddLoadFactor(v int) *AccountUpsert {
|
||||
u.Add(account.FieldLoadFactor, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||
func (u *AccountUpsert) ClearLoadFactor() *AccountUpsert {
|
||||
u.SetNull(account.FieldLoadFactor)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (u *AccountUpsert) SetPriority(v int) *AccountUpsert {
|
||||
u.Set(account.FieldPriority, v)
|
||||
@@ -1080,6 +1158,42 @@ func (u *AccountUpsert) ClearOverloadUntil() *AccountUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsert) SetTempUnschedulableUntil(v time.Time) *AccountUpsert {
|
||||
u.Set(account.FieldTempUnschedulableUntil, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
|
||||
func (u *AccountUpsert) UpdateTempUnschedulableUntil() *AccountUpsert {
|
||||
u.SetExcluded(account.FieldTempUnschedulableUntil)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsert) ClearTempUnschedulableUntil() *AccountUpsert {
|
||||
u.SetNull(account.FieldTempUnschedulableUntil)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsert) SetTempUnschedulableReason(v string) *AccountUpsert {
|
||||
u.Set(account.FieldTempUnschedulableReason, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
|
||||
func (u *AccountUpsert) UpdateTempUnschedulableReason() *AccountUpsert {
|
||||
u.SetExcluded(account.FieldTempUnschedulableReason)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsert) ClearTempUnschedulableReason() *AccountUpsert {
|
||||
u.SetNull(account.FieldTempUnschedulableReason)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (u *AccountUpsert) SetSessionWindowStart(v time.Time) *AccountUpsert {
|
||||
u.Set(account.FieldSessionWindowStart, v)
|
||||
@@ -1347,6 +1461,34 @@ func (u *AccountUpsertOne) UpdateConcurrency() *AccountUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (u *AccountUpsertOne) SetLoadFactor(v int) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetLoadFactor(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddLoadFactor adds v to the "load_factor" field.
|
||||
func (u *AccountUpsertOne) AddLoadFactor(v int) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.AddLoadFactor(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||
func (u *AccountUpsertOne) UpdateLoadFactor() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateLoadFactor()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||
func (u *AccountUpsertOne) ClearLoadFactor() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearLoadFactor()
|
||||
})
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
@@ -1557,6 +1699,48 @@ func (u *AccountUpsertOne) ClearOverloadUntil() *AccountUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsertOne) SetTempUnschedulableUntil(v time.Time) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetTempUnschedulableUntil(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
|
||||
func (u *AccountUpsertOne) UpdateTempUnschedulableUntil() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateTempUnschedulableUntil()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsertOne) ClearTempUnschedulableUntil() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearTempUnschedulableUntil()
|
||||
})
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsertOne) SetTempUnschedulableReason(v string) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetTempUnschedulableReason(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
|
||||
func (u *AccountUpsertOne) UpdateTempUnschedulableReason() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateTempUnschedulableReason()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsertOne) ClearTempUnschedulableReason() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearTempUnschedulableReason()
|
||||
})
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (u *AccountUpsertOne) SetSessionWindowStart(v time.Time) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
@@ -1999,6 +2183,34 @@ func (u *AccountUpsertBulk) UpdateConcurrency() *AccountUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (u *AccountUpsertBulk) SetLoadFactor(v int) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetLoadFactor(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddLoadFactor adds v to the "load_factor" field.
|
||||
func (u *AccountUpsertBulk) AddLoadFactor(v int) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.AddLoadFactor(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||
func (u *AccountUpsertBulk) UpdateLoadFactor() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateLoadFactor()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||
func (u *AccountUpsertBulk) ClearLoadFactor() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearLoadFactor()
|
||||
})
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
@@ -2209,6 +2421,48 @@ func (u *AccountUpsertBulk) ClearOverloadUntil() *AccountUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsertBulk) SetTempUnschedulableUntil(v time.Time) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetTempUnschedulableUntil(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableUntil sets the "temp_unschedulable_until" field to the value that was provided on create.
|
||||
func (u *AccountUpsertBulk) UpdateTempUnschedulableUntil() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateTempUnschedulableUntil()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
|
||||
func (u *AccountUpsertBulk) ClearTempUnschedulableUntil() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearTempUnschedulableUntil()
|
||||
})
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsertBulk) SetTempUnschedulableReason(v string) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetTempUnschedulableReason(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTempUnschedulableReason sets the "temp_unschedulable_reason" field to the value that was provided on create.
|
||||
func (u *AccountUpsertBulk) UpdateTempUnschedulableReason() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateTempUnschedulableReason()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
|
||||
func (u *AccountUpsertBulk) ClearTempUnschedulableReason() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearTempUnschedulableReason()
|
||||
})
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (u *AccountUpsertBulk) SetSessionWindowStart(v time.Time) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
|
||||
@@ -172,6 +172,33 @@ func (_u *AccountUpdate) AddConcurrency(v int) *AccountUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (_u *AccountUpdate) SetLoadFactor(v int) *AccountUpdate {
|
||||
_u.mutation.ResetLoadFactor()
|
||||
_u.mutation.SetLoadFactor(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||
func (_u *AccountUpdate) SetNillableLoadFactor(v *int) *AccountUpdate {
|
||||
if v != nil {
|
||||
_u.SetLoadFactor(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddLoadFactor adds value to the "load_factor" field.
|
||||
func (_u *AccountUpdate) AddLoadFactor(v int) *AccountUpdate {
|
||||
_u.mutation.AddLoadFactor(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||
func (_u *AccountUpdate) ClearLoadFactor() *AccountUpdate {
|
||||
_u.mutation.ClearLoadFactor()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate {
|
||||
_u.mutation.ResetPriority()
|
||||
@@ -376,6 +403,46 @@ func (_u *AccountUpdate) ClearOverloadUntil() *AccountUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (_u *AccountUpdate) SetTempUnschedulableUntil(v time.Time) *AccountUpdate {
|
||||
_u.mutation.SetTempUnschedulableUntil(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
|
||||
func (_u *AccountUpdate) SetNillableTempUnschedulableUntil(v *time.Time) *AccountUpdate {
|
||||
if v != nil {
|
||||
_u.SetTempUnschedulableUntil(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
|
||||
func (_u *AccountUpdate) ClearTempUnschedulableUntil() *AccountUpdate {
|
||||
_u.mutation.ClearTempUnschedulableUntil()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (_u *AccountUpdate) SetTempUnschedulableReason(v string) *AccountUpdate {
|
||||
_u.mutation.SetTempUnschedulableReason(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
|
||||
func (_u *AccountUpdate) SetNillableTempUnschedulableReason(v *string) *AccountUpdate {
|
||||
if v != nil {
|
||||
_u.SetTempUnschedulableReason(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
|
||||
func (_u *AccountUpdate) ClearTempUnschedulableReason() *AccountUpdate {
|
||||
_u.mutation.ClearTempUnschedulableReason()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (_u *AccountUpdate) SetSessionWindowStart(v time.Time) *AccountUpdate {
|
||||
_u.mutation.SetSessionWindowStart(v)
|
||||
@@ -644,6 +711,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
||||
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LoadFactor(); ok {
|
||||
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedLoadFactor(); ok {
|
||||
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.LoadFactorCleared() {
|
||||
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.Priority(); ok {
|
||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||
}
|
||||
@@ -701,6 +777,18 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.OverloadUntilCleared() {
|
||||
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.TempUnschedulableUntil(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.TempUnschedulableUntilCleared() {
|
||||
_spec.ClearField(account.FieldTempUnschedulableUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.TempUnschedulableReason(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.TempUnschedulableReasonCleared() {
|
||||
_spec.ClearField(account.FieldTempUnschedulableReason, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.SessionWindowStart(); ok {
|
||||
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
|
||||
}
|
||||
@@ -1011,6 +1099,33 @@ func (_u *AccountUpdateOne) AddConcurrency(v int) *AccountUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLoadFactor sets the "load_factor" field.
|
||||
func (_u *AccountUpdateOne) SetLoadFactor(v int) *AccountUpdateOne {
|
||||
_u.mutation.ResetLoadFactor()
|
||||
_u.mutation.SetLoadFactor(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||
func (_u *AccountUpdateOne) SetNillableLoadFactor(v *int) *AccountUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetLoadFactor(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddLoadFactor adds value to the "load_factor" field.
|
||||
func (_u *AccountUpdateOne) AddLoadFactor(v int) *AccountUpdateOne {
|
||||
_u.mutation.AddLoadFactor(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||
func (_u *AccountUpdateOne) ClearLoadFactor() *AccountUpdateOne {
|
||||
_u.mutation.ClearLoadFactor()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPriority sets the "priority" field.
|
||||
func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne {
|
||||
_u.mutation.ResetPriority()
|
||||
@@ -1215,6 +1330,46 @@ func (_u *AccountUpdateOne) ClearOverloadUntil() *AccountUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableUntil sets the "temp_unschedulable_until" field.
|
||||
func (_u *AccountUpdateOne) SetTempUnschedulableUntil(v time.Time) *AccountUpdateOne {
|
||||
_u.mutation.SetTempUnschedulableUntil(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableUntil sets the "temp_unschedulable_until" field if the given value is not nil.
|
||||
func (_u *AccountUpdateOne) SetNillableTempUnschedulableUntil(v *time.Time) *AccountUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetTempUnschedulableUntil(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableUntil clears the value of the "temp_unschedulable_until" field.
|
||||
func (_u *AccountUpdateOne) ClearTempUnschedulableUntil() *AccountUpdateOne {
|
||||
_u.mutation.ClearTempUnschedulableUntil()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTempUnschedulableReason sets the "temp_unschedulable_reason" field.
|
||||
func (_u *AccountUpdateOne) SetTempUnschedulableReason(v string) *AccountUpdateOne {
|
||||
_u.mutation.SetTempUnschedulableReason(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTempUnschedulableReason sets the "temp_unschedulable_reason" field if the given value is not nil.
|
||||
func (_u *AccountUpdateOne) SetNillableTempUnschedulableReason(v *string) *AccountUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetTempUnschedulableReason(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTempUnschedulableReason clears the value of the "temp_unschedulable_reason" field.
|
||||
func (_u *AccountUpdateOne) ClearTempUnschedulableReason() *AccountUpdateOne {
|
||||
_u.mutation.ClearTempUnschedulableReason()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSessionWindowStart sets the "session_window_start" field.
|
||||
func (_u *AccountUpdateOne) SetSessionWindowStart(v time.Time) *AccountUpdateOne {
|
||||
_u.mutation.SetSessionWindowStart(v)
|
||||
@@ -1513,6 +1668,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
|
||||
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
||||
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LoadFactor(); ok {
|
||||
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedLoadFactor(); ok {
|
||||
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.LoadFactorCleared() {
|
||||
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.Priority(); ok {
|
||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||
}
|
||||
@@ -1570,6 +1734,18 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
|
||||
if _u.mutation.OverloadUntilCleared() {
|
||||
_spec.ClearField(account.FieldOverloadUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.TempUnschedulableUntil(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableUntil, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.TempUnschedulableUntilCleared() {
|
||||
_spec.ClearField(account.FieldTempUnschedulableUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.TempUnschedulableReason(); ok {
|
||||
_spec.SetField(account.FieldTempUnschedulableReason, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.TempUnschedulableReasonCleared() {
|
||||
_spec.ClearField(account.FieldTempUnschedulableReason, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.SessionWindowStart(); ok {
|
||||
_spec.SetField(account.FieldSessionWindowStart, field.TypeTime, value)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,24 @@ type APIKey struct {
|
||||
QuotaUsed float64 `json:"quota_used,omitempty"`
|
||||
// Expiration time for this API key (null = never expires)
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
// Rate limit in USD per 5 hours (0 = unlimited)
|
||||
RateLimit5h float64 `json:"rate_limit_5h,omitempty"`
|
||||
// Rate limit in USD per day (0 = unlimited)
|
||||
RateLimit1d float64 `json:"rate_limit_1d,omitempty"`
|
||||
// Rate limit in USD per 7 days (0 = unlimited)
|
||||
RateLimit7d float64 `json:"rate_limit_7d,omitempty"`
|
||||
// Used amount in USD for the current 5h window
|
||||
Usage5h float64 `json:"usage_5h,omitempty"`
|
||||
// Used amount in USD for the current 1d window
|
||||
Usage1d float64 `json:"usage_1d,omitempty"`
|
||||
// Used amount in USD for the current 7d window
|
||||
Usage7d float64 `json:"usage_7d,omitempty"`
|
||||
// Start time of the current 5h rate limit window
|
||||
Window5hStart *time.Time `json:"window_5h_start,omitempty"`
|
||||
// Start time of the current 1d rate limit window
|
||||
Window1dStart *time.Time `json:"window_1d_start,omitempty"`
|
||||
// Start time of the current 7d rate limit window
|
||||
Window7dStart *time.Time `json:"window_7d_start,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the APIKeyQuery when eager-loading is set.
|
||||
Edges APIKeyEdges `json:"edges"`
|
||||
@@ -105,13 +123,13 @@ func (*APIKey) scanValues(columns []string) ([]any, error) {
|
||||
switch columns[i] {
|
||||
case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist:
|
||||
values[i] = new([]byte)
|
||||
case apikey.FieldQuota, apikey.FieldQuotaUsed:
|
||||
case apikey.FieldQuota, apikey.FieldQuotaUsed, apikey.FieldRateLimit5h, apikey.FieldRateLimit1d, apikey.FieldRateLimit7d, apikey.FieldUsage5h, apikey.FieldUsage1d, apikey.FieldUsage7d:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case apikey.FieldID, apikey.FieldUserID, apikey.FieldGroupID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus:
|
||||
values[i] = new(sql.NullString)
|
||||
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt:
|
||||
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt, apikey.FieldWindow5hStart, apikey.FieldWindow1dStart, apikey.FieldWindow7dStart:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
@@ -226,6 +244,63 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
|
||||
_m.ExpiresAt = new(time.Time)
|
||||
*_m.ExpiresAt = value.Time
|
||||
}
|
||||
case apikey.FieldRateLimit5h:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field rate_limit_5h", values[i])
|
||||
} else if value.Valid {
|
||||
_m.RateLimit5h = value.Float64
|
||||
}
|
||||
case apikey.FieldRateLimit1d:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field rate_limit_1d", values[i])
|
||||
} else if value.Valid {
|
||||
_m.RateLimit1d = value.Float64
|
||||
}
|
||||
case apikey.FieldRateLimit7d:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field rate_limit_7d", values[i])
|
||||
} else if value.Valid {
|
||||
_m.RateLimit7d = value.Float64
|
||||
}
|
||||
case apikey.FieldUsage5h:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field usage_5h", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Usage5h = value.Float64
|
||||
}
|
||||
case apikey.FieldUsage1d:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field usage_1d", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Usage1d = value.Float64
|
||||
}
|
||||
case apikey.FieldUsage7d:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field usage_7d", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Usage7d = value.Float64
|
||||
}
|
||||
case apikey.FieldWindow5hStart:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field window_5h_start", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Window5hStart = new(time.Time)
|
||||
*_m.Window5hStart = value.Time
|
||||
}
|
||||
case apikey.FieldWindow1dStart:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field window_1d_start", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Window1dStart = new(time.Time)
|
||||
*_m.Window1dStart = value.Time
|
||||
}
|
||||
case apikey.FieldWindow7dStart:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field window_7d_start", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Window7dStart = new(time.Time)
|
||||
*_m.Window7dStart = value.Time
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
@@ -326,6 +401,39 @@ func (_m *APIKey) String() string {
|
||||
builder.WriteString("expires_at=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("rate_limit_5h=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit5h))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("rate_limit_1d=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit1d))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("rate_limit_7d=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit7d))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("usage_5h=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Usage5h))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("usage_1d=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Usage1d))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("usage_7d=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Usage7d))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.Window5hStart; v != nil {
|
||||
builder.WriteString("window_5h_start=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.Window1dStart; v != nil {
|
||||
builder.WriteString("window_1d_start=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.Window7dStart; v != nil {
|
||||
builder.WriteString("window_7d_start=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@@ -43,6 +43,24 @@ const (
|
||||
FieldQuotaUsed = "quota_used"
|
||||
// FieldExpiresAt holds the string denoting the expires_at field in the database.
|
||||
FieldExpiresAt = "expires_at"
|
||||
// FieldRateLimit5h holds the string denoting the rate_limit_5h field in the database.
|
||||
FieldRateLimit5h = "rate_limit_5h"
|
||||
// FieldRateLimit1d holds the string denoting the rate_limit_1d field in the database.
|
||||
FieldRateLimit1d = "rate_limit_1d"
|
||||
// FieldRateLimit7d holds the string denoting the rate_limit_7d field in the database.
|
||||
FieldRateLimit7d = "rate_limit_7d"
|
||||
// FieldUsage5h holds the string denoting the usage_5h field in the database.
|
||||
FieldUsage5h = "usage_5h"
|
||||
// FieldUsage1d holds the string denoting the usage_1d field in the database.
|
||||
FieldUsage1d = "usage_1d"
|
||||
// FieldUsage7d holds the string denoting the usage_7d field in the database.
|
||||
FieldUsage7d = "usage_7d"
|
||||
// FieldWindow5hStart holds the string denoting the window_5h_start field in the database.
|
||||
FieldWindow5hStart = "window_5h_start"
|
||||
// FieldWindow1dStart holds the string denoting the window_1d_start field in the database.
|
||||
FieldWindow1dStart = "window_1d_start"
|
||||
// FieldWindow7dStart holds the string denoting the window_7d_start field in the database.
|
||||
FieldWindow7dStart = "window_7d_start"
|
||||
// EdgeUser holds the string denoting the user edge name in mutations.
|
||||
EdgeUser = "user"
|
||||
// EdgeGroup holds the string denoting the group edge name in mutations.
|
||||
@@ -91,6 +109,15 @@ var Columns = []string{
|
||||
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).
|
||||
@@ -129,6 +156,18 @@ var (
|
||||
DefaultQuota float64
|
||||
// DefaultQuotaUsed holds the default value on creation for the "quota_used" field.
|
||||
DefaultQuotaUsed float64
|
||||
// DefaultRateLimit5h holds the default value on creation for the "rate_limit_5h" field.
|
||||
DefaultRateLimit5h float64
|
||||
// DefaultRateLimit1d holds the default value on creation for the "rate_limit_1d" field.
|
||||
DefaultRateLimit1d float64
|
||||
// DefaultRateLimit7d holds the default value on creation for the "rate_limit_7d" field.
|
||||
DefaultRateLimit7d float64
|
||||
// DefaultUsage5h holds the default value on creation for the "usage_5h" field.
|
||||
DefaultUsage5h float64
|
||||
// DefaultUsage1d holds the default value on creation for the "usage_1d" field.
|
||||
DefaultUsage1d float64
|
||||
// DefaultUsage7d holds the default value on creation for the "usage_7d" field.
|
||||
DefaultUsage7d float64
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the APIKey queries.
|
||||
@@ -199,6 +238,51 @@ func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByRateLimit5h orders the results by the rate_limit_5h field.
|
||||
func ByRateLimit5h(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldRateLimit5h, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByRateLimit1d orders the results by the rate_limit_1d field.
|
||||
func ByRateLimit1d(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldRateLimit1d, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByRateLimit7d orders the results by the rate_limit_7d field.
|
||||
func ByRateLimit7d(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldRateLimit7d, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUsage5h orders the results by the usage_5h field.
|
||||
func ByUsage5h(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUsage5h, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUsage1d orders the results by the usage_1d field.
|
||||
func ByUsage1d(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUsage1d, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUsage7d orders the results by the usage_7d field.
|
||||
func ByUsage7d(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUsage7d, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByWindow5hStart orders the results by the window_5h_start field.
|
||||
func ByWindow5hStart(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldWindow5hStart, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByWindow1dStart orders the results by the window_1d_start field.
|
||||
func ByWindow1dStart(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldWindow1dStart, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByWindow7dStart orders the results by the window_7d_start field.
|
||||
func ByWindow7dStart(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldWindow7dStart, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUserField orders the results by user field.
|
||||
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
|
||||
@@ -115,6 +115,51 @@ func ExpiresAt(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// RateLimit5h applies equality check predicate on the "rate_limit_5h" field. It's identical to RateLimit5hEQ.
|
||||
func RateLimit5h(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit1d applies equality check predicate on the "rate_limit_1d" field. It's identical to RateLimit1dEQ.
|
||||
func RateLimit1d(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit7d applies equality check predicate on the "rate_limit_7d" field. It's identical to RateLimit7dEQ.
|
||||
func RateLimit7d(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// Usage5h applies equality check predicate on the "usage_5h" field. It's identical to Usage5hEQ.
|
||||
func Usage5h(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage1d applies equality check predicate on the "usage_1d" field. It's identical to Usage1dEQ.
|
||||
func Usage1d(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage7d applies equality check predicate on the "usage_7d" field. It's identical to Usage7dEQ.
|
||||
func Usage7d(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Window5hStart applies equality check predicate on the "window_5h_start" field. It's identical to Window5hStartEQ.
|
||||
func Window5hStart(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window1dStart applies equality check predicate on the "window_1d_start" field. It's identical to Window1dStartEQ.
|
||||
func Window1dStart(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStart applies equality check predicate on the "window_7d_start" field. It's identical to Window7dStartEQ.
|
||||
func Window7dStart(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v))
|
||||
@@ -690,6 +735,396 @@ func ExpiresAtNotNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotNull(FieldExpiresAt))
|
||||
}
|
||||
|
||||
// RateLimit5hEQ applies the EQ predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit5hNEQ applies the NEQ predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit5hIn applies the In predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldRateLimit5h, vs...))
|
||||
}
|
||||
|
||||
// RateLimit5hNotIn applies the NotIn predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit5h, vs...))
|
||||
}
|
||||
|
||||
// RateLimit5hGT applies the GT predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit5hGTE applies the GTE predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit5hLT applies the LT predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit5hLTE applies the LTE predicate on the "rate_limit_5h" field.
|
||||
func RateLimit5hLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldRateLimit5h, v))
|
||||
}
|
||||
|
||||
// RateLimit1dEQ applies the EQ predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit1dNEQ applies the NEQ predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit1dIn applies the In predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldRateLimit1d, vs...))
|
||||
}
|
||||
|
||||
// RateLimit1dNotIn applies the NotIn predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit1d, vs...))
|
||||
}
|
||||
|
||||
// RateLimit1dGT applies the GT predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit1dGTE applies the GTE predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit1dLT applies the LT predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit1dLTE applies the LTE predicate on the "rate_limit_1d" field.
|
||||
func RateLimit1dLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldRateLimit1d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dEQ applies the EQ predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dNEQ applies the NEQ predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dIn applies the In predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldRateLimit7d, vs...))
|
||||
}
|
||||
|
||||
// RateLimit7dNotIn applies the NotIn predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit7d, vs...))
|
||||
}
|
||||
|
||||
// RateLimit7dGT applies the GT predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dGTE applies the GTE predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dLT applies the LT predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// RateLimit7dLTE applies the LTE predicate on the "rate_limit_7d" field.
|
||||
func RateLimit7dLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldRateLimit7d, v))
|
||||
}
|
||||
|
||||
// Usage5hEQ applies the EQ predicate on the "usage_5h" field.
|
||||
func Usage5hEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage5hNEQ applies the NEQ predicate on the "usage_5h" field.
|
||||
func Usage5hNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage5hIn applies the In predicate on the "usage_5h" field.
|
||||
func Usage5hIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldUsage5h, vs...))
|
||||
}
|
||||
|
||||
// Usage5hNotIn applies the NotIn predicate on the "usage_5h" field.
|
||||
func Usage5hNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldUsage5h, vs...))
|
||||
}
|
||||
|
||||
// Usage5hGT applies the GT predicate on the "usage_5h" field.
|
||||
func Usage5hGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage5hGTE applies the GTE predicate on the "usage_5h" field.
|
||||
func Usage5hGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage5hLT applies the LT predicate on the "usage_5h" field.
|
||||
func Usage5hLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage5hLTE applies the LTE predicate on the "usage_5h" field.
|
||||
func Usage5hLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldUsage5h, v))
|
||||
}
|
||||
|
||||
// Usage1dEQ applies the EQ predicate on the "usage_1d" field.
|
||||
func Usage1dEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage1dNEQ applies the NEQ predicate on the "usage_1d" field.
|
||||
func Usage1dNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage1dIn applies the In predicate on the "usage_1d" field.
|
||||
func Usage1dIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldUsage1d, vs...))
|
||||
}
|
||||
|
||||
// Usage1dNotIn applies the NotIn predicate on the "usage_1d" field.
|
||||
func Usage1dNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldUsage1d, vs...))
|
||||
}
|
||||
|
||||
// Usage1dGT applies the GT predicate on the "usage_1d" field.
|
||||
func Usage1dGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage1dGTE applies the GTE predicate on the "usage_1d" field.
|
||||
func Usage1dGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage1dLT applies the LT predicate on the "usage_1d" field.
|
||||
func Usage1dLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage1dLTE applies the LTE predicate on the "usage_1d" field.
|
||||
func Usage1dLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldUsage1d, v))
|
||||
}
|
||||
|
||||
// Usage7dEQ applies the EQ predicate on the "usage_7d" field.
|
||||
func Usage7dEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Usage7dNEQ applies the NEQ predicate on the "usage_7d" field.
|
||||
func Usage7dNEQ(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Usage7dIn applies the In predicate on the "usage_7d" field.
|
||||
func Usage7dIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldUsage7d, vs...))
|
||||
}
|
||||
|
||||
// Usage7dNotIn applies the NotIn predicate on the "usage_7d" field.
|
||||
func Usage7dNotIn(vs ...float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldUsage7d, vs...))
|
||||
}
|
||||
|
||||
// Usage7dGT applies the GT predicate on the "usage_7d" field.
|
||||
func Usage7dGT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Usage7dGTE applies the GTE predicate on the "usage_7d" field.
|
||||
func Usage7dGTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Usage7dLT applies the LT predicate on the "usage_7d" field.
|
||||
func Usage7dLT(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Usage7dLTE applies the LTE predicate on the "usage_7d" field.
|
||||
func Usage7dLTE(v float64) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldUsage7d, v))
|
||||
}
|
||||
|
||||
// Window5hStartEQ applies the EQ predicate on the "window_5h_start" field.
|
||||
func Window5hStartEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartNEQ applies the NEQ predicate on the "window_5h_start" field.
|
||||
func Window5hStartNEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartIn applies the In predicate on the "window_5h_start" field.
|
||||
func Window5hStartIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldWindow5hStart, vs...))
|
||||
}
|
||||
|
||||
// Window5hStartNotIn applies the NotIn predicate on the "window_5h_start" field.
|
||||
func Window5hStartNotIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldWindow5hStart, vs...))
|
||||
}
|
||||
|
||||
// Window5hStartGT applies the GT predicate on the "window_5h_start" field.
|
||||
func Window5hStartGT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartGTE applies the GTE predicate on the "window_5h_start" field.
|
||||
func Window5hStartGTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartLT applies the LT predicate on the "window_5h_start" field.
|
||||
func Window5hStartLT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartLTE applies the LTE predicate on the "window_5h_start" field.
|
||||
func Window5hStartLTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldWindow5hStart, v))
|
||||
}
|
||||
|
||||
// Window5hStartIsNil applies the IsNil predicate on the "window_5h_start" field.
|
||||
func Window5hStartIsNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIsNull(FieldWindow5hStart))
|
||||
}
|
||||
|
||||
// Window5hStartNotNil applies the NotNil predicate on the "window_5h_start" field.
|
||||
func Window5hStartNotNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotNull(FieldWindow5hStart))
|
||||
}
|
||||
|
||||
// Window1dStartEQ applies the EQ predicate on the "window_1d_start" field.
|
||||
func Window1dStartEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartNEQ applies the NEQ predicate on the "window_1d_start" field.
|
||||
func Window1dStartNEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartIn applies the In predicate on the "window_1d_start" field.
|
||||
func Window1dStartIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldWindow1dStart, vs...))
|
||||
}
|
||||
|
||||
// Window1dStartNotIn applies the NotIn predicate on the "window_1d_start" field.
|
||||
func Window1dStartNotIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldWindow1dStart, vs...))
|
||||
}
|
||||
|
||||
// Window1dStartGT applies the GT predicate on the "window_1d_start" field.
|
||||
func Window1dStartGT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartGTE applies the GTE predicate on the "window_1d_start" field.
|
||||
func Window1dStartGTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartLT applies the LT predicate on the "window_1d_start" field.
|
||||
func Window1dStartLT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartLTE applies the LTE predicate on the "window_1d_start" field.
|
||||
func Window1dStartLTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldWindow1dStart, v))
|
||||
}
|
||||
|
||||
// Window1dStartIsNil applies the IsNil predicate on the "window_1d_start" field.
|
||||
func Window1dStartIsNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIsNull(FieldWindow1dStart))
|
||||
}
|
||||
|
||||
// Window1dStartNotNil applies the NotNil predicate on the "window_1d_start" field.
|
||||
func Window1dStartNotNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotNull(FieldWindow1dStart))
|
||||
}
|
||||
|
||||
// Window7dStartEQ applies the EQ predicate on the "window_7d_start" field.
|
||||
func Window7dStartEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartNEQ applies the NEQ predicate on the "window_7d_start" field.
|
||||
func Window7dStartNEQ(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNEQ(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartIn applies the In predicate on the "window_7d_start" field.
|
||||
func Window7dStartIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIn(FieldWindow7dStart, vs...))
|
||||
}
|
||||
|
||||
// Window7dStartNotIn applies the NotIn predicate on the "window_7d_start" field.
|
||||
func Window7dStartNotIn(vs ...time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotIn(FieldWindow7dStart, vs...))
|
||||
}
|
||||
|
||||
// Window7dStartGT applies the GT predicate on the "window_7d_start" field.
|
||||
func Window7dStartGT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGT(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartGTE applies the GTE predicate on the "window_7d_start" field.
|
||||
func Window7dStartGTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldGTE(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartLT applies the LT predicate on the "window_7d_start" field.
|
||||
func Window7dStartLT(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLT(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartLTE applies the LTE predicate on the "window_7d_start" field.
|
||||
func Window7dStartLTE(v time.Time) predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldLTE(FieldWindow7dStart, v))
|
||||
}
|
||||
|
||||
// Window7dStartIsNil applies the IsNil predicate on the "window_7d_start" field.
|
||||
func Window7dStartIsNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldIsNull(FieldWindow7dStart))
|
||||
}
|
||||
|
||||
// Window7dStartNotNil applies the NotNil predicate on the "window_7d_start" field.
|
||||
func Window7dStartNotNil() predicate.APIKey {
|
||||
return predicate.APIKey(sql.FieldNotNull(FieldWindow7dStart))
|
||||
}
|
||||
|
||||
// HasUser applies the HasEdge predicate on the "user" edge.
|
||||
func HasUser() predicate.APIKey {
|
||||
return predicate.APIKey(func(s *sql.Selector) {
|
||||
|
||||
@@ -181,6 +181,132 @@ func (_c *APIKeyCreate) SetNillableExpiresAt(v *time.Time) *APIKeyCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (_c *APIKeyCreate) SetRateLimit5h(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetRateLimit5h(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableRateLimit5h(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetRateLimit5h(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (_c *APIKeyCreate) SetRateLimit1d(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetRateLimit1d(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableRateLimit1d(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetRateLimit1d(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (_c *APIKeyCreate) SetRateLimit7d(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetRateLimit7d(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableRateLimit7d(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetRateLimit7d(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (_c *APIKeyCreate) SetUsage5h(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetUsage5h(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableUsage5h(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetUsage5h(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (_c *APIKeyCreate) SetUsage1d(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetUsage1d(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableUsage1d(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetUsage1d(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (_c *APIKeyCreate) SetUsage7d(v float64) *APIKeyCreate {
|
||||
_c.mutation.SetUsage7d(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableUsage7d(v *float64) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetUsage7d(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (_c *APIKeyCreate) SetWindow5hStart(v time.Time) *APIKeyCreate {
|
||||
_c.mutation.SetWindow5hStart(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableWindow5hStart(v *time.Time) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetWindow5hStart(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (_c *APIKeyCreate) SetWindow1dStart(v time.Time) *APIKeyCreate {
|
||||
_c.mutation.SetWindow1dStart(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableWindow1dStart(v *time.Time) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetWindow1dStart(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (_c *APIKeyCreate) SetWindow7dStart(v time.Time) *APIKeyCreate {
|
||||
_c.mutation.SetWindow7dStart(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
|
||||
func (_c *APIKeyCreate) SetNillableWindow7dStart(v *time.Time) *APIKeyCreate {
|
||||
if v != nil {
|
||||
_c.SetWindow7dStart(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUser sets the "user" edge to the User entity.
|
||||
func (_c *APIKeyCreate) SetUser(v *User) *APIKeyCreate {
|
||||
return _c.SetUserID(v.ID)
|
||||
@@ -269,6 +395,30 @@ func (_c *APIKeyCreate) defaults() error {
|
||||
v := apikey.DefaultQuotaUsed
|
||||
_c.mutation.SetQuotaUsed(v)
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit5h(); !ok {
|
||||
v := apikey.DefaultRateLimit5h
|
||||
_c.mutation.SetRateLimit5h(v)
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit1d(); !ok {
|
||||
v := apikey.DefaultRateLimit1d
|
||||
_c.mutation.SetRateLimit1d(v)
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit7d(); !ok {
|
||||
v := apikey.DefaultRateLimit7d
|
||||
_c.mutation.SetRateLimit7d(v)
|
||||
}
|
||||
if _, ok := _c.mutation.Usage5h(); !ok {
|
||||
v := apikey.DefaultUsage5h
|
||||
_c.mutation.SetUsage5h(v)
|
||||
}
|
||||
if _, ok := _c.mutation.Usage1d(); !ok {
|
||||
v := apikey.DefaultUsage1d
|
||||
_c.mutation.SetUsage1d(v)
|
||||
}
|
||||
if _, ok := _c.mutation.Usage7d(); !ok {
|
||||
v := apikey.DefaultUsage7d
|
||||
_c.mutation.SetUsage7d(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -313,6 +463,24 @@ func (_c *APIKeyCreate) check() error {
|
||||
if _, ok := _c.mutation.QuotaUsed(); !ok {
|
||||
return &ValidationError{Name: "quota_used", err: errors.New(`ent: missing required field "APIKey.quota_used"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit5h(); !ok {
|
||||
return &ValidationError{Name: "rate_limit_5h", err: errors.New(`ent: missing required field "APIKey.rate_limit_5h"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit1d(); !ok {
|
||||
return &ValidationError{Name: "rate_limit_1d", err: errors.New(`ent: missing required field "APIKey.rate_limit_1d"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.RateLimit7d(); !ok {
|
||||
return &ValidationError{Name: "rate_limit_7d", err: errors.New(`ent: missing required field "APIKey.rate_limit_7d"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Usage5h(); !ok {
|
||||
return &ValidationError{Name: "usage_5h", err: errors.New(`ent: missing required field "APIKey.usage_5h"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Usage1d(); !ok {
|
||||
return &ValidationError{Name: "usage_1d", err: errors.New(`ent: missing required field "APIKey.usage_1d"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Usage7d(); !ok {
|
||||
return &ValidationError{Name: "usage_7d", err: errors.New(`ent: missing required field "APIKey.usage_7d"`)}
|
||||
}
|
||||
if len(_c.mutation.UserIDs()) == 0 {
|
||||
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "APIKey.user"`)}
|
||||
}
|
||||
@@ -391,6 +559,42 @@ func (_c *APIKeyCreate) createSpec() (*APIKey, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value)
|
||||
_node.ExpiresAt = &value
|
||||
}
|
||||
if value, ok := _c.mutation.RateLimit5h(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
|
||||
_node.RateLimit5h = value
|
||||
}
|
||||
if value, ok := _c.mutation.RateLimit1d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
|
||||
_node.RateLimit1d = value
|
||||
}
|
||||
if value, ok := _c.mutation.RateLimit7d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
|
||||
_node.RateLimit7d = value
|
||||
}
|
||||
if value, ok := _c.mutation.Usage5h(); ok {
|
||||
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
|
||||
_node.Usage5h = value
|
||||
}
|
||||
if value, ok := _c.mutation.Usage1d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
|
||||
_node.Usage1d = value
|
||||
}
|
||||
if value, ok := _c.mutation.Usage7d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
|
||||
_node.Usage7d = value
|
||||
}
|
||||
if value, ok := _c.mutation.Window5hStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
|
||||
_node.Window5hStart = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Window1dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
|
||||
_node.Window1dStart = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Window7dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
|
||||
_node.Window7dStart = &value
|
||||
}
|
||||
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
@@ -697,6 +901,168 @@ func (u *APIKeyUpsert) ClearExpiresAt() *APIKeyUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsert) SetRateLimit5h(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldRateLimit5h, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateRateLimit5h() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldRateLimit5h)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddRateLimit5h adds v to the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsert) AddRateLimit5h(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldRateLimit5h, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsert) SetRateLimit1d(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldRateLimit1d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateRateLimit1d() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldRateLimit1d)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddRateLimit1d adds v to the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsert) AddRateLimit1d(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldRateLimit1d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsert) SetRateLimit7d(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldRateLimit7d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateRateLimit7d() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldRateLimit7d)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddRateLimit7d adds v to the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsert) AddRateLimit7d(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldRateLimit7d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (u *APIKeyUpsert) SetUsage5h(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldUsage5h, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateUsage5h() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldUsage5h)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddUsage5h adds v to the "usage_5h" field.
|
||||
func (u *APIKeyUpsert) AddUsage5h(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldUsage5h, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (u *APIKeyUpsert) SetUsage1d(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldUsage1d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateUsage1d() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldUsage1d)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddUsage1d adds v to the "usage_1d" field.
|
||||
func (u *APIKeyUpsert) AddUsage1d(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldUsage1d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (u *APIKeyUpsert) SetUsage7d(v float64) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldUsage7d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateUsage7d() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldUsage7d)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddUsage7d adds v to the "usage_7d" field.
|
||||
func (u *APIKeyUpsert) AddUsage7d(v float64) *APIKeyUpsert {
|
||||
u.Add(apikey.FieldUsage7d, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (u *APIKeyUpsert) SetWindow5hStart(v time.Time) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldWindow5hStart, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateWindow5hStart() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldWindow5hStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearWindow5hStart clears the value of the "window_5h_start" field.
|
||||
func (u *APIKeyUpsert) ClearWindow5hStart() *APIKeyUpsert {
|
||||
u.SetNull(apikey.FieldWindow5hStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (u *APIKeyUpsert) SetWindow1dStart(v time.Time) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldWindow1dStart, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateWindow1dStart() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldWindow1dStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearWindow1dStart clears the value of the "window_1d_start" field.
|
||||
func (u *APIKeyUpsert) ClearWindow1dStart() *APIKeyUpsert {
|
||||
u.SetNull(apikey.FieldWindow1dStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (u *APIKeyUpsert) SetWindow7dStart(v time.Time) *APIKeyUpsert {
|
||||
u.Set(apikey.FieldWindow7dStart, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsert) UpdateWindow7dStart() *APIKeyUpsert {
|
||||
u.SetExcluded(apikey.FieldWindow7dStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearWindow7dStart clears the value of the "window_7d_start" field.
|
||||
func (u *APIKeyUpsert) ClearWindow7dStart() *APIKeyUpsert {
|
||||
u.SetNull(apikey.FieldWindow7dStart)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
@@ -980,6 +1346,195 @@ func (u *APIKeyUpsertOne) ClearExpiresAt() *APIKeyUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsertOne) SetRateLimit5h(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit5h adds v to the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsertOne) AddRateLimit5h(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateRateLimit5h() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit5h()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsertOne) SetRateLimit1d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit1d adds v to the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsertOne) AddRateLimit1d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateRateLimit1d() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit1d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsertOne) SetRateLimit7d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit7d adds v to the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsertOne) AddRateLimit7d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateRateLimit7d() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit7d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (u *APIKeyUpsertOne) SetUsage5h(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage5h adds v to the "usage_5h" field.
|
||||
func (u *APIKeyUpsertOne) AddUsage5h(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateUsage5h() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage5h()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (u *APIKeyUpsertOne) SetUsage1d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage1d adds v to the "usage_1d" field.
|
||||
func (u *APIKeyUpsertOne) AddUsage1d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateUsage1d() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage1d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (u *APIKeyUpsertOne) SetUsage7d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage7d adds v to the "usage_7d" field.
|
||||
func (u *APIKeyUpsertOne) AddUsage7d(v float64) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateUsage7d() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage7d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (u *APIKeyUpsertOne) SetWindow5hStart(v time.Time) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow5hStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateWindow5hStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow5hStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow5hStart clears the value of the "window_5h_start" field.
|
||||
func (u *APIKeyUpsertOne) ClearWindow5hStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow5hStart()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (u *APIKeyUpsertOne) SetWindow1dStart(v time.Time) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow1dStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateWindow1dStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow1dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow1dStart clears the value of the "window_1d_start" field.
|
||||
func (u *APIKeyUpsertOne) ClearWindow1dStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow1dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (u *APIKeyUpsertOne) SetWindow7dStart(v time.Time) *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow7dStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertOne) UpdateWindow7dStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow7dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow7dStart clears the value of the "window_7d_start" field.
|
||||
func (u *APIKeyUpsertOne) ClearWindow7dStart() *APIKeyUpsertOne {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow7dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *APIKeyUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
@@ -1429,6 +1984,195 @@ func (u *APIKeyUpsertBulk) ClearExpiresAt() *APIKeyUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsertBulk) SetRateLimit5h(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit5h adds v to the "rate_limit_5h" field.
|
||||
func (u *APIKeyUpsertBulk) AddRateLimit5h(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateRateLimit5h() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit5h()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsertBulk) SetRateLimit1d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit1d adds v to the "rate_limit_1d" field.
|
||||
func (u *APIKeyUpsertBulk) AddRateLimit1d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateRateLimit1d() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit1d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsertBulk) SetRateLimit7d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetRateLimit7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddRateLimit7d adds v to the "rate_limit_7d" field.
|
||||
func (u *APIKeyUpsertBulk) AddRateLimit7d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddRateLimit7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateRateLimit7d() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateRateLimit7d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (u *APIKeyUpsertBulk) SetUsage5h(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage5h adds v to the "usage_5h" field.
|
||||
func (u *APIKeyUpsertBulk) AddUsage5h(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage5h(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateUsage5h() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage5h()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (u *APIKeyUpsertBulk) SetUsage1d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage1d adds v to the "usage_1d" field.
|
||||
func (u *APIKeyUpsertBulk) AddUsage1d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage1d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateUsage1d() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage1d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (u *APIKeyUpsertBulk) SetUsage7d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetUsage7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddUsage7d adds v to the "usage_7d" field.
|
||||
func (u *APIKeyUpsertBulk) AddUsage7d(v float64) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.AddUsage7d(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateUsage7d() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateUsage7d()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (u *APIKeyUpsertBulk) SetWindow5hStart(v time.Time) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow5hStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateWindow5hStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow5hStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow5hStart clears the value of the "window_5h_start" field.
|
||||
func (u *APIKeyUpsertBulk) ClearWindow5hStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow5hStart()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (u *APIKeyUpsertBulk) SetWindow1dStart(v time.Time) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow1dStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateWindow1dStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow1dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow1dStart clears the value of the "window_1d_start" field.
|
||||
func (u *APIKeyUpsertBulk) ClearWindow1dStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow1dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (u *APIKeyUpsertBulk) SetWindow7dStart(v time.Time) *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.SetWindow7dStart(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
|
||||
func (u *APIKeyUpsertBulk) UpdateWindow7dStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.UpdateWindow7dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearWindow7dStart clears the value of the "window_7d_start" field.
|
||||
func (u *APIKeyUpsertBulk) ClearWindow7dStart() *APIKeyUpsertBulk {
|
||||
return u.Update(func(s *APIKeyUpsert) {
|
||||
s.ClearWindow7dStart()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *APIKeyUpsertBulk) Exec(ctx context.Context) error {
|
||||
if u.create.err != nil {
|
||||
|
||||
@@ -252,6 +252,192 @@ func (_u *APIKeyUpdate) ClearExpiresAt() *APIKeyUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (_u *APIKeyUpdate) SetRateLimit5h(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetRateLimit5h()
|
||||
_u.mutation.SetRateLimit5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableRateLimit5h(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetRateLimit5h(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit5h adds value to the "rate_limit_5h" field.
|
||||
func (_u *APIKeyUpdate) AddRateLimit5h(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddRateLimit5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (_u *APIKeyUpdate) SetRateLimit1d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetRateLimit1d()
|
||||
_u.mutation.SetRateLimit1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableRateLimit1d(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetRateLimit1d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit1d adds value to the "rate_limit_1d" field.
|
||||
func (_u *APIKeyUpdate) AddRateLimit1d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddRateLimit1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (_u *APIKeyUpdate) SetRateLimit7d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetRateLimit7d()
|
||||
_u.mutation.SetRateLimit7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableRateLimit7d(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetRateLimit7d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit7d adds value to the "rate_limit_7d" field.
|
||||
func (_u *APIKeyUpdate) AddRateLimit7d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddRateLimit7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (_u *APIKeyUpdate) SetUsage5h(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetUsage5h()
|
||||
_u.mutation.SetUsage5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableUsage5h(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetUsage5h(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage5h adds value to the "usage_5h" field.
|
||||
func (_u *APIKeyUpdate) AddUsage5h(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddUsage5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (_u *APIKeyUpdate) SetUsage1d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetUsage1d()
|
||||
_u.mutation.SetUsage1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableUsage1d(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetUsage1d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage1d adds value to the "usage_1d" field.
|
||||
func (_u *APIKeyUpdate) AddUsage1d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddUsage1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (_u *APIKeyUpdate) SetUsage7d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.ResetUsage7d()
|
||||
_u.mutation.SetUsage7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableUsage7d(v *float64) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetUsage7d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage7d adds value to the "usage_7d" field.
|
||||
func (_u *APIKeyUpdate) AddUsage7d(v float64) *APIKeyUpdate {
|
||||
_u.mutation.AddUsage7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (_u *APIKeyUpdate) SetWindow5hStart(v time.Time) *APIKeyUpdate {
|
||||
_u.mutation.SetWindow5hStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetWindow5hStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow5hStart clears the value of the "window_5h_start" field.
|
||||
func (_u *APIKeyUpdate) ClearWindow5hStart() *APIKeyUpdate {
|
||||
_u.mutation.ClearWindow5hStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (_u *APIKeyUpdate) SetWindow1dStart(v time.Time) *APIKeyUpdate {
|
||||
_u.mutation.SetWindow1dStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetWindow1dStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow1dStart clears the value of the "window_1d_start" field.
|
||||
func (_u *APIKeyUpdate) ClearWindow1dStart() *APIKeyUpdate {
|
||||
_u.mutation.ClearWindow1dStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (_u *APIKeyUpdate) SetWindow7dStart(v time.Time) *APIKeyUpdate {
|
||||
_u.mutation.SetWindow7dStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdate) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdate {
|
||||
if v != nil {
|
||||
_u.SetWindow7dStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow7dStart clears the value of the "window_7d_start" field.
|
||||
func (_u *APIKeyUpdate) ClearWindow7dStart() *APIKeyUpdate {
|
||||
_u.mutation.ClearWindow7dStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUser sets the "user" edge to the User entity.
|
||||
func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate {
|
||||
return _u.SetUserID(v.ID)
|
||||
@@ -456,6 +642,60 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.ExpiresAtCleared() {
|
||||
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit5h(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit1d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit7d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage5h(); ok {
|
||||
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage5h(); ok {
|
||||
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage1d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage1d(); ok {
|
||||
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage7d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage7d(); ok {
|
||||
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Window5hStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window5hStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.Window1dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window1dStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.Window7dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window7dStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
|
||||
}
|
||||
if _u.mutation.UserCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
@@ -799,6 +1039,192 @@ func (_u *APIKeyUpdateOne) ClearExpiresAt() *APIKeyUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit5h sets the "rate_limit_5h" field.
|
||||
func (_u *APIKeyUpdateOne) SetRateLimit5h(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetRateLimit5h()
|
||||
_u.mutation.SetRateLimit5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableRateLimit5h(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetRateLimit5h(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit5h adds value to the "rate_limit_5h" field.
|
||||
func (_u *APIKeyUpdateOne) AddRateLimit5h(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddRateLimit5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit1d sets the "rate_limit_1d" field.
|
||||
func (_u *APIKeyUpdateOne) SetRateLimit1d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetRateLimit1d()
|
||||
_u.mutation.SetRateLimit1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableRateLimit1d(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetRateLimit1d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit1d adds value to the "rate_limit_1d" field.
|
||||
func (_u *APIKeyUpdateOne) AddRateLimit1d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddRateLimit1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRateLimit7d sets the "rate_limit_7d" field.
|
||||
func (_u *APIKeyUpdateOne) SetRateLimit7d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetRateLimit7d()
|
||||
_u.mutation.SetRateLimit7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableRateLimit7d(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetRateLimit7d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddRateLimit7d adds value to the "rate_limit_7d" field.
|
||||
func (_u *APIKeyUpdateOne) AddRateLimit7d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddRateLimit7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage5h sets the "usage_5h" field.
|
||||
func (_u *APIKeyUpdateOne) SetUsage5h(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetUsage5h()
|
||||
_u.mutation.SetUsage5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableUsage5h(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetUsage5h(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage5h adds value to the "usage_5h" field.
|
||||
func (_u *APIKeyUpdateOne) AddUsage5h(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddUsage5h(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage1d sets the "usage_1d" field.
|
||||
func (_u *APIKeyUpdateOne) SetUsage1d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetUsage1d()
|
||||
_u.mutation.SetUsage1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableUsage1d(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetUsage1d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage1d adds value to the "usage_1d" field.
|
||||
func (_u *APIKeyUpdateOne) AddUsage1d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddUsage1d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUsage7d sets the "usage_7d" field.
|
||||
func (_u *APIKeyUpdateOne) SetUsage7d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.ResetUsage7d()
|
||||
_u.mutation.SetUsage7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableUsage7d(v *float64) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetUsage7d(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddUsage7d adds value to the "usage_7d" field.
|
||||
func (_u *APIKeyUpdateOne) AddUsage7d(v float64) *APIKeyUpdateOne {
|
||||
_u.mutation.AddUsage7d(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow5hStart sets the "window_5h_start" field.
|
||||
func (_u *APIKeyUpdateOne) SetWindow5hStart(v time.Time) *APIKeyUpdateOne {
|
||||
_u.mutation.SetWindow5hStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetWindow5hStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow5hStart clears the value of the "window_5h_start" field.
|
||||
func (_u *APIKeyUpdateOne) ClearWindow5hStart() *APIKeyUpdateOne {
|
||||
_u.mutation.ClearWindow5hStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow1dStart sets the "window_1d_start" field.
|
||||
func (_u *APIKeyUpdateOne) SetWindow1dStart(v time.Time) *APIKeyUpdateOne {
|
||||
_u.mutation.SetWindow1dStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetWindow1dStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow1dStart clears the value of the "window_1d_start" field.
|
||||
func (_u *APIKeyUpdateOne) ClearWindow1dStart() *APIKeyUpdateOne {
|
||||
_u.mutation.ClearWindow1dStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetWindow7dStart sets the "window_7d_start" field.
|
||||
func (_u *APIKeyUpdateOne) SetWindow7dStart(v time.Time) *APIKeyUpdateOne {
|
||||
_u.mutation.SetWindow7dStart(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
|
||||
func (_u *APIKeyUpdateOne) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetWindow7dStart(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearWindow7dStart clears the value of the "window_7d_start" field.
|
||||
func (_u *APIKeyUpdateOne) ClearWindow7dStart() *APIKeyUpdateOne {
|
||||
_u.mutation.ClearWindow7dStart()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUser sets the "user" edge to the User entity.
|
||||
func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne {
|
||||
return _u.SetUserID(v.ID)
|
||||
@@ -1033,6 +1459,60 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
|
||||
if _u.mutation.ExpiresAtCleared() {
|
||||
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit5h(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit1d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RateLimit7d(); ok {
|
||||
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
|
||||
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage5h(); ok {
|
||||
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage5h(); ok {
|
||||
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage1d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage1d(); ok {
|
||||
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Usage7d(); ok {
|
||||
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedUsage7d(); ok {
|
||||
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Window5hStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window5hStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.Window1dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window1dStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.Window7dStart(); ok {
|
||||
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.Window7dStartCleared() {
|
||||
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
|
||||
}
|
||||
if _u.mutation.UserCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
@@ -58,6 +59,8 @@ type Client struct {
|
||||
ErrorPassthroughRule *ErrorPassthroughRuleClient
|
||||
// Group is the client for interacting with the Group builders.
|
||||
Group *GroupClient
|
||||
// IdempotencyRecord is the client for interacting with the IdempotencyRecord builders.
|
||||
IdempotencyRecord *IdempotencyRecordClient
|
||||
// PromoCode is the client for interacting with the PromoCode builders.
|
||||
PromoCode *PromoCodeClient
|
||||
// PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
|
||||
@@ -102,6 +105,7 @@ func (c *Client) init() {
|
||||
c.AnnouncementRead = NewAnnouncementReadClient(c.config)
|
||||
c.ErrorPassthroughRule = NewErrorPassthroughRuleClient(c.config)
|
||||
c.Group = NewGroupClient(c.config)
|
||||
c.IdempotencyRecord = NewIdempotencyRecordClient(c.config)
|
||||
c.PromoCode = NewPromoCodeClient(c.config)
|
||||
c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
|
||||
c.Proxy = NewProxyClient(c.config)
|
||||
@@ -214,6 +218,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
|
||||
Group: NewGroupClient(cfg),
|
||||
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
|
||||
PromoCode: NewPromoCodeClient(cfg),
|
||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||
Proxy: NewProxyClient(cfg),
|
||||
@@ -253,6 +258,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
|
||||
Group: NewGroupClient(cfg),
|
||||
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
|
||||
PromoCode: NewPromoCodeClient(cfg),
|
||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||
Proxy: NewProxyClient(cfg),
|
||||
@@ -296,10 +302,10 @@ func (c *Client) Close() error {
|
||||
func (c *Client) Use(hooks ...Hook) {
|
||||
for _, n := range []interface{ Use(...Hook) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.ErrorPassthroughRule, c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy,
|
||||
c.RedeemCode, c.SecuritySecret, c.Setting, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
||||
} {
|
||||
n.Use(hooks...)
|
||||
}
|
||||
@@ -310,10 +316,10 @@ func (c *Client) Use(hooks ...Hook) {
|
||||
func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||
for _, n := range []interface{ Intercept(...Interceptor) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.ErrorPassthroughRule, c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy,
|
||||
c.RedeemCode, c.SecuritySecret, c.Setting, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
||||
} {
|
||||
n.Intercept(interceptors...)
|
||||
}
|
||||
@@ -336,6 +342,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return c.ErrorPassthroughRule.mutate(ctx, m)
|
||||
case *GroupMutation:
|
||||
return c.Group.mutate(ctx, m)
|
||||
case *IdempotencyRecordMutation:
|
||||
return c.IdempotencyRecord.mutate(ctx, m)
|
||||
case *PromoCodeMutation:
|
||||
return c.PromoCode.mutate(ctx, m)
|
||||
case *PromoCodeUsageMutation:
|
||||
@@ -1575,6 +1583,139 @@ func (c *GroupClient) mutate(ctx context.Context, m *GroupMutation) (Value, erro
|
||||
}
|
||||
}
|
||||
|
||||
// IdempotencyRecordClient is a client for the IdempotencyRecord schema.
|
||||
type IdempotencyRecordClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewIdempotencyRecordClient returns a client for the IdempotencyRecord from the given config.
|
||||
func NewIdempotencyRecordClient(c config) *IdempotencyRecordClient {
|
||||
return &IdempotencyRecordClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `idempotencyrecord.Hooks(f(g(h())))`.
|
||||
func (c *IdempotencyRecordClient) Use(hooks ...Hook) {
|
||||
c.hooks.IdempotencyRecord = append(c.hooks.IdempotencyRecord, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `idempotencyrecord.Intercept(f(g(h())))`.
|
||||
func (c *IdempotencyRecordClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.IdempotencyRecord = append(c.inters.IdempotencyRecord, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a IdempotencyRecord entity.
|
||||
func (c *IdempotencyRecordClient) Create() *IdempotencyRecordCreate {
|
||||
mutation := newIdempotencyRecordMutation(c.config, OpCreate)
|
||||
return &IdempotencyRecordCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of IdempotencyRecord entities.
|
||||
func (c *IdempotencyRecordClient) CreateBulk(builders ...*IdempotencyRecordCreate) *IdempotencyRecordCreateBulk {
|
||||
return &IdempotencyRecordCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||
// a builder and applies setFunc on it.
|
||||
func (c *IdempotencyRecordClient) MapCreateBulk(slice any, setFunc func(*IdempotencyRecordCreate, int)) *IdempotencyRecordCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &IdempotencyRecordCreateBulk{err: fmt.Errorf("calling to IdempotencyRecordClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*IdempotencyRecordCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &IdempotencyRecordCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for IdempotencyRecord.
|
||||
func (c *IdempotencyRecordClient) Update() *IdempotencyRecordUpdate {
|
||||
mutation := newIdempotencyRecordMutation(c.config, OpUpdate)
|
||||
return &IdempotencyRecordUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *IdempotencyRecordClient) UpdateOne(_m *IdempotencyRecord) *IdempotencyRecordUpdateOne {
|
||||
mutation := newIdempotencyRecordMutation(c.config, OpUpdateOne, withIdempotencyRecord(_m))
|
||||
return &IdempotencyRecordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *IdempotencyRecordClient) UpdateOneID(id int64) *IdempotencyRecordUpdateOne {
|
||||
mutation := newIdempotencyRecordMutation(c.config, OpUpdateOne, withIdempotencyRecordID(id))
|
||||
return &IdempotencyRecordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for IdempotencyRecord.
|
||||
func (c *IdempotencyRecordClient) Delete() *IdempotencyRecordDelete {
|
||||
mutation := newIdempotencyRecordMutation(c.config, OpDelete)
|
||||
return &IdempotencyRecordDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *IdempotencyRecordClient) DeleteOne(_m *IdempotencyRecord) *IdempotencyRecordDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *IdempotencyRecordClient) DeleteOneID(id int64) *IdempotencyRecordDeleteOne {
|
||||
builder := c.Delete().Where(idempotencyrecord.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &IdempotencyRecordDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for IdempotencyRecord.
|
||||
func (c *IdempotencyRecordClient) Query() *IdempotencyRecordQuery {
|
||||
return &IdempotencyRecordQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeIdempotencyRecord},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a IdempotencyRecord entity by its id.
|
||||
func (c *IdempotencyRecordClient) Get(ctx context.Context, id int64) (*IdempotencyRecord, error) {
|
||||
return c.Query().Where(idempotencyrecord.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *IdempotencyRecordClient) GetX(ctx context.Context, id int64) *IdempotencyRecord {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *IdempotencyRecordClient) Hooks() []Hook {
|
||||
return c.hooks.IdempotencyRecord
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *IdempotencyRecordClient) Interceptors() []Interceptor {
|
||||
return c.inters.IdempotencyRecord
|
||||
}
|
||||
|
||||
func (c *IdempotencyRecordClient) mutate(ctx context.Context, m *IdempotencyRecordMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&IdempotencyRecordCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&IdempotencyRecordUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&IdempotencyRecordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&IdempotencyRecordDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown IdempotencyRecord mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// PromoCodeClient is a client for the PromoCode schema.
|
||||
type PromoCodeClient struct {
|
||||
config
|
||||
@@ -3747,15 +3888,17 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
|
||||
type (
|
||||
hooks struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||
ErrorPassthroughRule, Group, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
|
||||
SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Hook
|
||||
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
||||
Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User,
|
||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||
UserSubscription []ent.Hook
|
||||
}
|
||||
inters struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||
ErrorPassthroughRule, Group, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
|
||||
SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Interceptor
|
||||
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
||||
Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User,
|
||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||
UserSubscription []ent.Interceptor
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
@@ -99,6 +100,7 @@ func checkColumn(t, c string) error {
|
||||
announcementread.Table: announcementread.ValidColumn,
|
||||
errorpassthroughrule.Table: errorpassthroughrule.ValidColumn,
|
||||
group.Table: group.ValidColumn,
|
||||
idempotencyrecord.Table: idempotencyrecord.ValidColumn,
|
||||
promocode.Table: promocode.ValidColumn,
|
||||
promocodeusage.Table: promocodeusage.ValidColumn,
|
||||
proxy.Table: proxy.ValidColumn,
|
||||
|
||||
@@ -60,6 +60,8 @@ type Group struct {
|
||||
SoraVideoPricePerRequest *float64 `json:"sora_video_price_per_request,omitempty"`
|
||||
// SoraVideoPricePerRequestHd holds the value of the "sora_video_price_per_request_hd" field.
|
||||
SoraVideoPricePerRequestHd *float64 `json:"sora_video_price_per_request_hd,omitempty"`
|
||||
// SoraStorageQuotaBytes holds the value of the "sora_storage_quota_bytes" field.
|
||||
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes,omitempty"`
|
||||
// 是否仅允许 Claude Code 客户端
|
||||
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
|
||||
// 非 Claude Code 请求降级使用的分组 ID
|
||||
@@ -188,7 +190,7 @@ func (*Group) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullBool)
|
||||
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldSoraImagePrice360, group.FieldSoraImagePrice540, group.FieldSoraVideoPricePerRequest, group.FieldSoraVideoPricePerRequestHd:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder:
|
||||
case group.FieldID, group.FieldDefaultValidityDays, group.FieldSoraStorageQuotaBytes, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType:
|
||||
values[i] = new(sql.NullString)
|
||||
@@ -353,6 +355,12 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
||||
_m.SoraVideoPricePerRequestHd = new(float64)
|
||||
*_m.SoraVideoPricePerRequestHd = value.Float64
|
||||
}
|
||||
case group.FieldSoraStorageQuotaBytes:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field sora_storage_quota_bytes", values[i])
|
||||
} else if value.Valid {
|
||||
_m.SoraStorageQuotaBytes = value.Int64
|
||||
}
|
||||
case group.FieldClaudeCodeOnly:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field claude_code_only", values[i])
|
||||
@@ -570,6 +578,9 @@ func (_m *Group) String() string {
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("sora_storage_quota_bytes=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.SoraStorageQuotaBytes))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("claude_code_only=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ClaudeCodeOnly))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -57,6 +57,8 @@ const (
|
||||
FieldSoraVideoPricePerRequest = "sora_video_price_per_request"
|
||||
// FieldSoraVideoPricePerRequestHd holds the string denoting the sora_video_price_per_request_hd field in the database.
|
||||
FieldSoraVideoPricePerRequestHd = "sora_video_price_per_request_hd"
|
||||
// FieldSoraStorageQuotaBytes holds the string denoting the sora_storage_quota_bytes field in the database.
|
||||
FieldSoraStorageQuotaBytes = "sora_storage_quota_bytes"
|
||||
// FieldClaudeCodeOnly holds the string denoting the claude_code_only field in the database.
|
||||
FieldClaudeCodeOnly = "claude_code_only"
|
||||
// FieldFallbackGroupID holds the string denoting the fallback_group_id field in the database.
|
||||
@@ -169,6 +171,7 @@ var Columns = []string{
|
||||
FieldSoraImagePrice540,
|
||||
FieldSoraVideoPricePerRequest,
|
||||
FieldSoraVideoPricePerRequestHd,
|
||||
FieldSoraStorageQuotaBytes,
|
||||
FieldClaudeCodeOnly,
|
||||
FieldFallbackGroupID,
|
||||
FieldFallbackGroupIDOnInvalidRequest,
|
||||
@@ -232,6 +235,8 @@ var (
|
||||
SubscriptionTypeValidator func(string) error
|
||||
// DefaultDefaultValidityDays holds the default value on creation for the "default_validity_days" field.
|
||||
DefaultDefaultValidityDays int
|
||||
// DefaultSoraStorageQuotaBytes holds the default value on creation for the "sora_storage_quota_bytes" field.
|
||||
DefaultSoraStorageQuotaBytes int64
|
||||
// DefaultClaudeCodeOnly holds the default value on creation for the "claude_code_only" field.
|
||||
DefaultClaudeCodeOnly bool
|
||||
// DefaultModelRoutingEnabled holds the default value on creation for the "model_routing_enabled" field.
|
||||
@@ -357,6 +362,11 @@ func BySoraVideoPricePerRequestHd(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSoraVideoPricePerRequestHd, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySoraStorageQuotaBytes orders the results by the sora_storage_quota_bytes field.
|
||||
func BySoraStorageQuotaBytes(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSoraStorageQuotaBytes, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByClaudeCodeOnly orders the results by the claude_code_only field.
|
||||
func ByClaudeCodeOnly(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldClaudeCodeOnly, opts...).ToFunc()
|
||||
|
||||
@@ -160,6 +160,11 @@ func SoraVideoPricePerRequestHd(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldSoraVideoPricePerRequestHd, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytes applies equality check predicate on the "sora_storage_quota_bytes" field. It's identical to SoraStorageQuotaBytesEQ.
|
||||
func SoraStorageQuotaBytes(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// ClaudeCodeOnly applies equality check predicate on the "claude_code_only" field. It's identical to ClaudeCodeOnlyEQ.
|
||||
func ClaudeCodeOnly(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
|
||||
@@ -1245,6 +1250,46 @@ func SoraVideoPricePerRequestHdNotNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldNotNull(FieldSoraVideoPricePerRequestHd))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesEQ applies the EQ predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesEQ(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesNEQ applies the NEQ predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesNEQ(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesIn applies the In predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesIn(vs ...int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldIn(FieldSoraStorageQuotaBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesNotIn applies the NotIn predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesNotIn(vs ...int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNotIn(FieldSoraStorageQuotaBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesGT applies the GT predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesGT(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGT(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesGTE applies the GTE predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesGTE(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGTE(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesLT applies the LT predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesLT(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLT(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesLTE applies the LTE predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesLTE(v int64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// ClaudeCodeOnlyEQ applies the EQ predicate on the "claude_code_only" field.
|
||||
func ClaudeCodeOnlyEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
|
||||
|
||||
@@ -314,6 +314,20 @@ func (_c *GroupCreate) SetNillableSoraVideoPricePerRequestHd(v *float64) *GroupC
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_c *GroupCreate) SetSoraStorageQuotaBytes(v int64) *GroupCreate {
|
||||
_c.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableSoraStorageQuotaBytes(v *int64) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_c *GroupCreate) SetClaudeCodeOnly(v bool) *GroupCreate {
|
||||
_c.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -575,6 +589,10 @@ func (_c *GroupCreate) defaults() error {
|
||||
v := group.DefaultDefaultValidityDays
|
||||
_c.mutation.SetDefaultValidityDays(v)
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageQuotaBytes(); !ok {
|
||||
v := group.DefaultSoraStorageQuotaBytes
|
||||
_c.mutation.SetSoraStorageQuotaBytes(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
||||
v := group.DefaultClaudeCodeOnly
|
||||
_c.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -647,6 +665,9 @@ func (_c *GroupCreate) check() error {
|
||||
if _, ok := _c.mutation.DefaultValidityDays(); !ok {
|
||||
return &ValidationError{Name: "default_validity_days", err: errors.New(`ent: missing required field "Group.default_validity_days"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageQuotaBytes(); !ok {
|
||||
return &ValidationError{Name: "sora_storage_quota_bytes", err: errors.New(`ent: missing required field "Group.sora_storage_quota_bytes"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
||||
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
|
||||
}
|
||||
@@ -773,6 +794,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(group.FieldSoraVideoPricePerRequestHd, field.TypeFloat64, value)
|
||||
_node.SoraVideoPricePerRequestHd = &value
|
||||
}
|
||||
if value, ok := _c.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(group.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
_node.SoraStorageQuotaBytes = value
|
||||
}
|
||||
if value, ok := _c.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
_node.ClaudeCodeOnly = value
|
||||
@@ -1345,6 +1370,24 @@ func (u *GroupUpsert) ClearSoraVideoPricePerRequestHd() *GroupUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsert) SetSoraStorageQuotaBytes(v int64) *GroupUpsert {
|
||||
u.Set(group.FieldSoraStorageQuotaBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateSoraStorageQuotaBytes() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldSoraStorageQuotaBytes)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsert) AddSoraStorageQuotaBytes(v int64) *GroupUpsert {
|
||||
u.Add(group.FieldSoraStorageQuotaBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsert) SetClaudeCodeOnly(v bool) *GroupUpsert {
|
||||
u.Set(group.FieldClaudeCodeOnly, v)
|
||||
@@ -1970,6 +2013,27 @@ func (u *GroupUpsertOne) ClearSoraVideoPricePerRequestHd() *GroupUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsertOne) SetSoraStorageQuotaBytes(v int64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsertOne) AddSoraStorageQuotaBytes(v int64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateSoraStorageQuotaBytes() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateSoraStorageQuotaBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsertOne) SetClaudeCodeOnly(v bool) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
@@ -2783,6 +2847,27 @@ func (u *GroupUpsertBulk) ClearSoraVideoPricePerRequestHd() *GroupUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsertBulk) SetSoraStorageQuotaBytes(v int64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *GroupUpsertBulk) AddSoraStorageQuotaBytes(v int64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateSoraStorageQuotaBytes() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateSoraStorageQuotaBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsertBulk) SetClaudeCodeOnly(v bool) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
|
||||
@@ -463,6 +463,27 @@ func (_u *GroupUpdate) ClearSoraVideoPricePerRequestHd() *GroupUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_u *GroupUpdate) SetSoraStorageQuotaBytes(v int64) *GroupUpdate {
|
||||
_u.mutation.ResetSoraStorageQuotaBytes()
|
||||
_u.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableSoraStorageQuotaBytes(v *int64) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds value to the "sora_storage_quota_bytes" field.
|
||||
func (_u *GroupUpdate) AddSoraStorageQuotaBytes(v int64) *GroupUpdate {
|
||||
_u.mutation.AddSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_u *GroupUpdate) SetClaudeCodeOnly(v bool) *GroupUpdate {
|
||||
_u.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -1036,6 +1057,12 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.SoraVideoPricePerRequestHdCleared() {
|
||||
_spec.ClearField(group.FieldSoraVideoPricePerRequestHd, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(group.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageQuotaBytes(); ok {
|
||||
_spec.AddField(group.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
}
|
||||
@@ -1825,6 +1852,27 @@ func (_u *GroupUpdateOne) ClearSoraVideoPricePerRequestHd() *GroupUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_u *GroupUpdateOne) SetSoraStorageQuotaBytes(v int64) *GroupUpdateOne {
|
||||
_u.mutation.ResetSoraStorageQuotaBytes()
|
||||
_u.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableSoraStorageQuotaBytes(v *int64) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds value to the "sora_storage_quota_bytes" field.
|
||||
func (_u *GroupUpdateOne) AddSoraStorageQuotaBytes(v int64) *GroupUpdateOne {
|
||||
_u.mutation.AddSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_u *GroupUpdateOne) SetClaudeCodeOnly(v bool) *GroupUpdateOne {
|
||||
_u.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -2428,6 +2476,12 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
if _u.mutation.SoraVideoPricePerRequestHdCleared() {
|
||||
_spec.ClearField(group.FieldSoraVideoPricePerRequestHd, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(group.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageQuotaBytes(); ok {
|
||||
_spec.AddField(group.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,18 @@ func (f GroupFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.GroupMutation", m)
|
||||
}
|
||||
|
||||
// The IdempotencyRecordFunc type is an adapter to allow the use of ordinary
|
||||
// function as IdempotencyRecord mutator.
|
||||
type IdempotencyRecordFunc func(context.Context, *ent.IdempotencyRecordMutation) (ent.Value, error)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f IdempotencyRecordFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.IdempotencyRecordMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.IdempotencyRecordMutation", m)
|
||||
}
|
||||
|
||||
// The PromoCodeFunc type is an adapter to allow the use of ordinary
|
||||
// function as PromoCode mutator.
|
||||
type PromoCodeFunc func(context.Context, *ent.PromoCodeMutation) (ent.Value, error)
|
||||
|
||||
228
backend/ent/idempotencyrecord.go
Normal file
228
backend/ent/idempotencyrecord.go
Normal file
@@ -0,0 +1,228 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
)
|
||||
|
||||
// IdempotencyRecord is the model entity for the IdempotencyRecord schema.
|
||||
type IdempotencyRecord struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID int64 `json:"id,omitempty"`
|
||||
// CreatedAt holds the value of the "created_at" field.
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// UpdatedAt holds the value of the "updated_at" field.
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
// Scope holds the value of the "scope" field.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// IdempotencyKeyHash holds the value of the "idempotency_key_hash" field.
|
||||
IdempotencyKeyHash string `json:"idempotency_key_hash,omitempty"`
|
||||
// RequestFingerprint holds the value of the "request_fingerprint" field.
|
||||
RequestFingerprint string `json:"request_fingerprint,omitempty"`
|
||||
// Status holds the value of the "status" field.
|
||||
Status string `json:"status,omitempty"`
|
||||
// ResponseStatus holds the value of the "response_status" field.
|
||||
ResponseStatus *int `json:"response_status,omitempty"`
|
||||
// ResponseBody holds the value of the "response_body" field.
|
||||
ResponseBody *string `json:"response_body,omitempty"`
|
||||
// ErrorReason holds the value of the "error_reason" field.
|
||||
ErrorReason *string `json:"error_reason,omitempty"`
|
||||
// LockedUntil holds the value of the "locked_until" field.
|
||||
LockedUntil *time.Time `json:"locked_until,omitempty"`
|
||||
// ExpiresAt holds the value of the "expires_at" field.
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*IdempotencyRecord) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case idempotencyrecord.FieldID, idempotencyrecord.FieldResponseStatus:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case idempotencyrecord.FieldScope, idempotencyrecord.FieldIdempotencyKeyHash, idempotencyrecord.FieldRequestFingerprint, idempotencyrecord.FieldStatus, idempotencyrecord.FieldResponseBody, idempotencyrecord.FieldErrorReason:
|
||||
values[i] = new(sql.NullString)
|
||||
case idempotencyrecord.FieldCreatedAt, idempotencyrecord.FieldUpdatedAt, idempotencyrecord.FieldLockedUntil, idempotencyrecord.FieldExpiresAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the IdempotencyRecord fields.
|
||||
func (_m *IdempotencyRecord) assignValues(columns []string, values []any) error {
|
||||
if m, n := len(values), len(columns); m < n {
|
||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||
}
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case idempotencyrecord.FieldID:
|
||||
value, ok := values[i].(*sql.NullInt64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field id", value)
|
||||
}
|
||||
_m.ID = int64(value.Int64)
|
||||
case idempotencyrecord.FieldCreatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CreatedAt = value.Time
|
||||
}
|
||||
case idempotencyrecord.FieldUpdatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.UpdatedAt = value.Time
|
||||
}
|
||||
case idempotencyrecord.FieldScope:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field scope", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Scope = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldIdempotencyKeyHash:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field idempotency_key_hash", values[i])
|
||||
} else if value.Valid {
|
||||
_m.IdempotencyKeyHash = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldRequestFingerprint:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field request_fingerprint", values[i])
|
||||
} else if value.Valid {
|
||||
_m.RequestFingerprint = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldStatus:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field status", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Status = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldResponseStatus:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field response_status", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ResponseStatus = new(int)
|
||||
*_m.ResponseStatus = int(value.Int64)
|
||||
}
|
||||
case idempotencyrecord.FieldResponseBody:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field response_body", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ResponseBody = new(string)
|
||||
*_m.ResponseBody = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldErrorReason:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field error_reason", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ErrorReason = new(string)
|
||||
*_m.ErrorReason = value.String
|
||||
}
|
||||
case idempotencyrecord.FieldLockedUntil:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field locked_until", values[i])
|
||||
} else if value.Valid {
|
||||
_m.LockedUntil = new(time.Time)
|
||||
*_m.LockedUntil = value.Time
|
||||
}
|
||||
case idempotencyrecord.FieldExpiresAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ExpiresAt = value.Time
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the IdempotencyRecord.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *IdempotencyRecord) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this IdempotencyRecord.
|
||||
// Note that you need to call IdempotencyRecord.Unwrap() before calling this method if this IdempotencyRecord
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *IdempotencyRecord) Update() *IdempotencyRecordUpdateOne {
|
||||
return NewIdempotencyRecordClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the IdempotencyRecord entity that was returned from a transaction after it was closed,
|
||||
// so that all future queries will be executed through the driver which created the transaction.
|
||||
func (_m *IdempotencyRecord) Unwrap() *IdempotencyRecord {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: IdempotencyRecord is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *IdempotencyRecord) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("IdempotencyRecord(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("created_at=")
|
||||
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("updated_at=")
|
||||
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("scope=")
|
||||
builder.WriteString(_m.Scope)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("idempotency_key_hash=")
|
||||
builder.WriteString(_m.IdempotencyKeyHash)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("request_fingerprint=")
|
||||
builder.WriteString(_m.RequestFingerprint)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("status=")
|
||||
builder.WriteString(_m.Status)
|
||||
builder.WriteString(", ")
|
||||
if v := _m.ResponseStatus; v != nil {
|
||||
builder.WriteString("response_status=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.ResponseBody; v != nil {
|
||||
builder.WriteString("response_body=")
|
||||
builder.WriteString(*v)
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.ErrorReason; v != nil {
|
||||
builder.WriteString("error_reason=")
|
||||
builder.WriteString(*v)
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.LockedUntil; v != nil {
|
||||
builder.WriteString("locked_until=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("expires_at=")
|
||||
builder.WriteString(_m.ExpiresAt.Format(time.ANSIC))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// IdempotencyRecords is a parsable slice of IdempotencyRecord.
|
||||
type IdempotencyRecords []*IdempotencyRecord
|
||||
148
backend/ent/idempotencyrecord/idempotencyrecord.go
Normal file
148
backend/ent/idempotencyrecord/idempotencyrecord.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package idempotencyrecord
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the idempotencyrecord type in the database.
|
||||
Label = "idempotency_record"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldScope holds the string denoting the scope field in the database.
|
||||
FieldScope = "scope"
|
||||
// FieldIdempotencyKeyHash holds the string denoting the idempotency_key_hash field in the database.
|
||||
FieldIdempotencyKeyHash = "idempotency_key_hash"
|
||||
// FieldRequestFingerprint holds the string denoting the request_fingerprint field in the database.
|
||||
FieldRequestFingerprint = "request_fingerprint"
|
||||
// FieldStatus holds the string denoting the status field in the database.
|
||||
FieldStatus = "status"
|
||||
// FieldResponseStatus holds the string denoting the response_status field in the database.
|
||||
FieldResponseStatus = "response_status"
|
||||
// FieldResponseBody holds the string denoting the response_body field in the database.
|
||||
FieldResponseBody = "response_body"
|
||||
// FieldErrorReason holds the string denoting the error_reason field in the database.
|
||||
FieldErrorReason = "error_reason"
|
||||
// FieldLockedUntil holds the string denoting the locked_until field in the database.
|
||||
FieldLockedUntil = "locked_until"
|
||||
// FieldExpiresAt holds the string denoting the expires_at field in the database.
|
||||
FieldExpiresAt = "expires_at"
|
||||
// Table holds the table name of the idempotencyrecord in the database.
|
||||
Table = "idempotency_records"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for idempotencyrecord fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldScope,
|
||||
FieldIdempotencyKeyHash,
|
||||
FieldRequestFingerprint,
|
||||
FieldStatus,
|
||||
FieldResponseStatus,
|
||||
FieldResponseBody,
|
||||
FieldErrorReason,
|
||||
FieldLockedUntil,
|
||||
FieldExpiresAt,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// ScopeValidator is a validator for the "scope" field. It is called by the builders before save.
|
||||
ScopeValidator func(string) error
|
||||
// IdempotencyKeyHashValidator is a validator for the "idempotency_key_hash" field. It is called by the builders before save.
|
||||
IdempotencyKeyHashValidator func(string) error
|
||||
// RequestFingerprintValidator is a validator for the "request_fingerprint" field. It is called by the builders before save.
|
||||
RequestFingerprintValidator func(string) error
|
||||
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||
StatusValidator func(string) error
|
||||
// ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save.
|
||||
ErrorReasonValidator func(string) error
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the IdempotencyRecord queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreatedAt orders the results by the created_at field.
|
||||
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUpdatedAt orders the results by the updated_at field.
|
||||
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByScope orders the results by the scope field.
|
||||
func ByScope(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldScope, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByIdempotencyKeyHash orders the results by the idempotency_key_hash field.
|
||||
func ByIdempotencyKeyHash(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIdempotencyKeyHash, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByRequestFingerprint orders the results by the request_fingerprint field.
|
||||
func ByRequestFingerprint(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldRequestFingerprint, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByStatus orders the results by the status field.
|
||||
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByResponseStatus orders the results by the response_status field.
|
||||
func ByResponseStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldResponseStatus, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByResponseBody orders the results by the response_body field.
|
||||
func ByResponseBody(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldResponseBody, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByErrorReason orders the results by the error_reason field.
|
||||
func ByErrorReason(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldErrorReason, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByLockedUntil orders the results by the locked_until field.
|
||||
func ByLockedUntil(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldLockedUntil, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByExpiresAt orders the results by the expires_at field.
|
||||
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
|
||||
}
|
||||
755
backend/ent/idempotencyrecord/where.go
Normal file
755
backend/ent/idempotencyrecord/where.go
Normal file
@@ -0,0 +1,755 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package idempotencyrecord
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id int64) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldID, id))
|
||||
}
|
||||
|
||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||
func CreatedAt(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
|
||||
func UpdatedAt(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// Scope applies equality check predicate on the "scope" field. It's identical to ScopeEQ.
|
||||
func Scope(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldScope, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHash applies equality check predicate on the "idempotency_key_hash" field. It's identical to IdempotencyKeyHashEQ.
|
||||
func IdempotencyKeyHash(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// RequestFingerprint applies equality check predicate on the "request_fingerprint" field. It's identical to RequestFingerprintEQ.
|
||||
func RequestFingerprint(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
|
||||
func Status(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatus applies equality check predicate on the "response_status" field. It's identical to ResponseStatusEQ.
|
||||
func ResponseStatus(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseBody applies equality check predicate on the "response_body" field. It's identical to ResponseBodyEQ.
|
||||
func ResponseBody(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ErrorReason applies equality check predicate on the "error_reason" field. It's identical to ErrorReasonEQ.
|
||||
func ErrorReason(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// LockedUntil applies equality check predicate on the "locked_until" field. It's identical to LockedUntilEQ.
|
||||
func LockedUntil(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
|
||||
func ExpiresAt(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||
func CreatedAtNEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||
func CreatedAtIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||
func CreatedAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||
func CreatedAtGT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||
func CreatedAtGTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||
func CreatedAtLT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||
func CreatedAtLTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||
func UpdatedAtEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||
func UpdatedAtNEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||
func UpdatedAtIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||
func UpdatedAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||
func UpdatedAtGT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||
func UpdatedAtGTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||
func UpdatedAtLT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||
func UpdatedAtLTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// ScopeEQ applies the EQ predicate on the "scope" field.
|
||||
func ScopeEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeNEQ applies the NEQ predicate on the "scope" field.
|
||||
func ScopeNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeIn applies the In predicate on the "scope" field.
|
||||
func ScopeIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldScope, vs...))
|
||||
}
|
||||
|
||||
// ScopeNotIn applies the NotIn predicate on the "scope" field.
|
||||
func ScopeNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldScope, vs...))
|
||||
}
|
||||
|
||||
// ScopeGT applies the GT predicate on the "scope" field.
|
||||
func ScopeGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeGTE applies the GTE predicate on the "scope" field.
|
||||
func ScopeGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeLT applies the LT predicate on the "scope" field.
|
||||
func ScopeLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeLTE applies the LTE predicate on the "scope" field.
|
||||
func ScopeLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeContains applies the Contains predicate on the "scope" field.
|
||||
func ScopeContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeHasPrefix applies the HasPrefix predicate on the "scope" field.
|
||||
func ScopeHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeHasSuffix applies the HasSuffix predicate on the "scope" field.
|
||||
func ScopeHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeEqualFold applies the EqualFold predicate on the "scope" field.
|
||||
func ScopeEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldScope, v))
|
||||
}
|
||||
|
||||
// ScopeContainsFold applies the ContainsFold predicate on the "scope" field.
|
||||
func ScopeContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldScope, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashEQ applies the EQ predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashNEQ applies the NEQ predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashIn applies the In predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldIdempotencyKeyHash, vs...))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashNotIn applies the NotIn predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldIdempotencyKeyHash, vs...))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashGT applies the GT predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashGTE applies the GTE predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashLT applies the LT predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashLTE applies the LTE predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashContains applies the Contains predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashHasPrefix applies the HasPrefix predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashHasSuffix applies the HasSuffix predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashEqualFold applies the EqualFold predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// IdempotencyKeyHashContainsFold applies the ContainsFold predicate on the "idempotency_key_hash" field.
|
||||
func IdempotencyKeyHashContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldIdempotencyKeyHash, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintEQ applies the EQ predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintNEQ applies the NEQ predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintIn applies the In predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldRequestFingerprint, vs...))
|
||||
}
|
||||
|
||||
// RequestFingerprintNotIn applies the NotIn predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldRequestFingerprint, vs...))
|
||||
}
|
||||
|
||||
// RequestFingerprintGT applies the GT predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintGTE applies the GTE predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintLT applies the LT predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintLTE applies the LTE predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintContains applies the Contains predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintHasPrefix applies the HasPrefix predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintHasSuffix applies the HasSuffix predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintEqualFold applies the EqualFold predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// RequestFingerprintContainsFold applies the ContainsFold predicate on the "request_fingerprint" field.
|
||||
func RequestFingerprintContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldRequestFingerprint, v))
|
||||
}
|
||||
|
||||
// StatusEQ applies the EQ predicate on the "status" field.
|
||||
func StatusEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusNEQ applies the NEQ predicate on the "status" field.
|
||||
func StatusNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusIn applies the In predicate on the "status" field.
|
||||
func StatusIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// StatusNotIn applies the NotIn predicate on the "status" field.
|
||||
func StatusNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// StatusGT applies the GT predicate on the "status" field.
|
||||
func StatusGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusGTE applies the GTE predicate on the "status" field.
|
||||
func StatusGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusLT applies the LT predicate on the "status" field.
|
||||
func StatusLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusLTE applies the LTE predicate on the "status" field.
|
||||
func StatusLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusContains applies the Contains predicate on the "status" field.
|
||||
func StatusContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
|
||||
func StatusHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
|
||||
func StatusHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusEqualFold applies the EqualFold predicate on the "status" field.
|
||||
func StatusEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusContainsFold applies the ContainsFold predicate on the "status" field.
|
||||
func StatusContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusEQ applies the EQ predicate on the "response_status" field.
|
||||
func ResponseStatusEQ(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusNEQ applies the NEQ predicate on the "response_status" field.
|
||||
func ResponseStatusNEQ(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusIn applies the In predicate on the "response_status" field.
|
||||
func ResponseStatusIn(vs ...int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldResponseStatus, vs...))
|
||||
}
|
||||
|
||||
// ResponseStatusNotIn applies the NotIn predicate on the "response_status" field.
|
||||
func ResponseStatusNotIn(vs ...int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldResponseStatus, vs...))
|
||||
}
|
||||
|
||||
// ResponseStatusGT applies the GT predicate on the "response_status" field.
|
||||
func ResponseStatusGT(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusGTE applies the GTE predicate on the "response_status" field.
|
||||
func ResponseStatusGTE(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusLT applies the LT predicate on the "response_status" field.
|
||||
func ResponseStatusLT(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusLTE applies the LTE predicate on the "response_status" field.
|
||||
func ResponseStatusLTE(v int) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldResponseStatus, v))
|
||||
}
|
||||
|
||||
// ResponseStatusIsNil applies the IsNil predicate on the "response_status" field.
|
||||
func ResponseStatusIsNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldResponseStatus))
|
||||
}
|
||||
|
||||
// ResponseStatusNotNil applies the NotNil predicate on the "response_status" field.
|
||||
func ResponseStatusNotNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldResponseStatus))
|
||||
}
|
||||
|
||||
// ResponseBodyEQ applies the EQ predicate on the "response_body" field.
|
||||
func ResponseBodyEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyNEQ applies the NEQ predicate on the "response_body" field.
|
||||
func ResponseBodyNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyIn applies the In predicate on the "response_body" field.
|
||||
func ResponseBodyIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldResponseBody, vs...))
|
||||
}
|
||||
|
||||
// ResponseBodyNotIn applies the NotIn predicate on the "response_body" field.
|
||||
func ResponseBodyNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldResponseBody, vs...))
|
||||
}
|
||||
|
||||
// ResponseBodyGT applies the GT predicate on the "response_body" field.
|
||||
func ResponseBodyGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyGTE applies the GTE predicate on the "response_body" field.
|
||||
func ResponseBodyGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyLT applies the LT predicate on the "response_body" field.
|
||||
func ResponseBodyLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyLTE applies the LTE predicate on the "response_body" field.
|
||||
func ResponseBodyLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyContains applies the Contains predicate on the "response_body" field.
|
||||
func ResponseBodyContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyHasPrefix applies the HasPrefix predicate on the "response_body" field.
|
||||
func ResponseBodyHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyHasSuffix applies the HasSuffix predicate on the "response_body" field.
|
||||
func ResponseBodyHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyIsNil applies the IsNil predicate on the "response_body" field.
|
||||
func ResponseBodyIsNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldResponseBody))
|
||||
}
|
||||
|
||||
// ResponseBodyNotNil applies the NotNil predicate on the "response_body" field.
|
||||
func ResponseBodyNotNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldResponseBody))
|
||||
}
|
||||
|
||||
// ResponseBodyEqualFold applies the EqualFold predicate on the "response_body" field.
|
||||
func ResponseBodyEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ResponseBodyContainsFold applies the ContainsFold predicate on the "response_body" field.
|
||||
func ResponseBodyContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldResponseBody, v))
|
||||
}
|
||||
|
||||
// ErrorReasonEQ applies the EQ predicate on the "error_reason" field.
|
||||
func ErrorReasonEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonNEQ applies the NEQ predicate on the "error_reason" field.
|
||||
func ErrorReasonNEQ(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonIn applies the In predicate on the "error_reason" field.
|
||||
func ErrorReasonIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldErrorReason, vs...))
|
||||
}
|
||||
|
||||
// ErrorReasonNotIn applies the NotIn predicate on the "error_reason" field.
|
||||
func ErrorReasonNotIn(vs ...string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldErrorReason, vs...))
|
||||
}
|
||||
|
||||
// ErrorReasonGT applies the GT predicate on the "error_reason" field.
|
||||
func ErrorReasonGT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonGTE applies the GTE predicate on the "error_reason" field.
|
||||
func ErrorReasonGTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonLT applies the LT predicate on the "error_reason" field.
|
||||
func ErrorReasonLT(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonLTE applies the LTE predicate on the "error_reason" field.
|
||||
func ErrorReasonLTE(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonContains applies the Contains predicate on the "error_reason" field.
|
||||
func ErrorReasonContains(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContains(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonHasPrefix applies the HasPrefix predicate on the "error_reason" field.
|
||||
func ErrorReasonHasPrefix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasPrefix(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonHasSuffix applies the HasSuffix predicate on the "error_reason" field.
|
||||
func ErrorReasonHasSuffix(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldHasSuffix(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonIsNil applies the IsNil predicate on the "error_reason" field.
|
||||
func ErrorReasonIsNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldErrorReason))
|
||||
}
|
||||
|
||||
// ErrorReasonNotNil applies the NotNil predicate on the "error_reason" field.
|
||||
func ErrorReasonNotNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldErrorReason))
|
||||
}
|
||||
|
||||
// ErrorReasonEqualFold applies the EqualFold predicate on the "error_reason" field.
|
||||
func ErrorReasonEqualFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEqualFold(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// ErrorReasonContainsFold applies the ContainsFold predicate on the "error_reason" field.
|
||||
func ErrorReasonContainsFold(v string) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldContainsFold(FieldErrorReason, v))
|
||||
}
|
||||
|
||||
// LockedUntilEQ applies the EQ predicate on the "locked_until" field.
|
||||
func LockedUntilEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilNEQ applies the NEQ predicate on the "locked_until" field.
|
||||
func LockedUntilNEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilIn applies the In predicate on the "locked_until" field.
|
||||
func LockedUntilIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldLockedUntil, vs...))
|
||||
}
|
||||
|
||||
// LockedUntilNotIn applies the NotIn predicate on the "locked_until" field.
|
||||
func LockedUntilNotIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldLockedUntil, vs...))
|
||||
}
|
||||
|
||||
// LockedUntilGT applies the GT predicate on the "locked_until" field.
|
||||
func LockedUntilGT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilGTE applies the GTE predicate on the "locked_until" field.
|
||||
func LockedUntilGTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilLT applies the LT predicate on the "locked_until" field.
|
||||
func LockedUntilLT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilLTE applies the LTE predicate on the "locked_until" field.
|
||||
func LockedUntilLTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldLockedUntil, v))
|
||||
}
|
||||
|
||||
// LockedUntilIsNil applies the IsNil predicate on the "locked_until" field.
|
||||
func LockedUntilIsNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIsNull(FieldLockedUntil))
|
||||
}
|
||||
|
||||
// LockedUntilNotNil applies the NotNil predicate on the "locked_until" field.
|
||||
func LockedUntilNotNil() predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotNull(FieldLockedUntil))
|
||||
}
|
||||
|
||||
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
|
||||
func ExpiresAtEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
|
||||
func ExpiresAtNEQ(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtIn applies the In predicate on the "expires_at" field.
|
||||
func ExpiresAtIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldIn(FieldExpiresAt, vs...))
|
||||
}
|
||||
|
||||
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
|
||||
func ExpiresAtNotIn(vs ...time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldNotIn(FieldExpiresAt, vs...))
|
||||
}
|
||||
|
||||
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
|
||||
func ExpiresAtGT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGT(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
|
||||
func ExpiresAtGTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldGTE(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
|
||||
func ExpiresAtLT(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLT(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
|
||||
func ExpiresAtLTE(v time.Time) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.FieldLTE(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// And groups predicates with the AND operator between them.
|
||||
func And(predicates ...predicate.IdempotencyRecord) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.IdempotencyRecord) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.IdempotencyRecord) predicate.IdempotencyRecord {
|
||||
return predicate.IdempotencyRecord(sql.NotPredicates(p))
|
||||
}
|
||||
1132
backend/ent/idempotencyrecord_create.go
Normal file
1132
backend/ent/idempotencyrecord_create.go
Normal file
File diff suppressed because it is too large
Load Diff
88
backend/ent/idempotencyrecord_delete.go
Normal file
88
backend/ent/idempotencyrecord_delete.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// IdempotencyRecordDelete is the builder for deleting a IdempotencyRecord entity.
|
||||
type IdempotencyRecordDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *IdempotencyRecordMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the IdempotencyRecordDelete builder.
|
||||
func (_d *IdempotencyRecordDelete) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordDelete {
|
||||
_d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (_d *IdempotencyRecordDelete) Exec(ctx context.Context) (int, error) {
|
||||
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *IdempotencyRecordDelete) ExecX(ctx context.Context) int {
|
||||
n, err := _d.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (_d *IdempotencyRecordDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := sqlgraph.NewDeleteSpec(idempotencyrecord.Table, sqlgraph.NewFieldSpec(idempotencyrecord.FieldID, field.TypeInt64))
|
||||
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
_d.mutation.done = true
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// IdempotencyRecordDeleteOne is the builder for deleting a single IdempotencyRecord entity.
|
||||
type IdempotencyRecordDeleteOne struct {
|
||||
_d *IdempotencyRecordDelete
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the IdempotencyRecordDelete builder.
|
||||
func (_d *IdempotencyRecordDeleteOne) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordDeleteOne {
|
||||
_d._d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (_d *IdempotencyRecordDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := _d._d.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{idempotencyrecord.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *IdempotencyRecordDeleteOne) ExecX(ctx context.Context) {
|
||||
if err := _d.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
564
backend/ent/idempotencyrecord_query.go
Normal file
564
backend/ent/idempotencyrecord_query.go
Normal file
@@ -0,0 +1,564 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// IdempotencyRecordQuery is the builder for querying IdempotencyRecord entities.
|
||||
type IdempotencyRecordQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []idempotencyrecord.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.IdempotencyRecord
|
||||
modifiers []func(*sql.Selector)
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Where adds a new predicate for the IdempotencyRecordQuery builder.
|
||||
func (_q *IdempotencyRecordQuery) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordQuery {
|
||||
_q.predicates = append(_q.predicates, ps...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// Limit the number of records to be returned by this query.
|
||||
func (_q *IdempotencyRecordQuery) Limit(limit int) *IdempotencyRecordQuery {
|
||||
_q.ctx.Limit = &limit
|
||||
return _q
|
||||
}
|
||||
|
||||
// Offset to start from.
|
||||
func (_q *IdempotencyRecordQuery) Offset(offset int) *IdempotencyRecordQuery {
|
||||
_q.ctx.Offset = &offset
|
||||
return _q
|
||||
}
|
||||
|
||||
// Unique configures the query builder to filter duplicate records on query.
|
||||
// By default, unique is set to true, and can be disabled using this method.
|
||||
func (_q *IdempotencyRecordQuery) Unique(unique bool) *IdempotencyRecordQuery {
|
||||
_q.ctx.Unique = &unique
|
||||
return _q
|
||||
}
|
||||
|
||||
// Order specifies how the records should be ordered.
|
||||
func (_q *IdempotencyRecordQuery) Order(o ...idempotencyrecord.OrderOption) *IdempotencyRecordQuery {
|
||||
_q.order = append(_q.order, o...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// First returns the first IdempotencyRecord entity from the query.
|
||||
// Returns a *NotFoundError when no IdempotencyRecord was found.
|
||||
func (_q *IdempotencyRecordQuery) First(ctx context.Context) (*IdempotencyRecord, error) {
|
||||
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nil, &NotFoundError{idempotencyrecord.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) FirstX(ctx context.Context) *IdempotencyRecord {
|
||||
node, err := _q.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first IdempotencyRecord ID from the query.
|
||||
// Returns a *NotFoundError when no IdempotencyRecord ID was found.
|
||||
func (_q *IdempotencyRecordQuery) FirstID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
err = &NotFoundError{idempotencyrecord.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) FirstIDX(ctx context.Context) int64 {
|
||||
id, err := _q.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single IdempotencyRecord entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one IdempotencyRecord entity is found.
|
||||
// Returns a *NotFoundError when no IdempotencyRecord entities are found.
|
||||
func (_q *IdempotencyRecordQuery) Only(ctx context.Context) (*IdempotencyRecord, error) {
|
||||
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(nodes) {
|
||||
case 1:
|
||||
return nodes[0], nil
|
||||
case 0:
|
||||
return nil, &NotFoundError{idempotencyrecord.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{idempotencyrecord.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) OnlyX(ctx context.Context) *IdempotencyRecord {
|
||||
node, err := _q.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only IdempotencyRecord ID in the query.
|
||||
// Returns a *NotSingularError when more than one IdempotencyRecord ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (_q *IdempotencyRecordQuery) OnlyID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
|
||||
return
|
||||
}
|
||||
switch len(ids) {
|
||||
case 1:
|
||||
id = ids[0]
|
||||
case 0:
|
||||
err = &NotFoundError{idempotencyrecord.Label}
|
||||
default:
|
||||
err = &NotSingularError{idempotencyrecord.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) OnlyIDX(ctx context.Context) int64 {
|
||||
id, err := _q.OnlyID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// All executes the query and returns a list of IdempotencyRecords.
|
||||
func (_q *IdempotencyRecordQuery) All(ctx context.Context) ([]*IdempotencyRecord, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr := querierAll[[]*IdempotencyRecord, *IdempotencyRecordQuery]()
|
||||
return withInterceptors[[]*IdempotencyRecord](ctx, _q, qr, _q.inters)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) AllX(ctx context.Context) []*IdempotencyRecord {
|
||||
nodes, err := _q.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of IdempotencyRecord IDs.
|
||||
func (_q *IdempotencyRecordQuery) IDs(ctx context.Context) (ids []int64, err error) {
|
||||
if _q.ctx.Unique == nil && _q.path != nil {
|
||||
_q.Unique(true)
|
||||
}
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
|
||||
if err = _q.Select(idempotencyrecord.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) IDsX(ctx context.Context) []int64 {
|
||||
ids, err := _q.IDs(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// Count returns the count of the given query.
|
||||
func (_q *IdempotencyRecordQuery) Count(ctx context.Context) (int, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return withInterceptors[int](ctx, _q, querierCount[*IdempotencyRecordQuery](), _q.inters)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) CountX(ctx context.Context) int {
|
||||
count, err := _q.Count(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Exist returns true if the query has elements in the graph.
|
||||
func (_q *IdempotencyRecordQuery) Exist(ctx context.Context) (bool, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||
switch _, err := _q.FirstID(ctx); {
|
||||
case IsNotFound(err):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExistX is like Exist, but panics if an error occurs.
|
||||
func (_q *IdempotencyRecordQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := _q.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the IdempotencyRecordQuery builder, including all associated steps. It can be
|
||||
// used to prepare common query builders and use them differently after the clone is made.
|
||||
func (_q *IdempotencyRecordQuery) Clone() *IdempotencyRecordQuery {
|
||||
if _q == nil {
|
||||
return nil
|
||||
}
|
||||
return &IdempotencyRecordQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]idempotencyrecord.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.IdempotencyRecord{}, _q.predicates...),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
}
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// Count int `json:"count,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.IdempotencyRecord.Query().
|
||||
// GroupBy(idempotencyrecord.FieldCreatedAt).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *IdempotencyRecordQuery) GroupBy(field string, fields ...string) *IdempotencyRecordGroupBy {
|
||||
_q.ctx.Fields = append([]string{field}, fields...)
|
||||
grbuild := &IdempotencyRecordGroupBy{build: _q}
|
||||
grbuild.flds = &_q.ctx.Fields
|
||||
grbuild.label = idempotencyrecord.Label
|
||||
grbuild.scan = grbuild.Scan
|
||||
return grbuild
|
||||
}
|
||||
|
||||
// Select allows the selection one or more fields/columns for the given query,
|
||||
// instead of selecting all fields in the entity.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.IdempotencyRecord.Query().
|
||||
// Select(idempotencyrecord.FieldCreatedAt).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *IdempotencyRecordQuery) Select(fields ...string) *IdempotencyRecordSelect {
|
||||
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||
sbuild := &IdempotencyRecordSelect{IdempotencyRecordQuery: _q}
|
||||
sbuild.label = idempotencyrecord.Label
|
||||
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||
return sbuild
|
||||
}
|
||||
|
||||
// Aggregate returns a IdempotencyRecordSelect configured with the given aggregations.
|
||||
func (_q *IdempotencyRecordQuery) Aggregate(fns ...AggregateFunc) *IdempotencyRecordSelect {
|
||||
return _q.Select().Aggregate(fns...)
|
||||
}
|
||||
|
||||
func (_q *IdempotencyRecordQuery) prepareQuery(ctx context.Context) error {
|
||||
for _, inter := range _q.inters {
|
||||
if inter == nil {
|
||||
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||
}
|
||||
if trv, ok := inter.(Traverser); ok {
|
||||
if err := trv.Traverse(ctx, _q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range _q.ctx.Fields {
|
||||
if !idempotencyrecord.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
}
|
||||
if _q.path != nil {
|
||||
prev, err := _q.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_q.sql = prev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *IdempotencyRecordQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*IdempotencyRecord, error) {
|
||||
var (
|
||||
nodes = []*IdempotencyRecord{}
|
||||
_spec = _q.querySpec()
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*IdempotencyRecord).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &IdempotencyRecord{config: _q.config}
|
||||
nodes = append(nodes, node)
|
||||
return node.assignValues(columns, values)
|
||||
}
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
for i := range hooks {
|
||||
hooks[i](ctx, _spec)
|
||||
}
|
||||
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *IdempotencyRecordQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
_spec.Node.Columns = _q.ctx.Fields
|
||||
if len(_q.ctx.Fields) > 0 {
|
||||
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||
}
|
||||
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||
}
|
||||
|
||||
func (_q *IdempotencyRecordQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := sqlgraph.NewQuerySpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.FieldID, field.TypeInt64))
|
||||
_spec.From = _q.sql
|
||||
if unique := _q.ctx.Unique; unique != nil {
|
||||
_spec.Unique = *unique
|
||||
} else if _q.path != nil {
|
||||
_spec.Unique = true
|
||||
}
|
||||
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, idempotencyrecord.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != idempotencyrecord.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _q.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
_spec.Limit = *limit
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
_spec.Offset = *offset
|
||||
}
|
||||
if ps := _q.order; len(ps) > 0 {
|
||||
_spec.Order = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
return _spec
|
||||
}
|
||||
|
||||
func (_q *IdempotencyRecordQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(_q.driver.Dialect())
|
||||
t1 := builder.Table(idempotencyrecord.Table)
|
||||
columns := _q.ctx.Fields
|
||||
if len(columns) == 0 {
|
||||
columns = idempotencyrecord.Columns
|
||||
}
|
||||
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||
if _q.sql != nil {
|
||||
selector = _q.sql
|
||||
selector.Select(selector.Columns(columns...)...)
|
||||
}
|
||||
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||
selector.Distinct()
|
||||
}
|
||||
for _, m := range _q.modifiers {
|
||||
m(selector)
|
||||
}
|
||||
for _, p := range _q.predicates {
|
||||
p(selector)
|
||||
}
|
||||
for _, p := range _q.order {
|
||||
p(selector)
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
// limit is mandatory for offset clause. We start
|
||||
// with default value, and override it below if needed.
|
||||
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
selector.Limit(*limit)
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
|
||||
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
|
||||
// either committed or rolled-back.
|
||||
func (_q *IdempotencyRecordQuery) ForUpdate(opts ...sql.LockOption) *IdempotencyRecordQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForUpdate(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
|
||||
// on any rows that are read. Other sessions can read the rows, but cannot modify them
|
||||
// until your transaction commits.
|
||||
func (_q *IdempotencyRecordQuery) ForShare(opts ...sql.LockOption) *IdempotencyRecordQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForShare(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// IdempotencyRecordGroupBy is the group-by builder for IdempotencyRecord entities.
|
||||
type IdempotencyRecordGroupBy struct {
|
||||
selector
|
||||
build *IdempotencyRecordQuery
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (_g *IdempotencyRecordGroupBy) Aggregate(fns ...AggregateFunc) *IdempotencyRecordGroupBy {
|
||||
_g.fns = append(_g.fns, fns...)
|
||||
return _g
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_g *IdempotencyRecordGroupBy) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
|
||||
if err := _g.build.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*IdempotencyRecordQuery, *IdempotencyRecordGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||
}
|
||||
|
||||
func (_g *IdempotencyRecordGroupBy) sqlScan(ctx context.Context, root *IdempotencyRecordQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx).Select()
|
||||
aggregation := make([]string, 0, len(_g.fns))
|
||||
for _, fn := range _g.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
if len(selector.SelectedColumns()) == 0 {
|
||||
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||
for _, f := range *_g.flds {
|
||||
columns = append(columns, selector.C(f))
|
||||
}
|
||||
columns = append(columns, aggregation...)
|
||||
selector.Select(columns...)
|
||||
}
|
||||
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||
if err := selector.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
|
||||
// IdempotencyRecordSelect is the builder for selecting fields of IdempotencyRecord entities.
|
||||
type IdempotencyRecordSelect struct {
|
||||
*IdempotencyRecordQuery
|
||||
selector
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the selector query.
|
||||
func (_s *IdempotencyRecordSelect) Aggregate(fns ...AggregateFunc) *IdempotencyRecordSelect {
|
||||
_s.fns = append(_s.fns, fns...)
|
||||
return _s
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_s *IdempotencyRecordSelect) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
|
||||
if err := _s.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*IdempotencyRecordQuery, *IdempotencyRecordSelect](ctx, _s.IdempotencyRecordQuery, _s, _s.inters, v)
|
||||
}
|
||||
|
||||
func (_s *IdempotencyRecordSelect) sqlScan(ctx context.Context, root *IdempotencyRecordQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx)
|
||||
aggregation := make([]string, 0, len(_s.fns))
|
||||
for _, fn := range _s.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
switch n := len(*_s.selector.flds); {
|
||||
case n == 0 && len(aggregation) > 0:
|
||||
selector.Select(aggregation...)
|
||||
case n != 0 && len(aggregation) > 0:
|
||||
selector.AppendSelect(aggregation...)
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
676
backend/ent/idempotencyrecord_update.go
Normal file
676
backend/ent/idempotencyrecord_update.go
Normal file
@@ -0,0 +1,676 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// IdempotencyRecordUpdate is the builder for updating IdempotencyRecord entities.
|
||||
type IdempotencyRecordUpdate struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *IdempotencyRecordMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the IdempotencyRecordUpdate builder.
|
||||
func (_u *IdempotencyRecordUpdate) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordUpdate {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetUpdatedAt(v time.Time) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetScope sets the "scope" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetScope(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetScope(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableScope sets the "scope" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableScope(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetScope(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetIdempotencyKeyHash sets the "idempotency_key_hash" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetIdempotencyKeyHash(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetIdempotencyKeyHash(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableIdempotencyKeyHash sets the "idempotency_key_hash" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableIdempotencyKeyHash(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetIdempotencyKeyHash(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRequestFingerprint sets the "request_fingerprint" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetRequestFingerprint(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetRequestFingerprint(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRequestFingerprint sets the "request_fingerprint" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableRequestFingerprint(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetRequestFingerprint(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetStatus(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableStatus(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetResponseStatus sets the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetResponseStatus(v int) *IdempotencyRecordUpdate {
|
||||
_u.mutation.ResetResponseStatus()
|
||||
_u.mutation.SetResponseStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableResponseStatus sets the "response_status" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableResponseStatus(v *int) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetResponseStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddResponseStatus adds value to the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdate) AddResponseStatus(v int) *IdempotencyRecordUpdate {
|
||||
_u.mutation.AddResponseStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearResponseStatus clears the value of the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdate) ClearResponseStatus() *IdempotencyRecordUpdate {
|
||||
_u.mutation.ClearResponseStatus()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetResponseBody sets the "response_body" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetResponseBody(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetResponseBody(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableResponseBody sets the "response_body" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableResponseBody(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetResponseBody(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearResponseBody clears the value of the "response_body" field.
|
||||
func (_u *IdempotencyRecordUpdate) ClearResponseBody() *IdempotencyRecordUpdate {
|
||||
_u.mutation.ClearResponseBody()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetErrorReason sets the "error_reason" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetErrorReason(v string) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetErrorReason(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableErrorReason sets the "error_reason" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableErrorReason(v *string) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetErrorReason(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearErrorReason clears the value of the "error_reason" field.
|
||||
func (_u *IdempotencyRecordUpdate) ClearErrorReason() *IdempotencyRecordUpdate {
|
||||
_u.mutation.ClearErrorReason()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLockedUntil sets the "locked_until" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetLockedUntil(v time.Time) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetLockedUntil(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLockedUntil sets the "locked_until" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableLockedUntil(v *time.Time) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetLockedUntil(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLockedUntil clears the value of the "locked_until" field.
|
||||
func (_u *IdempotencyRecordUpdate) ClearLockedUntil() *IdempotencyRecordUpdate {
|
||||
_u.mutation.ClearLockedUntil()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (_u *IdempotencyRecordUpdate) SetExpiresAt(v time.Time) *IdempotencyRecordUpdate {
|
||||
_u.mutation.SetExpiresAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdate) SetNillableExpiresAt(v *time.Time) *IdempotencyRecordUpdate {
|
||||
if v != nil {
|
||||
_u.SetExpiresAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// Mutation returns the IdempotencyRecordMutation object of the builder.
|
||||
func (_u *IdempotencyRecordUpdate) Mutation() *IdempotencyRecordMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *IdempotencyRecordUpdate) Save(ctx context.Context) (int, error) {
|
||||
_u.defaults()
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *IdempotencyRecordUpdate) SaveX(ctx context.Context) int {
|
||||
affected, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_u *IdempotencyRecordUpdate) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *IdempotencyRecordUpdate) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (_u *IdempotencyRecordUpdate) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := idempotencyrecord.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *IdempotencyRecordUpdate) check() error {
|
||||
if v, ok := _u.mutation.Scope(); ok {
|
||||
if err := idempotencyrecord.ScopeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "scope", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.scope": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.IdempotencyKeyHash(); ok {
|
||||
if err := idempotencyrecord.IdempotencyKeyHashValidator(v); err != nil {
|
||||
return &ValidationError{Name: "idempotency_key_hash", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.idempotency_key_hash": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.RequestFingerprint(); ok {
|
||||
if err := idempotencyrecord.RequestFingerprintValidator(v); err != nil {
|
||||
return &ValidationError{Name: "request_fingerprint", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.request_fingerprint": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Status(); ok {
|
||||
if err := idempotencyrecord.StatusValidator(v); err != nil {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ErrorReason(); ok {
|
||||
if err := idempotencyrecord.ErrorReasonValidator(v); err != nil {
|
||||
return &ValidationError{Name: "error_reason", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.error_reason": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *IdempotencyRecordUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.FieldID, field.TypeInt64))
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Scope(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldScope, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.IdempotencyKeyHash(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldIdempotencyKeyHash, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RequestFingerprint(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldRequestFingerprint, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldStatus, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ResponseStatus(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedResponseStatus(); ok {
|
||||
_spec.AddField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.ResponseStatusCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldResponseStatus, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.ResponseBody(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldResponseBody, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.ResponseBodyCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldResponseBody, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.ErrorReason(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldErrorReason, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.ErrorReasonCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldErrorReason, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.LockedUntil(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldLockedUntil, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.LockedUntilCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldLockedUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiresAt(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldExpiresAt, field.TypeTime, value)
|
||||
}
|
||||
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{idempotencyrecord.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
// IdempotencyRecordUpdateOne is the builder for updating a single IdempotencyRecord entity.
|
||||
type IdempotencyRecordUpdateOne struct {
|
||||
config
|
||||
fields []string
|
||||
hooks []Hook
|
||||
mutation *IdempotencyRecordMutation
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetUpdatedAt(v time.Time) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetScope sets the "scope" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetScope(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetScope(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableScope sets the "scope" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableScope(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetScope(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetIdempotencyKeyHash sets the "idempotency_key_hash" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetIdempotencyKeyHash(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetIdempotencyKeyHash(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableIdempotencyKeyHash sets the "idempotency_key_hash" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableIdempotencyKeyHash(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetIdempotencyKeyHash(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRequestFingerprint sets the "request_fingerprint" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetRequestFingerprint(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetRequestFingerprint(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableRequestFingerprint sets the "request_fingerprint" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableRequestFingerprint(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetRequestFingerprint(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetStatus(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableStatus(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetResponseStatus sets the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetResponseStatus(v int) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.ResetResponseStatus()
|
||||
_u.mutation.SetResponseStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableResponseStatus sets the "response_status" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableResponseStatus(v *int) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetResponseStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddResponseStatus adds value to the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) AddResponseStatus(v int) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.AddResponseStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearResponseStatus clears the value of the "response_status" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) ClearResponseStatus() *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.ClearResponseStatus()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetResponseBody sets the "response_body" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetResponseBody(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetResponseBody(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableResponseBody sets the "response_body" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableResponseBody(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetResponseBody(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearResponseBody clears the value of the "response_body" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) ClearResponseBody() *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.ClearResponseBody()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetErrorReason sets the "error_reason" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetErrorReason(v string) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetErrorReason(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableErrorReason sets the "error_reason" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableErrorReason(v *string) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetErrorReason(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearErrorReason clears the value of the "error_reason" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) ClearErrorReason() *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.ClearErrorReason()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLockedUntil sets the "locked_until" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetLockedUntil(v time.Time) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetLockedUntil(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLockedUntil sets the "locked_until" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableLockedUntil(v *time.Time) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetLockedUntil(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLockedUntil clears the value of the "locked_until" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) ClearLockedUntil() *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.ClearLockedUntil()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetExpiresAt(v time.Time) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.SetExpiresAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (_u *IdempotencyRecordUpdateOne) SetNillableExpiresAt(v *time.Time) *IdempotencyRecordUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetExpiresAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// Mutation returns the IdempotencyRecordMutation object of the builder.
|
||||
func (_u *IdempotencyRecordUpdateOne) Mutation() *IdempotencyRecordMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the IdempotencyRecordUpdate builder.
|
||||
func (_u *IdempotencyRecordUpdateOne) Where(ps ...predicate.IdempotencyRecord) *IdempotencyRecordUpdateOne {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||
// The default is selecting all fields defined in the entity schema.
|
||||
func (_u *IdempotencyRecordUpdateOne) Select(field string, fields ...string) *IdempotencyRecordUpdateOne {
|
||||
_u.fields = append([]string{field}, fields...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the updated IdempotencyRecord entity.
|
||||
func (_u *IdempotencyRecordUpdateOne) Save(ctx context.Context) (*IdempotencyRecord, error) {
|
||||
_u.defaults()
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *IdempotencyRecordUpdateOne) SaveX(ctx context.Context) *IdempotencyRecord {
|
||||
node, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Exec executes the query on the entity.
|
||||
func (_u *IdempotencyRecordUpdateOne) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *IdempotencyRecordUpdateOne) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (_u *IdempotencyRecordUpdateOne) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := idempotencyrecord.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *IdempotencyRecordUpdateOne) check() error {
|
||||
if v, ok := _u.mutation.Scope(); ok {
|
||||
if err := idempotencyrecord.ScopeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "scope", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.scope": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.IdempotencyKeyHash(); ok {
|
||||
if err := idempotencyrecord.IdempotencyKeyHashValidator(v); err != nil {
|
||||
return &ValidationError{Name: "idempotency_key_hash", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.idempotency_key_hash": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.RequestFingerprint(); ok {
|
||||
if err := idempotencyrecord.RequestFingerprintValidator(v); err != nil {
|
||||
return &ValidationError{Name: "request_fingerprint", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.request_fingerprint": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Status(); ok {
|
||||
if err := idempotencyrecord.StatusValidator(v); err != nil {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.ErrorReason(); ok {
|
||||
if err := idempotencyrecord.ErrorReasonValidator(v); err != nil {
|
||||
return &ValidationError{Name: "error_reason", err: fmt.Errorf(`ent: validator failed for field "IdempotencyRecord.error_reason": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *IdempotencyRecordUpdateOne) sqlSave(ctx context.Context) (_node *IdempotencyRecord, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(idempotencyrecord.Table, idempotencyrecord.Columns, sqlgraph.NewFieldSpec(idempotencyrecord.FieldID, field.TypeInt64))
|
||||
id, ok := _u.mutation.ID()
|
||||
if !ok {
|
||||
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "IdempotencyRecord.id" for update`)}
|
||||
}
|
||||
_spec.Node.ID.Value = id
|
||||
if fields := _u.fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, idempotencyrecord.FieldID)
|
||||
for _, f := range fields {
|
||||
if !idempotencyrecord.ValidColumn(f) {
|
||||
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
if f != idempotencyrecord.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Scope(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldScope, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.IdempotencyKeyHash(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldIdempotencyKeyHash, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RequestFingerprint(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldRequestFingerprint, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldStatus, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ResponseStatus(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedResponseStatus(); ok {
|
||||
_spec.AddField(idempotencyrecord.FieldResponseStatus, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.ResponseStatusCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldResponseStatus, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.ResponseBody(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldResponseBody, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.ResponseBodyCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldResponseBody, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.ErrorReason(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldErrorReason, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.ErrorReasonCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldErrorReason, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.LockedUntil(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldLockedUntil, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.LockedUntilCleared() {
|
||||
_spec.ClearField(idempotencyrecord.FieldLockedUntil, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiresAt(); ok {
|
||||
_spec.SetField(idempotencyrecord.FieldExpiresAt, field.TypeTime, value)
|
||||
}
|
||||
_node = &IdempotencyRecord{config: _u.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{idempotencyrecord.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
@@ -276,6 +277,33 @@ func (f TraverseGroup) Traverse(ctx context.Context, q ent.Query) error {
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.GroupQuery", q)
|
||||
}
|
||||
|
||||
// The IdempotencyRecordFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type IdempotencyRecordFunc func(context.Context, *ent.IdempotencyRecordQuery) (ent.Value, error)
|
||||
|
||||
// Query calls f(ctx, q).
|
||||
func (f IdempotencyRecordFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||
if q, ok := q.(*ent.IdempotencyRecordQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q)
|
||||
}
|
||||
|
||||
// The TraverseIdempotencyRecord type is an adapter to allow the use of ordinary function as Traverser.
|
||||
type TraverseIdempotencyRecord func(context.Context, *ent.IdempotencyRecordQuery) error
|
||||
|
||||
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||
func (f TraverseIdempotencyRecord) Intercept(next ent.Querier) ent.Querier {
|
||||
return next
|
||||
}
|
||||
|
||||
// Traverse calls f(ctx, q).
|
||||
func (f TraverseIdempotencyRecord) Traverse(ctx context.Context, q ent.Query) error {
|
||||
if q, ok := q.(*ent.IdempotencyRecordQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q)
|
||||
}
|
||||
|
||||
// The PromoCodeFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type PromoCodeFunc func(context.Context, *ent.PromoCodeQuery) (ent.Value, error)
|
||||
|
||||
@@ -644,6 +672,8 @@ func NewQuery(q ent.Query) (Query, error) {
|
||||
return &query[*ent.ErrorPassthroughRuleQuery, predicate.ErrorPassthroughRule, errorpassthroughrule.OrderOption]{typ: ent.TypeErrorPassthroughRule, tq: q}, nil
|
||||
case *ent.GroupQuery:
|
||||
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
|
||||
case *ent.IdempotencyRecordQuery:
|
||||
return &query[*ent.IdempotencyRecordQuery, predicate.IdempotencyRecord, idempotencyrecord.OrderOption]{typ: ent.TypeIdempotencyRecord, tq: q}, nil
|
||||
case *ent.PromoCodeQuery:
|
||||
return &query[*ent.PromoCodeQuery, predicate.PromoCode, promocode.OrderOption]{typ: ent.TypePromoCode, tq: q}, nil
|
||||
case *ent.PromoCodeUsageQuery:
|
||||
|
||||
@@ -24,6 +24,15 @@ var (
|
||||
{Name: "quota", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "quota_used", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "expires_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "rate_limit_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "rate_limit_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "rate_limit_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "usage_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "usage_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "usage_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "window_5h_start", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "window_1d_start", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "window_7d_start", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "group_id", Type: field.TypeInt64, Nullable: true},
|
||||
{Name: "user_id", Type: field.TypeInt64},
|
||||
}
|
||||
@@ -35,13 +44,13 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "api_keys_groups_api_keys",
|
||||
Columns: []*schema.Column{APIKeysColumns[13]},
|
||||
Columns: []*schema.Column{APIKeysColumns[22]},
|
||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
{
|
||||
Symbol: "api_keys_users_api_keys",
|
||||
Columns: []*schema.Column{APIKeysColumns[14]},
|
||||
Columns: []*schema.Column{APIKeysColumns[23]},
|
||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||
OnDelete: schema.NoAction,
|
||||
},
|
||||
@@ -50,12 +59,12 @@ var (
|
||||
{
|
||||
Name: "apikey_user_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{APIKeysColumns[14]},
|
||||
Columns: []*schema.Column{APIKeysColumns[23]},
|
||||
},
|
||||
{
|
||||
Name: "apikey_group_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{APIKeysColumns[13]},
|
||||
Columns: []*schema.Column{APIKeysColumns[22]},
|
||||
},
|
||||
{
|
||||
Name: "apikey_status",
|
||||
@@ -97,6 +106,7 @@ var (
|
||||
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||
{Name: "extra", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||
{Name: "concurrency", Type: field.TypeInt, Default: 3},
|
||||
{Name: "load_factor", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "priority", Type: field.TypeInt, Default: 50},
|
||||
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
|
||||
@@ -108,6 +118,8 @@ var (
|
||||
{Name: "rate_limited_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "rate_limit_reset_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "overload_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "temp_unschedulable_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "temp_unschedulable_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "session_window_start", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "session_window_end", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "session_window_status", Type: field.TypeString, Nullable: true, Size: 20},
|
||||
@@ -121,7 +133,7 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "accounts_proxies_proxy",
|
||||
Columns: []*schema.Column{AccountsColumns[25]},
|
||||
Columns: []*schema.Column{AccountsColumns[28]},
|
||||
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
@@ -140,42 +152,52 @@ var (
|
||||
{
|
||||
Name: "account_status",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[13]},
|
||||
Columns: []*schema.Column{AccountsColumns[14]},
|
||||
},
|
||||
{
|
||||
Name: "account_proxy_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[25]},
|
||||
Columns: []*schema.Column{AccountsColumns[28]},
|
||||
},
|
||||
{
|
||||
Name: "account_priority",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[11]},
|
||||
Columns: []*schema.Column{AccountsColumns[12]},
|
||||
},
|
||||
{
|
||||
Name: "account_last_used_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[15]},
|
||||
Columns: []*schema.Column{AccountsColumns[16]},
|
||||
},
|
||||
{
|
||||
Name: "account_schedulable",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[18]},
|
||||
Columns: []*schema.Column{AccountsColumns[19]},
|
||||
},
|
||||
{
|
||||
Name: "account_rate_limited_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[19]},
|
||||
Columns: []*schema.Column{AccountsColumns[20]},
|
||||
},
|
||||
{
|
||||
Name: "account_rate_limit_reset_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[20]},
|
||||
Columns: []*schema.Column{AccountsColumns[21]},
|
||||
},
|
||||
{
|
||||
Name: "account_overload_until",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[21]},
|
||||
Columns: []*schema.Column{AccountsColumns[22]},
|
||||
},
|
||||
{
|
||||
Name: "account_platform_priority",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[12]},
|
||||
},
|
||||
{
|
||||
Name: "account_priority_status",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[12], AccountsColumns[14]},
|
||||
},
|
||||
{
|
||||
Name: "account_deleted_at",
|
||||
@@ -376,6 +398,7 @@ var (
|
||||
{Name: "sora_image_price_540", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "sora_video_price_per_request", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "sora_video_price_per_request_hd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "sora_storage_quota_bytes", Type: field.TypeInt64, Default: 0},
|
||||
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
|
||||
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
|
||||
{Name: "fallback_group_id_on_invalid_request", Type: field.TypeInt64, Nullable: true},
|
||||
@@ -419,7 +442,45 @@ var (
|
||||
{
|
||||
Name: "group_sort_order",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{GroupsColumns[29]},
|
||||
Columns: []*schema.Column{GroupsColumns[30]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// IdempotencyRecordsColumns holds the columns for the "idempotency_records" table.
|
||||
IdempotencyRecordsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "scope", Type: field.TypeString, Size: 128},
|
||||
{Name: "idempotency_key_hash", Type: field.TypeString, Size: 64},
|
||||
{Name: "request_fingerprint", Type: field.TypeString, Size: 64},
|
||||
{Name: "status", Type: field.TypeString, Size: 32},
|
||||
{Name: "response_status", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "response_body", Type: field.TypeString, Nullable: true},
|
||||
{Name: "error_reason", Type: field.TypeString, Nullable: true, Size: 128},
|
||||
{Name: "locked_until", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "expires_at", Type: field.TypeTime},
|
||||
}
|
||||
// IdempotencyRecordsTable holds the schema information for the "idempotency_records" table.
|
||||
IdempotencyRecordsTable = &schema.Table{
|
||||
Name: "idempotency_records",
|
||||
Columns: IdempotencyRecordsColumns,
|
||||
PrimaryKey: []*schema.Column{IdempotencyRecordsColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "idempotencyrecord_scope_idempotency_key_hash",
|
||||
Unique: true,
|
||||
Columns: []*schema.Column{IdempotencyRecordsColumns[3], IdempotencyRecordsColumns[4]},
|
||||
},
|
||||
{
|
||||
Name: "idempotencyrecord_expires_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{IdempotencyRecordsColumns[11]},
|
||||
},
|
||||
{
|
||||
Name: "idempotencyrecord_status_locked_until",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{IdempotencyRecordsColumns[6], IdempotencyRecordsColumns[10]},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -771,6 +832,11 @@ var (
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[28], UsageLogsColumns[27]},
|
||||
},
|
||||
{
|
||||
Name: "usagelog_group_id_created_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UsageLogsColumns[30], UsageLogsColumns[27]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// UsersColumns holds the columns for the "users" table.
|
||||
@@ -790,6 +856,8 @@ var (
|
||||
{Name: "totp_secret_encrypted", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "totp_enabled", Type: field.TypeBool, Default: false},
|
||||
{Name: "totp_enabled_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "sora_storage_quota_bytes", Type: field.TypeInt64, Default: 0},
|
||||
{Name: "sora_storage_used_bytes", Type: field.TypeInt64, Default: 0},
|
||||
}
|
||||
// UsersTable holds the schema information for the "users" table.
|
||||
UsersTable = &schema.Table{
|
||||
@@ -995,6 +1063,11 @@ var (
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UserSubscriptionsColumns[5]},
|
||||
},
|
||||
{
|
||||
Name: "usersubscription_user_id_status_expires_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{UserSubscriptionsColumns[16], UserSubscriptionsColumns[6], UserSubscriptionsColumns[5]},
|
||||
},
|
||||
{
|
||||
Name: "usersubscription_assigned_by",
|
||||
Unique: false,
|
||||
@@ -1021,6 +1094,7 @@ var (
|
||||
AnnouncementReadsTable,
|
||||
ErrorPassthroughRulesTable,
|
||||
GroupsTable,
|
||||
IdempotencyRecordsTable,
|
||||
PromoCodesTable,
|
||||
PromoCodeUsagesTable,
|
||||
ProxiesTable,
|
||||
@@ -1066,6 +1140,9 @@ func init() {
|
||||
GroupsTable.Annotation = &entsql.Annotation{
|
||||
Table: "groups",
|
||||
}
|
||||
IdempotencyRecordsTable.Annotation = &entsql.Annotation{
|
||||
Table: "idempotency_records",
|
||||
}
|
||||
PromoCodesTable.Annotation = &entsql.Annotation{
|
||||
Table: "promo_codes",
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,9 @@ type ErrorPassthroughRule func(*sql.Selector)
|
||||
// Group is the predicate function for group builders.
|
||||
type Group func(*sql.Selector)
|
||||
|
||||
// IdempotencyRecord is the predicate function for idempotencyrecord builders.
|
||||
type IdempotencyRecord func(*sql.Selector)
|
||||
|
||||
// PromoCode is the predicate function for promocode builders.
|
||||
type PromoCode func(*sql.Selector)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
@@ -101,6 +102,30 @@ func init() {
|
||||
apikeyDescQuotaUsed := apikeyFields[9].Descriptor()
|
||||
// apikey.DefaultQuotaUsed holds the default value on creation for the quota_used field.
|
||||
apikey.DefaultQuotaUsed = apikeyDescQuotaUsed.Default.(float64)
|
||||
// apikeyDescRateLimit5h is the schema descriptor for rate_limit_5h field.
|
||||
apikeyDescRateLimit5h := apikeyFields[11].Descriptor()
|
||||
// apikey.DefaultRateLimit5h holds the default value on creation for the rate_limit_5h field.
|
||||
apikey.DefaultRateLimit5h = apikeyDescRateLimit5h.Default.(float64)
|
||||
// apikeyDescRateLimit1d is the schema descriptor for rate_limit_1d field.
|
||||
apikeyDescRateLimit1d := apikeyFields[12].Descriptor()
|
||||
// apikey.DefaultRateLimit1d holds the default value on creation for the rate_limit_1d field.
|
||||
apikey.DefaultRateLimit1d = apikeyDescRateLimit1d.Default.(float64)
|
||||
// apikeyDescRateLimit7d is the schema descriptor for rate_limit_7d field.
|
||||
apikeyDescRateLimit7d := apikeyFields[13].Descriptor()
|
||||
// apikey.DefaultRateLimit7d holds the default value on creation for the rate_limit_7d field.
|
||||
apikey.DefaultRateLimit7d = apikeyDescRateLimit7d.Default.(float64)
|
||||
// apikeyDescUsage5h is the schema descriptor for usage_5h field.
|
||||
apikeyDescUsage5h := apikeyFields[14].Descriptor()
|
||||
// apikey.DefaultUsage5h holds the default value on creation for the usage_5h field.
|
||||
apikey.DefaultUsage5h = apikeyDescUsage5h.Default.(float64)
|
||||
// apikeyDescUsage1d is the schema descriptor for usage_1d field.
|
||||
apikeyDescUsage1d := apikeyFields[15].Descriptor()
|
||||
// apikey.DefaultUsage1d holds the default value on creation for the usage_1d field.
|
||||
apikey.DefaultUsage1d = apikeyDescUsage1d.Default.(float64)
|
||||
// apikeyDescUsage7d is the schema descriptor for usage_7d field.
|
||||
apikeyDescUsage7d := apikeyFields[16].Descriptor()
|
||||
// apikey.DefaultUsage7d holds the default value on creation for the usage_7d field.
|
||||
apikey.DefaultUsage7d = apikeyDescUsage7d.Default.(float64)
|
||||
accountMixin := schema.Account{}.Mixin()
|
||||
accountMixinHooks1 := accountMixin[1].Hooks()
|
||||
account.Hooks[0] = accountMixinHooks1[0]
|
||||
@@ -187,29 +212,29 @@ func init() {
|
||||
// account.DefaultConcurrency holds the default value on creation for the concurrency field.
|
||||
account.DefaultConcurrency = accountDescConcurrency.Default.(int)
|
||||
// accountDescPriority is the schema descriptor for priority field.
|
||||
accountDescPriority := accountFields[8].Descriptor()
|
||||
accountDescPriority := accountFields[9].Descriptor()
|
||||
// account.DefaultPriority holds the default value on creation for the priority field.
|
||||
account.DefaultPriority = accountDescPriority.Default.(int)
|
||||
// accountDescRateMultiplier is the schema descriptor for rate_multiplier field.
|
||||
accountDescRateMultiplier := accountFields[9].Descriptor()
|
||||
accountDescRateMultiplier := accountFields[10].Descriptor()
|
||||
// account.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
||||
account.DefaultRateMultiplier = accountDescRateMultiplier.Default.(float64)
|
||||
// accountDescStatus is the schema descriptor for status field.
|
||||
accountDescStatus := accountFields[10].Descriptor()
|
||||
accountDescStatus := accountFields[11].Descriptor()
|
||||
// account.DefaultStatus holds the default value on creation for the status field.
|
||||
account.DefaultStatus = accountDescStatus.Default.(string)
|
||||
// account.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||
account.StatusValidator = accountDescStatus.Validators[0].(func(string) error)
|
||||
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
|
||||
accountDescAutoPauseOnExpired := accountFields[14].Descriptor()
|
||||
accountDescAutoPauseOnExpired := accountFields[15].Descriptor()
|
||||
// account.DefaultAutoPauseOnExpired holds the default value on creation for the auto_pause_on_expired field.
|
||||
account.DefaultAutoPauseOnExpired = accountDescAutoPauseOnExpired.Default.(bool)
|
||||
// accountDescSchedulable is the schema descriptor for schedulable field.
|
||||
accountDescSchedulable := accountFields[15].Descriptor()
|
||||
accountDescSchedulable := accountFields[16].Descriptor()
|
||||
// account.DefaultSchedulable holds the default value on creation for the schedulable field.
|
||||
account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
|
||||
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
|
||||
accountDescSessionWindowStatus := accountFields[21].Descriptor()
|
||||
accountDescSessionWindowStatus := accountFields[24].Descriptor()
|
||||
// account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
|
||||
account.SessionWindowStatusValidator = accountDescSessionWindowStatus.Validators[0].(func(string) error)
|
||||
accountgroupFields := schema.AccountGroup{}.Fields()
|
||||
@@ -398,26 +423,65 @@ func init() {
|
||||
groupDescDefaultValidityDays := groupFields[10].Descriptor()
|
||||
// group.DefaultDefaultValidityDays holds the default value on creation for the default_validity_days field.
|
||||
group.DefaultDefaultValidityDays = groupDescDefaultValidityDays.Default.(int)
|
||||
// groupDescSoraStorageQuotaBytes is the schema descriptor for sora_storage_quota_bytes field.
|
||||
groupDescSoraStorageQuotaBytes := groupFields[18].Descriptor()
|
||||
// group.DefaultSoraStorageQuotaBytes holds the default value on creation for the sora_storage_quota_bytes field.
|
||||
group.DefaultSoraStorageQuotaBytes = groupDescSoraStorageQuotaBytes.Default.(int64)
|
||||
// groupDescClaudeCodeOnly is the schema descriptor for claude_code_only field.
|
||||
groupDescClaudeCodeOnly := groupFields[18].Descriptor()
|
||||
groupDescClaudeCodeOnly := groupFields[19].Descriptor()
|
||||
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
|
||||
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
|
||||
// groupDescModelRoutingEnabled is the schema descriptor for model_routing_enabled field.
|
||||
groupDescModelRoutingEnabled := groupFields[22].Descriptor()
|
||||
groupDescModelRoutingEnabled := groupFields[23].Descriptor()
|
||||
// group.DefaultModelRoutingEnabled holds the default value on creation for the model_routing_enabled field.
|
||||
group.DefaultModelRoutingEnabled = groupDescModelRoutingEnabled.Default.(bool)
|
||||
// groupDescMcpXMLInject is the schema descriptor for mcp_xml_inject field.
|
||||
groupDescMcpXMLInject := groupFields[23].Descriptor()
|
||||
groupDescMcpXMLInject := groupFields[24].Descriptor()
|
||||
// group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field.
|
||||
group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool)
|
||||
// groupDescSupportedModelScopes is the schema descriptor for supported_model_scopes field.
|
||||
groupDescSupportedModelScopes := groupFields[24].Descriptor()
|
||||
groupDescSupportedModelScopes := groupFields[25].Descriptor()
|
||||
// group.DefaultSupportedModelScopes holds the default value on creation for the supported_model_scopes field.
|
||||
group.DefaultSupportedModelScopes = groupDescSupportedModelScopes.Default.([]string)
|
||||
// groupDescSortOrder is the schema descriptor for sort_order field.
|
||||
groupDescSortOrder := groupFields[25].Descriptor()
|
||||
groupDescSortOrder := groupFields[26].Descriptor()
|
||||
// group.DefaultSortOrder holds the default value on creation for the sort_order field.
|
||||
group.DefaultSortOrder = groupDescSortOrder.Default.(int)
|
||||
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
|
||||
idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields()
|
||||
_ = idempotencyrecordMixinFields0
|
||||
idempotencyrecordFields := schema.IdempotencyRecord{}.Fields()
|
||||
_ = idempotencyrecordFields
|
||||
// idempotencyrecordDescCreatedAt is the schema descriptor for created_at field.
|
||||
idempotencyrecordDescCreatedAt := idempotencyrecordMixinFields0[0].Descriptor()
|
||||
// idempotencyrecord.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
idempotencyrecord.DefaultCreatedAt = idempotencyrecordDescCreatedAt.Default.(func() time.Time)
|
||||
// idempotencyrecordDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
idempotencyrecordDescUpdatedAt := idempotencyrecordMixinFields0[1].Descriptor()
|
||||
// idempotencyrecord.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
idempotencyrecord.DefaultUpdatedAt = idempotencyrecordDescUpdatedAt.Default.(func() time.Time)
|
||||
// idempotencyrecord.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
idempotencyrecord.UpdateDefaultUpdatedAt = idempotencyrecordDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||
// idempotencyrecordDescScope is the schema descriptor for scope field.
|
||||
idempotencyrecordDescScope := idempotencyrecordFields[0].Descriptor()
|
||||
// idempotencyrecord.ScopeValidator is a validator for the "scope" field. It is called by the builders before save.
|
||||
idempotencyrecord.ScopeValidator = idempotencyrecordDescScope.Validators[0].(func(string) error)
|
||||
// idempotencyrecordDescIdempotencyKeyHash is the schema descriptor for idempotency_key_hash field.
|
||||
idempotencyrecordDescIdempotencyKeyHash := idempotencyrecordFields[1].Descriptor()
|
||||
// idempotencyrecord.IdempotencyKeyHashValidator is a validator for the "idempotency_key_hash" field. It is called by the builders before save.
|
||||
idempotencyrecord.IdempotencyKeyHashValidator = idempotencyrecordDescIdempotencyKeyHash.Validators[0].(func(string) error)
|
||||
// idempotencyrecordDescRequestFingerprint is the schema descriptor for request_fingerprint field.
|
||||
idempotencyrecordDescRequestFingerprint := idempotencyrecordFields[2].Descriptor()
|
||||
// idempotencyrecord.RequestFingerprintValidator is a validator for the "request_fingerprint" field. It is called by the builders before save.
|
||||
idempotencyrecord.RequestFingerprintValidator = idempotencyrecordDescRequestFingerprint.Validators[0].(func(string) error)
|
||||
// idempotencyrecordDescStatus is the schema descriptor for status field.
|
||||
idempotencyrecordDescStatus := idempotencyrecordFields[3].Descriptor()
|
||||
// idempotencyrecord.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||
idempotencyrecord.StatusValidator = idempotencyrecordDescStatus.Validators[0].(func(string) error)
|
||||
// idempotencyrecordDescErrorReason is the schema descriptor for error_reason field.
|
||||
idempotencyrecordDescErrorReason := idempotencyrecordFields[6].Descriptor()
|
||||
// idempotencyrecord.ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save.
|
||||
idempotencyrecord.ErrorReasonValidator = idempotencyrecordDescErrorReason.Validators[0].(func(string) error)
|
||||
promocodeFields := schema.PromoCode{}.Fields()
|
||||
_ = promocodeFields
|
||||
// promocodeDescCode is the schema descriptor for code field.
|
||||
@@ -918,6 +982,14 @@ func init() {
|
||||
userDescTotpEnabled := userFields[9].Descriptor()
|
||||
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
|
||||
user.DefaultTotpEnabled = userDescTotpEnabled.Default.(bool)
|
||||
// userDescSoraStorageQuotaBytes is the schema descriptor for sora_storage_quota_bytes field.
|
||||
userDescSoraStorageQuotaBytes := userFields[11].Descriptor()
|
||||
// user.DefaultSoraStorageQuotaBytes holds the default value on creation for the sora_storage_quota_bytes field.
|
||||
user.DefaultSoraStorageQuotaBytes = userDescSoraStorageQuotaBytes.Default.(int64)
|
||||
// userDescSoraStorageUsedBytes is the schema descriptor for sora_storage_used_bytes field.
|
||||
userDescSoraStorageUsedBytes := userFields[12].Descriptor()
|
||||
// user.DefaultSoraStorageUsedBytes holds the default value on creation for the sora_storage_used_bytes field.
|
||||
user.DefaultSoraStorageUsedBytes = userDescSoraStorageUsedBytes.Default.(int64)
|
||||
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
|
||||
_ = userallowedgroupFields
|
||||
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.
|
||||
|
||||
@@ -97,6 +97,8 @@ func (Account) Fields() []ent.Field {
|
||||
field.Int("concurrency").
|
||||
Default(3),
|
||||
|
||||
field.Int("load_factor").Optional().Nillable(),
|
||||
|
||||
// priority: 账户优先级,数值越小优先级越高
|
||||
// 调度器会优先使用高优先级的账户
|
||||
field.Int("priority").
|
||||
@@ -164,6 +166,19 @@ func (Account) Fields() []ent.Field {
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||
|
||||
// temp_unschedulable_until: 临时不可调度状态解除时间
|
||||
// 当命中临时不可调度规则时设置,在此时间前调度器应跳过该账号
|
||||
field.Time("temp_unschedulable_until").
|
||||
Optional().
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||
|
||||
// temp_unschedulable_reason: 临时不可调度原因,便于排障审计
|
||||
field.String("temp_unschedulable_reason").
|
||||
Optional().
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "text"}),
|
||||
|
||||
// session_window_*: 会话窗口相关字段
|
||||
// 用于管理某些需要会话时间窗口的 API(如 Claude Pro)
|
||||
field.Time("session_window_start").
|
||||
@@ -213,6 +228,9 @@ func (Account) Indexes() []ent.Index {
|
||||
index.Fields("rate_limited_at"), // 筛选速率限制账户
|
||||
index.Fields("rate_limit_reset_at"), // 筛选速率限制解除时间
|
||||
index.Fields("overload_until"), // 筛选过载账户
|
||||
index.Fields("deleted_at"), // 软删除查询优化
|
||||
// 调度热路径复合索引(线上由 SQL 迁移创建部分索引,schema 仅用于模型可读性对齐)
|
||||
index.Fields("platform", "priority"),
|
||||
index.Fields("priority", "status"),
|
||||
index.Fields("deleted_at"), // 软删除查询优化
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,47 @@ func (APIKey) Fields() []ent.Field {
|
||||
Optional().
|
||||
Nillable().
|
||||
Comment("Expiration time for this API key (null = never expires)"),
|
||||
|
||||
// ========== Rate limit fields ==========
|
||||
// Rate limit configuration (0 = unlimited)
|
||||
field.Float("rate_limit_5h").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Rate limit in USD per 5 hours (0 = unlimited)"),
|
||||
field.Float("rate_limit_1d").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Rate limit in USD per day (0 = unlimited)"),
|
||||
field.Float("rate_limit_7d").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Rate limit in USD per 7 days (0 = unlimited)"),
|
||||
// Rate limit usage tracking
|
||||
field.Float("usage_5h").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Used amount in USD for the current 5h window"),
|
||||
field.Float("usage_1d").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Used amount in USD for the current 1d window"),
|
||||
field.Float("usage_7d").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||
Default(0).
|
||||
Comment("Used amount in USD for the current 7d window"),
|
||||
// Window start times
|
||||
field.Time("window_5h_start").
|
||||
Optional().
|
||||
Nillable().
|
||||
Comment("Start time of the current 5h rate limit window"),
|
||||
field.Time("window_1d_start").
|
||||
Optional().
|
||||
Nillable().
|
||||
Comment("Start time of the current 1d rate limit window"),
|
||||
field.Time("window_7d_start").
|
||||
Optional().
|
||||
Nillable().
|
||||
Comment("Start time of the current 7d rate limit window"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,10 @@ func (Group) Fields() []ent.Field {
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
|
||||
|
||||
// Sora 存储配额
|
||||
field.Int64("sora_storage_quota_bytes").
|
||||
Default(0),
|
||||
|
||||
// Claude Code 客户端限制 (added by migration 029)
|
||||
field.Bool("claude_code_only").
|
||||
Default(false).
|
||||
|
||||
@@ -179,5 +179,7 @@ func (UsageLog) Indexes() []ent.Index {
|
||||
// 复合索引用于时间范围查询
|
||||
index.Fields("user_id", "created_at"),
|
||||
index.Fields("api_key_id", "created_at"),
|
||||
// 分组维度时间范围查询(线上由 SQL 迁移创建 group_id IS NOT NULL 的部分索引)
|
||||
index.Fields("group_id", "created_at"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,12 @@ func (User) Fields() []ent.Field {
|
||||
field.Time("totp_enabled_at").
|
||||
Optional().
|
||||
Nillable(),
|
||||
|
||||
// Sora 存储配额
|
||||
field.Int64("sora_storage_quota_bytes").
|
||||
Default(0),
|
||||
field.Int64("sora_storage_used_bytes").
|
||||
Default(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ func (UserSubscription) Indexes() []ent.Index {
|
||||
index.Fields("group_id"),
|
||||
index.Fields("status"),
|
||||
index.Fields("expires_at"),
|
||||
// 活跃订阅查询复合索引(线上由 SQL 迁移创建部分索引,schema 仅用于模型可读性对齐)
|
||||
index.Fields("user_id", "status", "expires_at"),
|
||||
index.Fields("assigned_by"),
|
||||
// 唯一约束通过部分索引实现(WHERE deleted_at IS NULL),支持软删除后重新订阅
|
||||
// 见迁移文件 016_soft_delete_partial_unique_indexes.sql
|
||||
|
||||
@@ -28,6 +28,8 @@ type Tx struct {
|
||||
ErrorPassthroughRule *ErrorPassthroughRuleClient
|
||||
// Group is the client for interacting with the Group builders.
|
||||
Group *GroupClient
|
||||
// IdempotencyRecord is the client for interacting with the IdempotencyRecord builders.
|
||||
IdempotencyRecord *IdempotencyRecordClient
|
||||
// PromoCode is the client for interacting with the PromoCode builders.
|
||||
PromoCode *PromoCodeClient
|
||||
// PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
|
||||
@@ -192,6 +194,7 @@ func (tx *Tx) init() {
|
||||
tx.AnnouncementRead = NewAnnouncementReadClient(tx.config)
|
||||
tx.ErrorPassthroughRule = NewErrorPassthroughRuleClient(tx.config)
|
||||
tx.Group = NewGroupClient(tx.config)
|
||||
tx.IdempotencyRecord = NewIdempotencyRecordClient(tx.config)
|
||||
tx.PromoCode = NewPromoCodeClient(tx.config)
|
||||
tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
|
||||
tx.Proxy = NewProxyClient(tx.config)
|
||||
|
||||
@@ -45,6 +45,10 @@ type User struct {
|
||||
TotpEnabled bool `json:"totp_enabled,omitempty"`
|
||||
// TotpEnabledAt holds the value of the "totp_enabled_at" field.
|
||||
TotpEnabledAt *time.Time `json:"totp_enabled_at,omitempty"`
|
||||
// SoraStorageQuotaBytes holds the value of the "sora_storage_quota_bytes" field.
|
||||
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes,omitempty"`
|
||||
// SoraStorageUsedBytes holds the value of the "sora_storage_used_bytes" field.
|
||||
SoraStorageUsedBytes int64 `json:"sora_storage_used_bytes,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the UserQuery when eager-loading is set.
|
||||
Edges UserEdges `json:"edges"`
|
||||
@@ -177,7 +181,7 @@ func (*User) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullBool)
|
||||
case user.FieldBalance:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case user.FieldID, user.FieldConcurrency:
|
||||
case user.FieldID, user.FieldConcurrency, user.FieldSoraStorageQuotaBytes, user.FieldSoraStorageUsedBytes:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case user.FieldEmail, user.FieldPasswordHash, user.FieldRole, user.FieldStatus, user.FieldUsername, user.FieldNotes, user.FieldTotpSecretEncrypted:
|
||||
values[i] = new(sql.NullString)
|
||||
@@ -291,6 +295,18 @@ func (_m *User) assignValues(columns []string, values []any) error {
|
||||
_m.TotpEnabledAt = new(time.Time)
|
||||
*_m.TotpEnabledAt = value.Time
|
||||
}
|
||||
case user.FieldSoraStorageQuotaBytes:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field sora_storage_quota_bytes", values[i])
|
||||
} else if value.Valid {
|
||||
_m.SoraStorageQuotaBytes = value.Int64
|
||||
}
|
||||
case user.FieldSoraStorageUsedBytes:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field sora_storage_used_bytes", values[i])
|
||||
} else if value.Valid {
|
||||
_m.SoraStorageUsedBytes = value.Int64
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
@@ -424,6 +440,12 @@ func (_m *User) String() string {
|
||||
builder.WriteString("totp_enabled_at=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("sora_storage_quota_bytes=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.SoraStorageQuotaBytes))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("sora_storage_used_bytes=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.SoraStorageUsedBytes))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ const (
|
||||
FieldTotpEnabled = "totp_enabled"
|
||||
// FieldTotpEnabledAt holds the string denoting the totp_enabled_at field in the database.
|
||||
FieldTotpEnabledAt = "totp_enabled_at"
|
||||
// FieldSoraStorageQuotaBytes holds the string denoting the sora_storage_quota_bytes field in the database.
|
||||
FieldSoraStorageQuotaBytes = "sora_storage_quota_bytes"
|
||||
// FieldSoraStorageUsedBytes holds the string denoting the sora_storage_used_bytes field in the database.
|
||||
FieldSoraStorageUsedBytes = "sora_storage_used_bytes"
|
||||
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
||||
EdgeAPIKeys = "api_keys"
|
||||
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
||||
@@ -152,6 +156,8 @@ var Columns = []string{
|
||||
FieldTotpSecretEncrypted,
|
||||
FieldTotpEnabled,
|
||||
FieldTotpEnabledAt,
|
||||
FieldSoraStorageQuotaBytes,
|
||||
FieldSoraStorageUsedBytes,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -208,6 +214,10 @@ var (
|
||||
DefaultNotes string
|
||||
// DefaultTotpEnabled holds the default value on creation for the "totp_enabled" field.
|
||||
DefaultTotpEnabled bool
|
||||
// DefaultSoraStorageQuotaBytes holds the default value on creation for the "sora_storage_quota_bytes" field.
|
||||
DefaultSoraStorageQuotaBytes int64
|
||||
// DefaultSoraStorageUsedBytes holds the default value on creation for the "sora_storage_used_bytes" field.
|
||||
DefaultSoraStorageUsedBytes int64
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the User queries.
|
||||
@@ -288,6 +298,16 @@ func ByTotpEnabledAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTotpEnabledAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySoraStorageQuotaBytes orders the results by the sora_storage_quota_bytes field.
|
||||
func BySoraStorageQuotaBytes(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSoraStorageQuotaBytes, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// BySoraStorageUsedBytes orders the results by the sora_storage_used_bytes field.
|
||||
func BySoraStorageUsedBytes(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldSoraStorageUsedBytes, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByAPIKeysCount orders the results by api_keys count.
|
||||
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
|
||||
@@ -125,6 +125,16 @@ func TotpEnabledAt(v time.Time) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldTotpEnabledAt, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytes applies equality check predicate on the "sora_storage_quota_bytes" field. It's identical to SoraStorageQuotaBytesEQ.
|
||||
func SoraStorageQuotaBytes(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytes applies equality check predicate on the "sora_storage_used_bytes" field. It's identical to SoraStorageUsedBytesEQ.
|
||||
func SoraStorageUsedBytes(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
||||
@@ -860,6 +870,86 @@ func TotpEnabledAtNotNil() predicate.User {
|
||||
return predicate.User(sql.FieldNotNull(FieldTotpEnabledAt))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesEQ applies the EQ predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesEQ(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesNEQ applies the NEQ predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesNEQ(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldNEQ(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesIn applies the In predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesIn(vs ...int64) predicate.User {
|
||||
return predicate.User(sql.FieldIn(FieldSoraStorageQuotaBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesNotIn applies the NotIn predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesNotIn(vs ...int64) predicate.User {
|
||||
return predicate.User(sql.FieldNotIn(FieldSoraStorageQuotaBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesGT applies the GT predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesGT(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldGT(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesGTE applies the GTE predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesGTE(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldGTE(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesLT applies the LT predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesLT(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldLT(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageQuotaBytesLTE applies the LTE predicate on the "sora_storage_quota_bytes" field.
|
||||
func SoraStorageQuotaBytesLTE(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldLTE(FieldSoraStorageQuotaBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesEQ applies the EQ predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesEQ(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldEQ(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesNEQ applies the NEQ predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesNEQ(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldNEQ(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesIn applies the In predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesIn(vs ...int64) predicate.User {
|
||||
return predicate.User(sql.FieldIn(FieldSoraStorageUsedBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesNotIn applies the NotIn predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesNotIn(vs ...int64) predicate.User {
|
||||
return predicate.User(sql.FieldNotIn(FieldSoraStorageUsedBytes, vs...))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesGT applies the GT predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesGT(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldGT(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesGTE applies the GTE predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesGTE(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldGTE(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesLT applies the LT predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesLT(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldLT(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// SoraStorageUsedBytesLTE applies the LTE predicate on the "sora_storage_used_bytes" field.
|
||||
func SoraStorageUsedBytesLTE(v int64) predicate.User {
|
||||
return predicate.User(sql.FieldLTE(FieldSoraStorageUsedBytes, v))
|
||||
}
|
||||
|
||||
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
||||
func HasAPIKeys() predicate.User {
|
||||
return predicate.User(func(s *sql.Selector) {
|
||||
|
||||
@@ -210,6 +210,34 @@ func (_c *UserCreate) SetNillableTotpEnabledAt(v *time.Time) *UserCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_c *UserCreate) SetSoraStorageQuotaBytes(v int64) *UserCreate {
|
||||
_c.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_c *UserCreate) SetNillableSoraStorageQuotaBytes(v *int64) *UserCreate {
|
||||
if v != nil {
|
||||
_c.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (_c *UserCreate) SetSoraStorageUsedBytes(v int64) *UserCreate {
|
||||
_c.mutation.SetSoraStorageUsedBytes(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageUsedBytes sets the "sora_storage_used_bytes" field if the given value is not nil.
|
||||
func (_c *UserCreate) SetNillableSoraStorageUsedBytes(v *int64) *UserCreate {
|
||||
if v != nil {
|
||||
_c.SetSoraStorageUsedBytes(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||
func (_c *UserCreate) AddAPIKeyIDs(ids ...int64) *UserCreate {
|
||||
_c.mutation.AddAPIKeyIDs(ids...)
|
||||
@@ -424,6 +452,14 @@ func (_c *UserCreate) defaults() error {
|
||||
v := user.DefaultTotpEnabled
|
||||
_c.mutation.SetTotpEnabled(v)
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageQuotaBytes(); !ok {
|
||||
v := user.DefaultSoraStorageQuotaBytes
|
||||
_c.mutation.SetSoraStorageQuotaBytes(v)
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageUsedBytes(); !ok {
|
||||
v := user.DefaultSoraStorageUsedBytes
|
||||
_c.mutation.SetSoraStorageUsedBytes(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -487,6 +523,12 @@ func (_c *UserCreate) check() error {
|
||||
if _, ok := _c.mutation.TotpEnabled(); !ok {
|
||||
return &ValidationError{Name: "totp_enabled", err: errors.New(`ent: missing required field "User.totp_enabled"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageQuotaBytes(); !ok {
|
||||
return &ValidationError{Name: "sora_storage_quota_bytes", err: errors.New(`ent: missing required field "User.sora_storage_quota_bytes"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.SoraStorageUsedBytes(); !ok {
|
||||
return &ValidationError{Name: "sora_storage_used_bytes", err: errors.New(`ent: missing required field "User.sora_storage_used_bytes"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -570,6 +612,14 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(user.FieldTotpEnabledAt, field.TypeTime, value)
|
||||
_node.TotpEnabledAt = &value
|
||||
}
|
||||
if value, ok := _c.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
_node.SoraStorageQuotaBytes = value
|
||||
}
|
||||
if value, ok := _c.mutation.SoraStorageUsedBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageUsedBytes, field.TypeInt64, value)
|
||||
_node.SoraStorageUsedBytes = value
|
||||
}
|
||||
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
@@ -956,6 +1006,42 @@ func (u *UserUpsert) ClearTotpEnabledAt() *UserUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsert) SetSoraStorageQuotaBytes(v int64) *UserUpsert {
|
||||
u.Set(user.FieldSoraStorageQuotaBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsert) UpdateSoraStorageQuotaBytes() *UserUpsert {
|
||||
u.SetExcluded(user.FieldSoraStorageQuotaBytes)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsert) AddSoraStorageQuotaBytes(v int64) *UserUpsert {
|
||||
u.Add(user.FieldSoraStorageQuotaBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsert) SetSoraStorageUsedBytes(v int64) *UserUpsert {
|
||||
u.Set(user.FieldSoraStorageUsedBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateSoraStorageUsedBytes sets the "sora_storage_used_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsert) UpdateSoraStorageUsedBytes() *UserUpsert {
|
||||
u.SetExcluded(user.FieldSoraStorageUsedBytes)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddSoraStorageUsedBytes adds v to the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsert) AddSoraStorageUsedBytes(v int64) *UserUpsert {
|
||||
u.Add(user.FieldSoraStorageUsedBytes, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
@@ -1218,6 +1304,48 @@ func (u *UserUpsertOne) ClearTotpEnabledAt() *UserUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsertOne) SetSoraStorageQuotaBytes(v int64) *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.SetSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsertOne) AddSoraStorageQuotaBytes(v int64) *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.AddSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsertOne) UpdateSoraStorageQuotaBytes() *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.UpdateSoraStorageQuotaBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsertOne) SetSoraStorageUsedBytes(v int64) *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.SetSoraStorageUsedBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageUsedBytes adds v to the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsertOne) AddSoraStorageUsedBytes(v int64) *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.AddSoraStorageUsedBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageUsedBytes sets the "sora_storage_used_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsertOne) UpdateSoraStorageUsedBytes() *UserUpsertOne {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.UpdateSoraStorageUsedBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *UserUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
@@ -1646,6 +1774,48 @@ func (u *UserUpsertBulk) ClearTotpEnabledAt() *UserUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsertBulk) SetSoraStorageQuotaBytes(v int64) *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.SetSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds v to the "sora_storage_quota_bytes" field.
|
||||
func (u *UserUpsertBulk) AddSoraStorageQuotaBytes(v int64) *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.AddSoraStorageQuotaBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsertBulk) UpdateSoraStorageQuotaBytes() *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.UpdateSoraStorageQuotaBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsertBulk) SetSoraStorageUsedBytes(v int64) *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.SetSoraStorageUsedBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddSoraStorageUsedBytes adds v to the "sora_storage_used_bytes" field.
|
||||
func (u *UserUpsertBulk) AddSoraStorageUsedBytes(v int64) *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.AddSoraStorageUsedBytes(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraStorageUsedBytes sets the "sora_storage_used_bytes" field to the value that was provided on create.
|
||||
func (u *UserUpsertBulk) UpdateSoraStorageUsedBytes() *UserUpsertBulk {
|
||||
return u.Update(func(s *UserUpsert) {
|
||||
s.UpdateSoraStorageUsedBytes()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *UserUpsertBulk) Exec(ctx context.Context) error {
|
||||
if u.create.err != nil {
|
||||
|
||||
@@ -242,6 +242,48 @@ func (_u *UserUpdate) ClearTotpEnabledAt() *UserUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_u *UserUpdate) SetSoraStorageQuotaBytes(v int64) *UserUpdate {
|
||||
_u.mutation.ResetSoraStorageQuotaBytes()
|
||||
_u.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_u *UserUpdate) SetNillableSoraStorageQuotaBytes(v *int64) *UserUpdate {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds value to the "sora_storage_quota_bytes" field.
|
||||
func (_u *UserUpdate) AddSoraStorageQuotaBytes(v int64) *UserUpdate {
|
||||
_u.mutation.AddSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (_u *UserUpdate) SetSoraStorageUsedBytes(v int64) *UserUpdate {
|
||||
_u.mutation.ResetSoraStorageUsedBytes()
|
||||
_u.mutation.SetSoraStorageUsedBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageUsedBytes sets the "sora_storage_used_bytes" field if the given value is not nil.
|
||||
func (_u *UserUpdate) SetNillableSoraStorageUsedBytes(v *int64) *UserUpdate {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageUsedBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageUsedBytes adds value to the "sora_storage_used_bytes" field.
|
||||
func (_u *UserUpdate) AddSoraStorageUsedBytes(v int64) *UserUpdate {
|
||||
_u.mutation.AddSoraStorageUsedBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||
func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate {
|
||||
_u.mutation.AddAPIKeyIDs(ids...)
|
||||
@@ -709,6 +751,18 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.TotpEnabledAtCleared() {
|
||||
_spec.ClearField(user.FieldTotpEnabledAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageQuotaBytes(); ok {
|
||||
_spec.AddField(user.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageUsedBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageUsedBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageUsedBytes(); ok {
|
||||
_spec.AddField(user.FieldSoraStorageUsedBytes, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.APIKeysCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
@@ -1352,6 +1406,48 @@ func (_u *UserUpdateOne) ClearTotpEnabledAt() *UserUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field.
|
||||
func (_u *UserUpdateOne) SetSoraStorageQuotaBytes(v int64) *UserUpdateOne {
|
||||
_u.mutation.ResetSoraStorageQuotaBytes()
|
||||
_u.mutation.SetSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageQuotaBytes sets the "sora_storage_quota_bytes" field if the given value is not nil.
|
||||
func (_u *UserUpdateOne) SetNillableSoraStorageQuotaBytes(v *int64) *UserUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageQuotaBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageQuotaBytes adds value to the "sora_storage_quota_bytes" field.
|
||||
func (_u *UserUpdateOne) AddSoraStorageQuotaBytes(v int64) *UserUpdateOne {
|
||||
_u.mutation.AddSoraStorageQuotaBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetSoraStorageUsedBytes sets the "sora_storage_used_bytes" field.
|
||||
func (_u *UserUpdateOne) SetSoraStorageUsedBytes(v int64) *UserUpdateOne {
|
||||
_u.mutation.ResetSoraStorageUsedBytes()
|
||||
_u.mutation.SetSoraStorageUsedBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableSoraStorageUsedBytes sets the "sora_storage_used_bytes" field if the given value is not nil.
|
||||
func (_u *UserUpdateOne) SetNillableSoraStorageUsedBytes(v *int64) *UserUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetSoraStorageUsedBytes(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddSoraStorageUsedBytes adds value to the "sora_storage_used_bytes" field.
|
||||
func (_u *UserUpdateOne) AddSoraStorageUsedBytes(v int64) *UserUpdateOne {
|
||||
_u.mutation.AddSoraStorageUsedBytes(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||
func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne {
|
||||
_u.mutation.AddAPIKeyIDs(ids...)
|
||||
@@ -1849,6 +1945,18 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
||||
if _u.mutation.TotpEnabledAtCleared() {
|
||||
_spec.ClearField(user.FieldTotpEnabledAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageQuotaBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageQuotaBytes(); ok {
|
||||
_spec.AddField(user.FieldSoraStorageQuotaBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.SoraStorageUsedBytes(); ok {
|
||||
_spec.SetField(user.FieldSoraStorageUsedBytes, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedSoraStorageUsedBytes(); ok {
|
||||
_spec.AddField(user.FieldSoraStorageUsedBytes, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.APIKeysCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
|
||||
@@ -7,7 +7,11 @@ require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/DouDOU-start/go-sora2api v1.1.0
|
||||
github.com/alitto/pond/v2 v2.6.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/dgraph-io/ristretto v0.2.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
@@ -34,6 +38,8 @@ require (
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/term v0.40.0
|
||||
google.golang.org/grpc v1.75.1
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.44.3
|
||||
@@ -47,6 +53,22 @@ require (
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||
github.com/aws/smithy-go v1.24.1 // indirect
|
||||
github.com/bdandy/go-errors v1.2.2 // indirect
|
||||
github.com/bdandy/go-socks4 v1.2.3 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
@@ -87,6 +109,7 @@ require (
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/subcommands v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||
@@ -146,7 +169,6 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
@@ -156,8 +178,8 @@ require (
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
||||
@@ -22,6 +22,44 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
||||
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
|
||||
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
|
||||
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
|
||||
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
||||
@@ -56,6 +94,12 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
@@ -80,6 +124,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
@@ -136,6 +182,7 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
@@ -155,6 +202,8 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
||||
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@@ -190,6 +239,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
@@ -223,6 +274,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -233,6 +286,10 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo=
|
||||
github.com/pkoukk/tiktoken-go v0.1.8/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
github.com/pkoukk/tiktoken-go-loader v0.0.2 h1:LUKws63GV3pVHwH1srkBplBv+7URgmOmhSkRxsIvsK4=
|
||||
github.com/pkoukk/tiktoken-go-loader v0.0.2/go.mod h1:4mIkYyZooFlnenDlormIo6cd5wrlUKNr97wp9nGgEKo=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -274,6 +331,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
|
||||
@@ -30,6 +30,14 @@ const (
|
||||
// __CSP_NONCE__ will be replaced with actual nonce at request time by the SecurityHeaders middleware
|
||||
const DefaultCSPPolicy = "default-src 'self'; script-src 'self' __CSP_NONCE__ https://challenges.cloudflare.com https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:; frame-src https://challenges.cloudflare.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
||||
|
||||
// UMQ(用户消息队列)模式常量
|
||||
const (
|
||||
// UMQModeSerialize: 账号级串行锁 + RPM 自适应延迟
|
||||
UMQModeSerialize = "serialize"
|
||||
// UMQModeThrottle: 仅 RPM 自适应前置延迟,不阻塞并发
|
||||
UMQModeThrottle = "throttle"
|
||||
)
|
||||
|
||||
// 连接池隔离策略常量
|
||||
// 用于控制上游 HTTP 连接池的隔离粒度,影响连接复用和资源消耗
|
||||
const (
|
||||
@@ -265,8 +273,13 @@ type CSPConfig struct {
|
||||
}
|
||||
|
||||
type ProxyFallbackConfig struct {
|
||||
// AllowDirectOnError 当代理初始化失败时是否允许回退直连。
|
||||
// 默认 false:避免因代理配置错误导致 IP 泄露/关联。
|
||||
// AllowDirectOnError 当辅助服务的代理初始化失败时是否允许回退直连。
|
||||
// 仅影响以下非 AI 账号连接的辅助服务:
|
||||
// - GitHub Release 更新检查
|
||||
// - 定价数据拉取
|
||||
// 不影响 AI 账号网关连接(Claude/OpenAI/Gemini/Antigravity),
|
||||
// 这些关键路径的代理失败始终返回错误,不会回退直连。
|
||||
// 默认 false:避免因代理配置错误导致服务器真实 IP 泄露。
|
||||
AllowDirectOnError bool `mapstructure:"allow_direct_on_error"`
|
||||
}
|
||||
|
||||
@@ -364,6 +377,8 @@ type GatewayConfig struct {
|
||||
// OpenAIPassthroughAllowTimeoutHeaders: OpenAI 透传模式是否放行客户端超时头
|
||||
// 关闭(默认)可避免 x-stainless-timeout 等头导致上游提前断流。
|
||||
OpenAIPassthroughAllowTimeoutHeaders bool `mapstructure:"openai_passthrough_allow_timeout_headers"`
|
||||
// OpenAIWS: OpenAI Responses WebSocket 配置(默认开启,可按需回滚到 HTTP)
|
||||
OpenAIWS GatewayOpenAIWSConfig `mapstructure:"openai_ws"`
|
||||
|
||||
// HTTP 上游连接池配置(性能优化:支持高并发场景调优)
|
||||
// MaxIdleConns: 所有主机的最大空闲连接总数
|
||||
@@ -448,6 +463,147 @@ type GatewayConfig struct {
|
||||
UserGroupRateCacheTTLSeconds int `mapstructure:"user_group_rate_cache_ttl_seconds"`
|
||||
// ModelsListCacheTTLSeconds: /v1/models 模型列表短缓存 TTL(秒)
|
||||
ModelsListCacheTTLSeconds int `mapstructure:"models_list_cache_ttl_seconds"`
|
||||
|
||||
// UserMessageQueue: 用户消息串行队列配置
|
||||
// 对 role:"user" 的真实用户消息实施账号级串行化 + RPM 自适应延迟
|
||||
UserMessageQueue UserMessageQueueConfig `mapstructure:"user_message_queue"`
|
||||
}
|
||||
|
||||
// UserMessageQueueConfig 用户消息串行队列配置
|
||||
// 用于 Anthropic OAuth/SetupToken 账号的用户消息串行化发送
|
||||
type UserMessageQueueConfig struct {
|
||||
// Mode: 模式选择
|
||||
// "serialize" = 账号级串行锁 + RPM 自适应延迟
|
||||
// "throttle" = 仅 RPM 自适应前置延迟,不阻塞并发
|
||||
// "" = 禁用(默认)
|
||||
Mode string `mapstructure:"mode"`
|
||||
// Enabled: 已废弃,仅向后兼容(等同于 mode: "serialize")
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
// LockTTLMs: 串行锁 TTL(毫秒),应大于最长请求时间
|
||||
LockTTLMs int `mapstructure:"lock_ttl_ms"`
|
||||
// WaitTimeoutMs: 等待获取锁的超时时间(毫秒)
|
||||
WaitTimeoutMs int `mapstructure:"wait_timeout_ms"`
|
||||
// MinDelayMs: RPM 自适应延迟下限(毫秒)
|
||||
MinDelayMs int `mapstructure:"min_delay_ms"`
|
||||
// MaxDelayMs: RPM 自适应延迟上限(毫秒)
|
||||
MaxDelayMs int `mapstructure:"max_delay_ms"`
|
||||
// CleanupIntervalSeconds: 孤儿锁清理间隔(秒),0 表示禁用
|
||||
CleanupIntervalSeconds int `mapstructure:"cleanup_interval_seconds"`
|
||||
}
|
||||
|
||||
// WaitTimeout 返回等待超时的 time.Duration
|
||||
func (c *UserMessageQueueConfig) WaitTimeout() time.Duration {
|
||||
if c.WaitTimeoutMs <= 0 {
|
||||
return 30 * time.Second
|
||||
}
|
||||
return time.Duration(c.WaitTimeoutMs) * time.Millisecond
|
||||
}
|
||||
|
||||
// GetEffectiveMode 返回生效的模式
|
||||
// 注意:Mode 字段已在 load() 中做过白名单校验和规范化,此处无需重复验证
|
||||
func (c *UserMessageQueueConfig) GetEffectiveMode() string {
|
||||
if c.Mode == UMQModeSerialize || c.Mode == UMQModeThrottle {
|
||||
return c.Mode
|
||||
}
|
||||
if c.Enabled {
|
||||
return UMQModeSerialize // 向后兼容
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GatewayOpenAIWSConfig OpenAI Responses WebSocket 配置。
|
||||
// 注意:默认全局开启;如需回滚可使用 force_http 或关闭 enabled。
|
||||
type GatewayOpenAIWSConfig struct {
|
||||
// ModeRouterV2Enabled: 新版 WS mode 路由开关(默认 false;关闭时保持 legacy 行为)
|
||||
ModeRouterV2Enabled bool `mapstructure:"mode_router_v2_enabled"`
|
||||
// IngressModeDefault: ingress 默认模式(off/ctx_pool/passthrough)
|
||||
IngressModeDefault string `mapstructure:"ingress_mode_default"`
|
||||
// Enabled: 全局总开关(默认 true)
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
// OAuthEnabled: 是否允许 OpenAI OAuth 账号使用 WS
|
||||
OAuthEnabled bool `mapstructure:"oauth_enabled"`
|
||||
// APIKeyEnabled: 是否允许 OpenAI API Key 账号使用 WS
|
||||
APIKeyEnabled bool `mapstructure:"apikey_enabled"`
|
||||
// ForceHTTP: 全局强制 HTTP(用于紧急回滚)
|
||||
ForceHTTP bool `mapstructure:"force_http"`
|
||||
// AllowStoreRecovery: 允许在 WSv2 下按策略恢复 store=true(默认 false)
|
||||
AllowStoreRecovery bool `mapstructure:"allow_store_recovery"`
|
||||
// IngressPreviousResponseRecoveryEnabled: ingress 模式收到 previous_response_not_found 时,是否允许自动去掉 previous_response_id 重试一次(默认 true)
|
||||
IngressPreviousResponseRecoveryEnabled bool `mapstructure:"ingress_previous_response_recovery_enabled"`
|
||||
// StoreDisabledConnMode: store=false 且无可复用会话连接时的建连策略(strict/adaptive/off)
|
||||
// - strict: 强制新建连接(隔离优先)
|
||||
// - adaptive: 仅在高风险失败后强制新建连接(性能与隔离折中)
|
||||
// - off: 不强制新建连接(复用优先)
|
||||
StoreDisabledConnMode string `mapstructure:"store_disabled_conn_mode"`
|
||||
// StoreDisabledForceNewConn: store=false 且无可复用粘连连接时是否强制新建连接(默认 true,保障会话隔离)
|
||||
// 兼容旧配置;当 StoreDisabledConnMode 为空时才生效。
|
||||
StoreDisabledForceNewConn bool `mapstructure:"store_disabled_force_new_conn"`
|
||||
// PrewarmGenerateEnabled: 是否启用 WSv2 generate=false 预热(默认 false)
|
||||
PrewarmGenerateEnabled bool `mapstructure:"prewarm_generate_enabled"`
|
||||
|
||||
// Feature 开关:v2 优先于 v1
|
||||
ResponsesWebsockets bool `mapstructure:"responses_websockets"`
|
||||
ResponsesWebsocketsV2 bool `mapstructure:"responses_websockets_v2"`
|
||||
|
||||
// 连接池参数
|
||||
MaxConnsPerAccount int `mapstructure:"max_conns_per_account"`
|
||||
MinIdlePerAccount int `mapstructure:"min_idle_per_account"`
|
||||
MaxIdlePerAccount int `mapstructure:"max_idle_per_account"`
|
||||
// DynamicMaxConnsByAccountConcurrencyEnabled: 是否按账号并发动态计算连接池上限
|
||||
DynamicMaxConnsByAccountConcurrencyEnabled bool `mapstructure:"dynamic_max_conns_by_account_concurrency_enabled"`
|
||||
// OAuthMaxConnsFactor: OAuth 账号连接池系数(effective=ceil(concurrency*factor))
|
||||
OAuthMaxConnsFactor float64 `mapstructure:"oauth_max_conns_factor"`
|
||||
// APIKeyMaxConnsFactor: API Key 账号连接池系数(effective=ceil(concurrency*factor))
|
||||
APIKeyMaxConnsFactor float64 `mapstructure:"apikey_max_conns_factor"`
|
||||
DialTimeoutSeconds int `mapstructure:"dial_timeout_seconds"`
|
||||
ReadTimeoutSeconds int `mapstructure:"read_timeout_seconds"`
|
||||
WriteTimeoutSeconds int `mapstructure:"write_timeout_seconds"`
|
||||
PoolTargetUtilization float64 `mapstructure:"pool_target_utilization"`
|
||||
QueueLimitPerConn int `mapstructure:"queue_limit_per_conn"`
|
||||
// EventFlushBatchSize: WS 流式写出批量 flush 阈值(事件条数)
|
||||
EventFlushBatchSize int `mapstructure:"event_flush_batch_size"`
|
||||
// EventFlushIntervalMS: WS 流式写出最大等待时间(毫秒);0 表示仅按 batch 触发
|
||||
EventFlushIntervalMS int `mapstructure:"event_flush_interval_ms"`
|
||||
// PrewarmCooldownMS: 连接池预热触发冷却时间(毫秒)
|
||||
PrewarmCooldownMS int `mapstructure:"prewarm_cooldown_ms"`
|
||||
// FallbackCooldownSeconds: WS 回退冷却窗口,避免 WS/HTTP 抖动;0 表示关闭冷却
|
||||
FallbackCooldownSeconds int `mapstructure:"fallback_cooldown_seconds"`
|
||||
// RetryBackoffInitialMS: WS 重试初始退避(毫秒);<=0 表示关闭退避
|
||||
RetryBackoffInitialMS int `mapstructure:"retry_backoff_initial_ms"`
|
||||
// RetryBackoffMaxMS: WS 重试最大退避(毫秒)
|
||||
RetryBackoffMaxMS int `mapstructure:"retry_backoff_max_ms"`
|
||||
// RetryJitterRatio: WS 重试退避抖动比例(0-1)
|
||||
RetryJitterRatio float64 `mapstructure:"retry_jitter_ratio"`
|
||||
// RetryTotalBudgetMS: WS 单次请求重试总预算(毫秒);0 表示关闭预算限制
|
||||
RetryTotalBudgetMS int `mapstructure:"retry_total_budget_ms"`
|
||||
// PayloadLogSampleRate: payload_schema 日志采样率(0-1)
|
||||
PayloadLogSampleRate float64 `mapstructure:"payload_log_sample_rate"`
|
||||
|
||||
// 账号调度与粘连参数
|
||||
LBTopK int `mapstructure:"lb_top_k"`
|
||||
// StickySessionTTLSeconds: session_hash -> account_id 粘连 TTL
|
||||
StickySessionTTLSeconds int `mapstructure:"sticky_session_ttl_seconds"`
|
||||
// SessionHashReadOldFallback: 会话哈希迁移期是否允许“新 key 未命中时回退读旧 SHA-256 key”
|
||||
SessionHashReadOldFallback bool `mapstructure:"session_hash_read_old_fallback"`
|
||||
// SessionHashDualWriteOld: 会话哈希迁移期是否双写旧 SHA-256 key(短 TTL)
|
||||
SessionHashDualWriteOld bool `mapstructure:"session_hash_dual_write_old"`
|
||||
// MetadataBridgeEnabled: RequestMetadata 迁移期是否保留旧 ctxkey.* 兼容桥接
|
||||
MetadataBridgeEnabled bool `mapstructure:"metadata_bridge_enabled"`
|
||||
// StickyResponseIDTTLSeconds: response_id -> account_id 粘连 TTL
|
||||
StickyResponseIDTTLSeconds int `mapstructure:"sticky_response_id_ttl_seconds"`
|
||||
// StickyPreviousResponseTTLSeconds: 兼容旧键(当新键未设置时回退)
|
||||
StickyPreviousResponseTTLSeconds int `mapstructure:"sticky_previous_response_ttl_seconds"`
|
||||
|
||||
SchedulerScoreWeights GatewayOpenAIWSSchedulerScoreWeights `mapstructure:"scheduler_score_weights"`
|
||||
}
|
||||
|
||||
// GatewayOpenAIWSSchedulerScoreWeights 账号调度打分权重。
|
||||
type GatewayOpenAIWSSchedulerScoreWeights struct {
|
||||
Priority float64 `mapstructure:"priority"`
|
||||
Load float64 `mapstructure:"load"`
|
||||
Queue float64 `mapstructure:"queue"`
|
||||
ErrorRate float64 `mapstructure:"error_rate"`
|
||||
TTFT float64 `mapstructure:"ttft"`
|
||||
}
|
||||
|
||||
// GatewayUsageRecordConfig 使用量记录异步队列配置
|
||||
@@ -716,7 +872,8 @@ type DefaultConfig struct {
|
||||
}
|
||||
|
||||
type RateLimitConfig struct {
|
||||
OverloadCooldownMinutes int `mapstructure:"overload_cooldown_minutes"` // 529过载冷却时间(分钟)
|
||||
OverloadCooldownMinutes int `mapstructure:"overload_cooldown_minutes"` // 529过载冷却时间(分钟)
|
||||
OAuth401CooldownMinutes int `mapstructure:"oauth_401_cooldown_minutes"` // OAuth 401临时不可调度冷却(分钟)
|
||||
}
|
||||
|
||||
// APIKeyAuthCacheConfig API Key 认证缓存配置
|
||||
@@ -886,6 +1043,20 @@ func load(allowMissingJWTSecret bool) (*Config, error) {
|
||||
cfg.Log.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.Log.StacktraceLevel))
|
||||
cfg.Log.Output.FilePath = strings.TrimSpace(cfg.Log.Output.FilePath)
|
||||
|
||||
// 兼容旧键 gateway.openai_ws.sticky_previous_response_ttl_seconds。
|
||||
// 新键未配置(<=0)时回退旧键;新键优先。
|
||||
if cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds <= 0 && cfg.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds > 0 {
|
||||
cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds = cfg.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds
|
||||
}
|
||||
|
||||
// Normalize UMQ mode: 白名单校验,非法值在加载时一次性 warn 并清空
|
||||
if m := cfg.Gateway.UserMessageQueue.Mode; m != "" && m != UMQModeSerialize && m != UMQModeThrottle {
|
||||
slog.Warn("invalid user_message_queue mode, disabling",
|
||||
"mode", m,
|
||||
"valid_modes", []string{UMQModeSerialize, UMQModeThrottle})
|
||||
cfg.Gateway.UserMessageQueue.Mode = ""
|
||||
}
|
||||
|
||||
// Auto-generate TOTP encryption key if not set (32 bytes = 64 hex chars for AES-256)
|
||||
cfg.Totp.EncryptionKey = strings.TrimSpace(cfg.Totp.EncryptionKey)
|
||||
if cfg.Totp.EncryptionKey == "" {
|
||||
@@ -945,7 +1116,7 @@ func setDefaults() {
|
||||
viper.SetDefault("server.read_header_timeout", 30) // 30秒读取请求头
|
||||
viper.SetDefault("server.idle_timeout", 120) // 120秒空闲超时
|
||||
viper.SetDefault("server.trusted_proxies", []string{})
|
||||
viper.SetDefault("server.max_request_body_size", int64(100*1024*1024))
|
||||
viper.SetDefault("server.max_request_body_size", int64(256*1024*1024))
|
||||
// H2C 默认配置
|
||||
viper.SetDefault("server.h2c.enabled", false)
|
||||
viper.SetDefault("server.h2c.max_concurrent_streams", uint32(50)) // 50 个并发流
|
||||
@@ -1002,6 +1173,9 @@ func setDefaults() {
|
||||
viper.SetDefault("security.csp.policy", DefaultCSPPolicy)
|
||||
viper.SetDefault("security.proxy_probe.insecure_skip_verify", false)
|
||||
|
||||
// Security - disable direct fallback on proxy error
|
||||
viper.SetDefault("security.proxy_fallback.allow_direct_on_error", false)
|
||||
|
||||
// Billing
|
||||
viper.SetDefault("billing.circuit_breaker.enabled", true)
|
||||
viper.SetDefault("billing.circuit_breaker.failure_threshold", 5)
|
||||
@@ -1053,7 +1227,7 @@ func setDefaults() {
|
||||
|
||||
// Ops (vNext)
|
||||
viper.SetDefault("ops.enabled", true)
|
||||
viper.SetDefault("ops.use_preaggregated_tables", false)
|
||||
viper.SetDefault("ops.use_preaggregated_tables", true)
|
||||
viper.SetDefault("ops.cleanup.enabled", true)
|
||||
viper.SetDefault("ops.cleanup.schedule", "0 2 * * *")
|
||||
// Retention days: vNext defaults to 30 days across ops datasets.
|
||||
@@ -1087,10 +1261,11 @@ func setDefaults() {
|
||||
|
||||
// RateLimit
|
||||
viper.SetDefault("rate_limit.overload_cooldown_minutes", 10)
|
||||
viper.SetDefault("rate_limit.oauth_401_cooldown_minutes", 10)
|
||||
|
||||
// Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据的配置
|
||||
viper.SetDefault("pricing.remote_url", "https://github.com/Wei-Shaw/model-price-repo/raw/refs/heads/main/model_prices_and_context_window.json")
|
||||
viper.SetDefault("pricing.hash_url", "https://github.com/Wei-Shaw/model-price-repo/raw/refs/heads/main/model_prices_and_context_window.sha256")
|
||||
// Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据(固定到 commit,避免分支漂移)
|
||||
viper.SetDefault("pricing.remote_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.json")
|
||||
viper.SetDefault("pricing.hash_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.sha256")
|
||||
viper.SetDefault("pricing.data_dir", "./data")
|
||||
viper.SetDefault("pricing.fallback_file", "./resources/model-pricing/model_prices_and_context_window.json")
|
||||
viper.SetDefault("pricing.update_interval_hours", 24)
|
||||
@@ -1157,9 +1332,55 @@ func setDefaults() {
|
||||
viper.SetDefault("gateway.max_account_switches_gemini", 3)
|
||||
viper.SetDefault("gateway.force_codex_cli", false)
|
||||
viper.SetDefault("gateway.openai_passthrough_allow_timeout_headers", false)
|
||||
// OpenAI Responses WebSocket(默认开启;可通过 force_http 紧急回滚)
|
||||
viper.SetDefault("gateway.openai_ws.enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.mode_router_v2_enabled", false)
|
||||
viper.SetDefault("gateway.openai_ws.ingress_mode_default", "ctx_pool")
|
||||
viper.SetDefault("gateway.openai_ws.oauth_enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.apikey_enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.force_http", false)
|
||||
viper.SetDefault("gateway.openai_ws.allow_store_recovery", false)
|
||||
viper.SetDefault("gateway.openai_ws.ingress_previous_response_recovery_enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.store_disabled_conn_mode", "strict")
|
||||
viper.SetDefault("gateway.openai_ws.store_disabled_force_new_conn", true)
|
||||
viper.SetDefault("gateway.openai_ws.prewarm_generate_enabled", false)
|
||||
viper.SetDefault("gateway.openai_ws.responses_websockets", false)
|
||||
viper.SetDefault("gateway.openai_ws.responses_websockets_v2", true)
|
||||
viper.SetDefault("gateway.openai_ws.max_conns_per_account", 128)
|
||||
viper.SetDefault("gateway.openai_ws.min_idle_per_account", 4)
|
||||
viper.SetDefault("gateway.openai_ws.max_idle_per_account", 12)
|
||||
viper.SetDefault("gateway.openai_ws.dynamic_max_conns_by_account_concurrency_enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.oauth_max_conns_factor", 1.0)
|
||||
viper.SetDefault("gateway.openai_ws.apikey_max_conns_factor", 1.0)
|
||||
viper.SetDefault("gateway.openai_ws.dial_timeout_seconds", 10)
|
||||
viper.SetDefault("gateway.openai_ws.read_timeout_seconds", 900)
|
||||
viper.SetDefault("gateway.openai_ws.write_timeout_seconds", 120)
|
||||
viper.SetDefault("gateway.openai_ws.pool_target_utilization", 0.7)
|
||||
viper.SetDefault("gateway.openai_ws.queue_limit_per_conn", 64)
|
||||
viper.SetDefault("gateway.openai_ws.event_flush_batch_size", 1)
|
||||
viper.SetDefault("gateway.openai_ws.event_flush_interval_ms", 10)
|
||||
viper.SetDefault("gateway.openai_ws.prewarm_cooldown_ms", 300)
|
||||
viper.SetDefault("gateway.openai_ws.fallback_cooldown_seconds", 30)
|
||||
viper.SetDefault("gateway.openai_ws.retry_backoff_initial_ms", 120)
|
||||
viper.SetDefault("gateway.openai_ws.retry_backoff_max_ms", 2000)
|
||||
viper.SetDefault("gateway.openai_ws.retry_jitter_ratio", 0.2)
|
||||
viper.SetDefault("gateway.openai_ws.retry_total_budget_ms", 5000)
|
||||
viper.SetDefault("gateway.openai_ws.payload_log_sample_rate", 0.2)
|
||||
viper.SetDefault("gateway.openai_ws.lb_top_k", 7)
|
||||
viper.SetDefault("gateway.openai_ws.sticky_session_ttl_seconds", 3600)
|
||||
viper.SetDefault("gateway.openai_ws.session_hash_read_old_fallback", true)
|
||||
viper.SetDefault("gateway.openai_ws.session_hash_dual_write_old", true)
|
||||
viper.SetDefault("gateway.openai_ws.metadata_bridge_enabled", true)
|
||||
viper.SetDefault("gateway.openai_ws.sticky_response_id_ttl_seconds", 3600)
|
||||
viper.SetDefault("gateway.openai_ws.sticky_previous_response_ttl_seconds", 3600)
|
||||
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.priority", 1.0)
|
||||
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.load", 1.0)
|
||||
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.queue", 0.7)
|
||||
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.error_rate", 0.8)
|
||||
viper.SetDefault("gateway.openai_ws.scheduler_score_weights.ttft", 0.5)
|
||||
viper.SetDefault("gateway.antigravity_fallback_cooldown_minutes", 1)
|
||||
viper.SetDefault("gateway.antigravity_extra_retries", 10)
|
||||
viper.SetDefault("gateway.max_body_size", int64(100*1024*1024))
|
||||
viper.SetDefault("gateway.max_body_size", int64(256*1024*1024))
|
||||
viper.SetDefault("gateway.upstream_response_read_max_bytes", int64(8*1024*1024))
|
||||
viper.SetDefault("gateway.proxy_probe_response_read_max_bytes", int64(1024*1024))
|
||||
viper.SetDefault("gateway.gemini_debug_response_headers", false)
|
||||
@@ -1215,6 +1436,14 @@ func setDefaults() {
|
||||
viper.SetDefault("gateway.user_group_rate_cache_ttl_seconds", 30)
|
||||
viper.SetDefault("gateway.models_list_cache_ttl_seconds", 15)
|
||||
// TLS指纹伪装配置(默认关闭,需要账号级别单独启用)
|
||||
// 用户消息串行队列默认值
|
||||
viper.SetDefault("gateway.user_message_queue.enabled", false)
|
||||
viper.SetDefault("gateway.user_message_queue.lock_ttl_ms", 120000)
|
||||
viper.SetDefault("gateway.user_message_queue.wait_timeout_ms", 30000)
|
||||
viper.SetDefault("gateway.user_message_queue.min_delay_ms", 200)
|
||||
viper.SetDefault("gateway.user_message_queue.max_delay_ms", 2000)
|
||||
viper.SetDefault("gateway.user_message_queue.cleanup_interval_seconds", 60)
|
||||
|
||||
viper.SetDefault("gateway.tls_fingerprint.enabled", true)
|
||||
viper.SetDefault("concurrency.ping_interval", 10)
|
||||
|
||||
@@ -1266,9 +1495,6 @@ func setDefaults() {
|
||||
viper.SetDefault("gemini.oauth.scopes", "")
|
||||
viper.SetDefault("gemini.quota.policy", "")
|
||||
|
||||
// Security - proxy fallback
|
||||
viper.SetDefault("security.proxy_fallback.allow_direct_on_error", false)
|
||||
|
||||
// Subscription Maintenance (bounded queue + worker pool)
|
||||
viper.SetDefault("subscription_maintenance.worker_count", 2)
|
||||
viper.SetDefault("subscription_maintenance.queue_size", 1024)
|
||||
@@ -1747,6 +1973,120 @@ func (c *Config) Validate() error {
|
||||
(c.Gateway.StreamKeepaliveInterval < 5 || c.Gateway.StreamKeepaliveInterval > 30) {
|
||||
return fmt.Errorf("gateway.stream_keepalive_interval must be 0 or between 5-30 seconds")
|
||||
}
|
||||
// 兼容旧键 sticky_previous_response_ttl_seconds
|
||||
if c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds <= 0 && c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds > 0 {
|
||||
c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds = c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds
|
||||
}
|
||||
if c.Gateway.OpenAIWS.MaxConnsPerAccount <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.max_conns_per_account must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.MinIdlePerAccount < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.min_idle_per_account must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.MaxIdlePerAccount < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.max_idle_per_account must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.MinIdlePerAccount > c.Gateway.OpenAIWS.MaxIdlePerAccount {
|
||||
return fmt.Errorf("gateway.openai_ws.min_idle_per_account must be <= max_idle_per_account")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.MaxIdlePerAccount > c.Gateway.OpenAIWS.MaxConnsPerAccount {
|
||||
return fmt.Errorf("gateway.openai_ws.max_idle_per_account must be <= max_conns_per_account")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.OAuthMaxConnsFactor <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.oauth_max_conns_factor must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.APIKeyMaxConnsFactor <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.apikey_max_conns_factor must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.DialTimeoutSeconds <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.dial_timeout_seconds must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.ReadTimeoutSeconds <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.read_timeout_seconds must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.WriteTimeoutSeconds <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.write_timeout_seconds must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.PoolTargetUtilization <= 0 || c.Gateway.OpenAIWS.PoolTargetUtilization > 1 {
|
||||
return fmt.Errorf("gateway.openai_ws.pool_target_utilization must be within (0,1]")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.QueueLimitPerConn <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.queue_limit_per_conn must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.EventFlushBatchSize <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.event_flush_batch_size must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.EventFlushIntervalMS < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.event_flush_interval_ms must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.PrewarmCooldownMS < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.prewarm_cooldown_ms must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.FallbackCooldownSeconds < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.fallback_cooldown_seconds must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.RetryBackoffInitialMS < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.retry_backoff_initial_ms must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.RetryBackoffMaxMS < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.retry_backoff_max_ms must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.RetryBackoffInitialMS > 0 && c.Gateway.OpenAIWS.RetryBackoffMaxMS > 0 &&
|
||||
c.Gateway.OpenAIWS.RetryBackoffMaxMS < c.Gateway.OpenAIWS.RetryBackoffInitialMS {
|
||||
return fmt.Errorf("gateway.openai_ws.retry_backoff_max_ms must be >= retry_backoff_initial_ms")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.RetryJitterRatio < 0 || c.Gateway.OpenAIWS.RetryJitterRatio > 1 {
|
||||
return fmt.Errorf("gateway.openai_ws.retry_jitter_ratio must be within [0,1]")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.RetryTotalBudgetMS < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.retry_total_budget_ms must be non-negative")
|
||||
}
|
||||
if mode := strings.ToLower(strings.TrimSpace(c.Gateway.OpenAIWS.IngressModeDefault)); mode != "" {
|
||||
switch mode {
|
||||
case "off", "ctx_pool", "passthrough":
|
||||
case "shared", "dedicated":
|
||||
slog.Warn("gateway.openai_ws.ingress_mode_default is deprecated, treating as ctx_pool; please update to off|ctx_pool|passthrough", "value", mode)
|
||||
default:
|
||||
return fmt.Errorf("gateway.openai_ws.ingress_mode_default must be one of off|ctx_pool|passthrough")
|
||||
}
|
||||
}
|
||||
if mode := strings.ToLower(strings.TrimSpace(c.Gateway.OpenAIWS.StoreDisabledConnMode)); mode != "" {
|
||||
switch mode {
|
||||
case "strict", "adaptive", "off":
|
||||
default:
|
||||
return fmt.Errorf("gateway.openai_ws.store_disabled_conn_mode must be one of strict|adaptive|off")
|
||||
}
|
||||
}
|
||||
if c.Gateway.OpenAIWS.PayloadLogSampleRate < 0 || c.Gateway.OpenAIWS.PayloadLogSampleRate > 1 {
|
||||
return fmt.Errorf("gateway.openai_ws.payload_log_sample_rate must be within [0,1]")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.LBTopK <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.lb_top_k must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.StickySessionTTLSeconds <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.sticky_session_ttl_seconds must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.sticky_response_id_ttl_seconds must be positive")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.sticky_previous_response_ttl_seconds must be non-negative")
|
||||
}
|
||||
if c.Gateway.OpenAIWS.SchedulerScoreWeights.Priority < 0 ||
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Load < 0 ||
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue < 0 ||
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate < 0 ||
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT < 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.scheduler_score_weights.* must be non-negative")
|
||||
}
|
||||
weightSum := c.Gateway.OpenAIWS.SchedulerScoreWeights.Priority +
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Load +
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue +
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate +
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT
|
||||
if weightSum <= 0 {
|
||||
return fmt.Errorf("gateway.openai_ws.scheduler_score_weights must not all be zero")
|
||||
}
|
||||
if c.Gateway.MaxLineSize < 0 {
|
||||
return fmt.Errorf("gateway.max_line_size must be non-negative")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func resetViperWithJWTSecret(t *testing.T) {
|
||||
@@ -75,6 +76,103 @@ func TestLoadDefaultSchedulingConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefaultOpenAIWSConfig(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error: %v", err)
|
||||
}
|
||||
|
||||
if !cfg.Gateway.OpenAIWS.Enabled {
|
||||
t.Fatalf("Gateway.OpenAIWS.Enabled = false, want true")
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.ResponsesWebsocketsV2 {
|
||||
t.Fatalf("Gateway.OpenAIWS.ResponsesWebsocketsV2 = false, want true")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.ResponsesWebsockets {
|
||||
t.Fatalf("Gateway.OpenAIWS.ResponsesWebsockets = true, want false")
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.DynamicMaxConnsByAccountConcurrencyEnabled {
|
||||
t.Fatalf("Gateway.OpenAIWS.DynamicMaxConnsByAccountConcurrencyEnabled = false, want true")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.OAuthMaxConnsFactor != 1.0 {
|
||||
t.Fatalf("Gateway.OpenAIWS.OAuthMaxConnsFactor = %v, want 1.0", cfg.Gateway.OpenAIWS.OAuthMaxConnsFactor)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.APIKeyMaxConnsFactor != 1.0 {
|
||||
t.Fatalf("Gateway.OpenAIWS.APIKeyMaxConnsFactor = %v, want 1.0", cfg.Gateway.OpenAIWS.APIKeyMaxConnsFactor)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.StickySessionTTLSeconds != 3600 {
|
||||
t.Fatalf("Gateway.OpenAIWS.StickySessionTTLSeconds = %d, want 3600", cfg.Gateway.OpenAIWS.StickySessionTTLSeconds)
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.SessionHashReadOldFallback {
|
||||
t.Fatalf("Gateway.OpenAIWS.SessionHashReadOldFallback = false, want true")
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.SessionHashDualWriteOld {
|
||||
t.Fatalf("Gateway.OpenAIWS.SessionHashDualWriteOld = false, want true")
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.MetadataBridgeEnabled {
|
||||
t.Fatalf("Gateway.OpenAIWS.MetadataBridgeEnabled = false, want true")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds != 3600 {
|
||||
t.Fatalf("Gateway.OpenAIWS.StickyResponseIDTTLSeconds = %d, want 3600", cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.FallbackCooldownSeconds != 30 {
|
||||
t.Fatalf("Gateway.OpenAIWS.FallbackCooldownSeconds = %d, want 30", cfg.Gateway.OpenAIWS.FallbackCooldownSeconds)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.EventFlushBatchSize != 1 {
|
||||
t.Fatalf("Gateway.OpenAIWS.EventFlushBatchSize = %d, want 1", cfg.Gateway.OpenAIWS.EventFlushBatchSize)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.EventFlushIntervalMS != 10 {
|
||||
t.Fatalf("Gateway.OpenAIWS.EventFlushIntervalMS = %d, want 10", cfg.Gateway.OpenAIWS.EventFlushIntervalMS)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.PrewarmCooldownMS != 300 {
|
||||
t.Fatalf("Gateway.OpenAIWS.PrewarmCooldownMS = %d, want 300", cfg.Gateway.OpenAIWS.PrewarmCooldownMS)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.RetryBackoffInitialMS != 120 {
|
||||
t.Fatalf("Gateway.OpenAIWS.RetryBackoffInitialMS = %d, want 120", cfg.Gateway.OpenAIWS.RetryBackoffInitialMS)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.RetryBackoffMaxMS != 2000 {
|
||||
t.Fatalf("Gateway.OpenAIWS.RetryBackoffMaxMS = %d, want 2000", cfg.Gateway.OpenAIWS.RetryBackoffMaxMS)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.RetryJitterRatio != 0.2 {
|
||||
t.Fatalf("Gateway.OpenAIWS.RetryJitterRatio = %v, want 0.2", cfg.Gateway.OpenAIWS.RetryJitterRatio)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.RetryTotalBudgetMS != 5000 {
|
||||
t.Fatalf("Gateway.OpenAIWS.RetryTotalBudgetMS = %d, want 5000", cfg.Gateway.OpenAIWS.RetryTotalBudgetMS)
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.PayloadLogSampleRate != 0.2 {
|
||||
t.Fatalf("Gateway.OpenAIWS.PayloadLogSampleRate = %v, want 0.2", cfg.Gateway.OpenAIWS.PayloadLogSampleRate)
|
||||
}
|
||||
if !cfg.Gateway.OpenAIWS.StoreDisabledForceNewConn {
|
||||
t.Fatalf("Gateway.OpenAIWS.StoreDisabledForceNewConn = false, want true")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.StoreDisabledConnMode != "strict" {
|
||||
t.Fatalf("Gateway.OpenAIWS.StoreDisabledConnMode = %q, want %q", cfg.Gateway.OpenAIWS.StoreDisabledConnMode, "strict")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.ModeRouterV2Enabled {
|
||||
t.Fatalf("Gateway.OpenAIWS.ModeRouterV2Enabled = true, want false")
|
||||
}
|
||||
if cfg.Gateway.OpenAIWS.IngressModeDefault != "ctx_pool" {
|
||||
t.Fatalf("Gateway.OpenAIWS.IngressModeDefault = %q, want %q", cfg.Gateway.OpenAIWS.IngressModeDefault, "ctx_pool")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOpenAIWSStickyTTLCompatibility(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
t.Setenv("GATEWAY_OPENAI_WS_STICKY_RESPONSE_ID_TTL_SECONDS", "0")
|
||||
t.Setenv("GATEWAY_OPENAI_WS_STICKY_PREVIOUS_RESPONSE_TTL_SECONDS", "7200")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds != 7200 {
|
||||
t.Fatalf("StickyResponseIDTTLSeconds = %d, want 7200", cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefaultIdempotencyConfig(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
|
||||
@@ -993,6 +1091,16 @@ func TestValidateConfigErrors(t *testing.T) {
|
||||
mutate: func(c *Config) { c.Gateway.StreamKeepaliveInterval = 4 },
|
||||
wantErr: "gateway.stream_keepalive_interval",
|
||||
},
|
||||
{
|
||||
name: "gateway openai ws oauth max conns factor",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.OAuthMaxConnsFactor = 0 },
|
||||
wantErr: "gateway.openai_ws.oauth_max_conns_factor",
|
||||
},
|
||||
{
|
||||
name: "gateway openai ws apikey max conns factor",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.APIKeyMaxConnsFactor = 0 },
|
||||
wantErr: "gateway.openai_ws.apikey_max_conns_factor",
|
||||
},
|
||||
{
|
||||
name: "gateway stream data interval range",
|
||||
mutate: func(c *Config) { c.Gateway.StreamDataIntervalTimeout = 5 },
|
||||
@@ -1174,6 +1282,165 @@ func TestValidateConfigErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateConfig_OpenAIWSRules(t *testing.T) {
|
||||
buildValid := func(t *testing.T) *Config {
|
||||
t.Helper()
|
||||
resetViperWithJWTSecret(t)
|
||||
cfg, err := Load()
|
||||
require.NoError(t, err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
t.Run("sticky response id ttl 兼容旧键回填", func(t *testing.T) {
|
||||
cfg := buildValid(t)
|
||||
cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds = 0
|
||||
cfg.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds = 7200
|
||||
|
||||
require.NoError(t, cfg.Validate())
|
||||
require.Equal(t, 7200, cfg.Gateway.OpenAIWS.StickyResponseIDTTLSeconds)
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
mutate func(*Config)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "max_conns_per_account 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.MaxConnsPerAccount = 0 },
|
||||
wantErr: "gateway.openai_ws.max_conns_per_account",
|
||||
},
|
||||
{
|
||||
name: "min_idle_per_account 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.MinIdlePerAccount = -1 },
|
||||
wantErr: "gateway.openai_ws.min_idle_per_account",
|
||||
},
|
||||
{
|
||||
name: "max_idle_per_account 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.MaxIdlePerAccount = -1 },
|
||||
wantErr: "gateway.openai_ws.max_idle_per_account",
|
||||
},
|
||||
{
|
||||
name: "min_idle_per_account 不能大于 max_idle_per_account",
|
||||
mutate: func(c *Config) {
|
||||
c.Gateway.OpenAIWS.MinIdlePerAccount = 3
|
||||
c.Gateway.OpenAIWS.MaxIdlePerAccount = 2
|
||||
},
|
||||
wantErr: "gateway.openai_ws.min_idle_per_account must be <= max_idle_per_account",
|
||||
},
|
||||
{
|
||||
name: "max_idle_per_account 不能大于 max_conns_per_account",
|
||||
mutate: func(c *Config) {
|
||||
c.Gateway.OpenAIWS.MaxConnsPerAccount = 2
|
||||
c.Gateway.OpenAIWS.MinIdlePerAccount = 1
|
||||
c.Gateway.OpenAIWS.MaxIdlePerAccount = 3
|
||||
},
|
||||
wantErr: "gateway.openai_ws.max_idle_per_account must be <= max_conns_per_account",
|
||||
},
|
||||
{
|
||||
name: "dial_timeout_seconds 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.DialTimeoutSeconds = 0 },
|
||||
wantErr: "gateway.openai_ws.dial_timeout_seconds",
|
||||
},
|
||||
{
|
||||
name: "read_timeout_seconds 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.ReadTimeoutSeconds = 0 },
|
||||
wantErr: "gateway.openai_ws.read_timeout_seconds",
|
||||
},
|
||||
{
|
||||
name: "write_timeout_seconds 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.WriteTimeoutSeconds = 0 },
|
||||
wantErr: "gateway.openai_ws.write_timeout_seconds",
|
||||
},
|
||||
{
|
||||
name: "pool_target_utilization 必须在 (0,1]",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.PoolTargetUtilization = 0 },
|
||||
wantErr: "gateway.openai_ws.pool_target_utilization",
|
||||
},
|
||||
{
|
||||
name: "queue_limit_per_conn 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.QueueLimitPerConn = 0 },
|
||||
wantErr: "gateway.openai_ws.queue_limit_per_conn",
|
||||
},
|
||||
{
|
||||
name: "fallback_cooldown_seconds 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.FallbackCooldownSeconds = -1 },
|
||||
wantErr: "gateway.openai_ws.fallback_cooldown_seconds",
|
||||
},
|
||||
{
|
||||
name: "store_disabled_conn_mode 必须为 strict|adaptive|off",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.StoreDisabledConnMode = "invalid" },
|
||||
wantErr: "gateway.openai_ws.store_disabled_conn_mode",
|
||||
},
|
||||
{
|
||||
name: "ingress_mode_default 必须为 off|ctx_pool|passthrough",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.IngressModeDefault = "invalid" },
|
||||
wantErr: "gateway.openai_ws.ingress_mode_default",
|
||||
},
|
||||
{
|
||||
name: "payload_log_sample_rate 必须在 [0,1] 范围内",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.PayloadLogSampleRate = 1.2 },
|
||||
wantErr: "gateway.openai_ws.payload_log_sample_rate",
|
||||
},
|
||||
{
|
||||
name: "retry_total_budget_ms 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.RetryTotalBudgetMS = -1 },
|
||||
wantErr: "gateway.openai_ws.retry_total_budget_ms",
|
||||
},
|
||||
{
|
||||
name: "lb_top_k 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.LBTopK = 0 },
|
||||
wantErr: "gateway.openai_ws.lb_top_k",
|
||||
},
|
||||
{
|
||||
name: "sticky_session_ttl_seconds 必须为正数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.StickySessionTTLSeconds = 0 },
|
||||
wantErr: "gateway.openai_ws.sticky_session_ttl_seconds",
|
||||
},
|
||||
{
|
||||
name: "sticky_response_id_ttl_seconds 必须为正数",
|
||||
mutate: func(c *Config) {
|
||||
c.Gateway.OpenAIWS.StickyResponseIDTTLSeconds = 0
|
||||
c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds = 0
|
||||
},
|
||||
wantErr: "gateway.openai_ws.sticky_response_id_ttl_seconds",
|
||||
},
|
||||
{
|
||||
name: "sticky_previous_response_ttl_seconds 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.StickyPreviousResponseTTLSeconds = -1 },
|
||||
wantErr: "gateway.openai_ws.sticky_previous_response_ttl_seconds",
|
||||
},
|
||||
{
|
||||
name: "scheduler_score_weights 不能为负数",
|
||||
mutate: func(c *Config) { c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue = -0.1 },
|
||||
wantErr: "gateway.openai_ws.scheduler_score_weights.* must be non-negative",
|
||||
},
|
||||
{
|
||||
name: "scheduler_score_weights 不能全为 0",
|
||||
mutate: func(c *Config) {
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Priority = 0
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Load = 0
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.Queue = 0
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.ErrorRate = 0
|
||||
c.Gateway.OpenAIWS.SchedulerScoreWeights.TTFT = 0
|
||||
},
|
||||
wantErr: "gateway.openai_ws.scheduler_score_weights must not all be zero",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := buildValid(t)
|
||||
tc.mutate(cfg)
|
||||
|
||||
err := cfg.Validate()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateConfig_AutoScaleDisabledIgnoreAutoScaleFields(t *testing.T) {
|
||||
resetViperWithJWTSecret(t)
|
||||
cfg, err := Load()
|
||||
|
||||
@@ -104,6 +104,9 @@ var DefaultAntigravityModelMapping = map[string]string{
|
||||
"gemini-3.1-flash-image": "gemini-3.1-flash-image",
|
||||
// Gemini 3.1 image preview 映射
|
||||
"gemini-3.1-flash-image-preview": "gemini-3.1-flash-image",
|
||||
// Gemini 3 image 兼容映射(向 3.1 image 迁移)
|
||||
"gemini-3-pro-image": "gemini-3.1-flash-image",
|
||||
"gemini-3-pro-image-preview": "gemini-3.1-flash-image",
|
||||
// 其他官方模型
|
||||
"gpt-oss-120b-medium": "gpt-oss-120b-medium",
|
||||
"tab_flash_lite_preview": "tab_flash_lite_preview",
|
||||
|
||||
24
backend/internal/domain/constants_test.go
Normal file
24
backend/internal/domain/constants_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package domain
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDefaultAntigravityModelMapping_ImageCompatibilityAliases(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]string{
|
||||
"gemini-3.1-flash-image": "gemini-3.1-flash-image",
|
||||
"gemini-3.1-flash-image-preview": "gemini-3.1-flash-image",
|
||||
"gemini-3-pro-image": "gemini-3.1-flash-image",
|
||||
"gemini-3-pro-image-preview": "gemini-3.1-flash-image",
|
||||
}
|
||||
|
||||
for from, want := range cases {
|
||||
got, ok := DefaultAntigravityModelMapping[from]
|
||||
if !ok {
|
||||
t.Fatalf("expected mapping for %q to exist", from)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("unexpected mapping for %q: got %q want %q", from, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ func setupAccountDataRouter() (*gin.Engine, *stubAdminService) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
router.GET("/api/v1/admin/accounts/data", h.ExportData)
|
||||
|
||||
@@ -53,6 +53,7 @@ type AccountHandler struct {
|
||||
concurrencyService *service.ConcurrencyService
|
||||
crsSyncService *service.CRSSyncService
|
||||
sessionLimitCache service.SessionLimitCache
|
||||
rpmCache service.RPMCache
|
||||
tokenCacheInvalidator service.TokenCacheInvalidator
|
||||
}
|
||||
|
||||
@@ -69,6 +70,7 @@ func NewAccountHandler(
|
||||
concurrencyService *service.ConcurrencyService,
|
||||
crsSyncService *service.CRSSyncService,
|
||||
sessionLimitCache service.SessionLimitCache,
|
||||
rpmCache service.RPMCache,
|
||||
tokenCacheInvalidator service.TokenCacheInvalidator,
|
||||
) *AccountHandler {
|
||||
return &AccountHandler{
|
||||
@@ -83,6 +85,7 @@ func NewAccountHandler(
|
||||
concurrencyService: concurrencyService,
|
||||
crsSyncService: crsSyncService,
|
||||
sessionLimitCache: sessionLimitCache,
|
||||
rpmCache: rpmCache,
|
||||
tokenCacheInvalidator: tokenCacheInvalidator,
|
||||
}
|
||||
}
|
||||
@@ -99,6 +102,7 @@ type CreateAccountRequest struct {
|
||||
Concurrency int `json:"concurrency"`
|
||||
Priority int `json:"priority"`
|
||||
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||
LoadFactor *int `json:"load_factor"`
|
||||
GroupIDs []int64 `json:"group_ids"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
|
||||
@@ -117,6 +121,7 @@ type UpdateAccountRequest struct {
|
||||
Concurrency *int `json:"concurrency"`
|
||||
Priority *int `json:"priority"`
|
||||
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||
LoadFactor *int `json:"load_factor"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||
GroupIDs *[]int64 `json:"group_ids"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
@@ -132,6 +137,7 @@ type BulkUpdateAccountsRequest struct {
|
||||
Concurrency *int `json:"concurrency"`
|
||||
Priority *int `json:"priority"`
|
||||
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||
LoadFactor *int `json:"load_factor"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=active inactive error"`
|
||||
Schedulable *bool `json:"schedulable"`
|
||||
GroupIDs *[]int64 `json:"group_ids"`
|
||||
@@ -154,6 +160,7 @@ type AccountWithConcurrency struct {
|
||||
// 以下字段仅对 Anthropic OAuth/SetupToken 账号有效,且仅在启用相应功能时返回
|
||||
CurrentWindowCost *float64 `json:"current_window_cost,omitempty"` // 当前窗口费用
|
||||
ActiveSessions *int `json:"active_sessions,omitempty"` // 当前活跃会话数
|
||||
CurrentRPM *int `json:"current_rpm,omitempty"` // 当前分钟 RPM 计数
|
||||
}
|
||||
|
||||
func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, account *service.Account) AccountWithConcurrency {
|
||||
@@ -189,6 +196,12 @@ func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, ac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if h.rpmCache != nil && account.GetBaseRPM() > 0 {
|
||||
if rpm, err := h.rpmCache.GetRPM(ctx, account.ID); err == nil {
|
||||
item.CurrentRPM = &rpm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return item
|
||||
@@ -207,6 +220,7 @@ func (h *AccountHandler) List(c *gin.Context) {
|
||||
if len(search) > 100 {
|
||||
search = search[:100]
|
||||
}
|
||||
lite := parseBoolQueryWithDefault(c.Query("lite"), false)
|
||||
|
||||
var groupID int64
|
||||
if groupIDStr := c.Query("group"); groupIDStr != "" {
|
||||
@@ -225,15 +239,22 @@ func (h *AccountHandler) List(c *gin.Context) {
|
||||
accountIDs[i] = acc.ID
|
||||
}
|
||||
|
||||
concurrencyCounts, err := h.concurrencyService.GetAccountConcurrencyBatch(c.Request.Context(), accountIDs)
|
||||
if err != nil {
|
||||
// Log error but don't fail the request, just use 0 for all
|
||||
concurrencyCounts = make(map[int64]int)
|
||||
concurrencyCounts := make(map[int64]int)
|
||||
var windowCosts map[int64]float64
|
||||
var activeSessions map[int64]int
|
||||
var rpmCounts map[int64]int
|
||||
|
||||
// 始终获取并发数(Redis ZCARD,极低开销)
|
||||
if h.concurrencyService != nil {
|
||||
if cc, ccErr := h.concurrencyService.GetAccountConcurrencyBatch(c.Request.Context(), accountIDs); ccErr == nil && cc != nil {
|
||||
concurrencyCounts = cc
|
||||
}
|
||||
}
|
||||
|
||||
// 识别需要查询窗口费用和会话数的账号(Anthropic OAuth/SetupToken 且启用了相应功能)
|
||||
// 识别需要查询窗口费用、会话数和 RPM 的账号(Anthropic OAuth/SetupToken 且启用了相应功能)
|
||||
windowCostAccountIDs := make([]int64, 0)
|
||||
sessionLimitAccountIDs := make([]int64, 0)
|
||||
rpmAccountIDs := make([]int64, 0)
|
||||
sessionIdleTimeouts := make(map[int64]time.Duration) // 各账号的会话空闲超时配置
|
||||
for i := range accounts {
|
||||
acc := &accounts[i]
|
||||
@@ -245,14 +266,21 @@ func (h *AccountHandler) List(c *gin.Context) {
|
||||
sessionLimitAccountIDs = append(sessionLimitAccountIDs, acc.ID)
|
||||
sessionIdleTimeouts[acc.ID] = time.Duration(acc.GetSessionIdleTimeoutMinutes()) * time.Minute
|
||||
}
|
||||
if acc.GetBaseRPM() > 0 {
|
||||
rpmAccountIDs = append(rpmAccountIDs, acc.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 并行获取窗口费用和活跃会话数
|
||||
var windowCosts map[int64]float64
|
||||
var activeSessions map[int64]int
|
||||
// 始终获取 RPM 计数(Redis GET,极低开销)
|
||||
if len(rpmAccountIDs) > 0 && h.rpmCache != nil {
|
||||
rpmCounts, _ = h.rpmCache.GetRPMBatch(c.Request.Context(), rpmAccountIDs)
|
||||
if rpmCounts == nil {
|
||||
rpmCounts = make(map[int64]int)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取活跃会话数(批量查询,传入各账号的 idleTimeout 配置)
|
||||
// 始终获取活跃会话数(Redis ZCARD,低开销)
|
||||
if len(sessionLimitAccountIDs) > 0 && h.sessionLimitCache != nil {
|
||||
activeSessions, _ = h.sessionLimitCache.GetActiveSessionCountBatch(c.Request.Context(), sessionLimitAccountIDs, sessionIdleTimeouts)
|
||||
if activeSessions == nil {
|
||||
@@ -260,32 +288,48 @@ func (h *AccountHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取窗口费用(并行查询)
|
||||
// 窗口费用获取:lite 模式从快照缓存读取,非 lite 模式执行 PostgreSQL 查询后写入缓存
|
||||
if len(windowCostAccountIDs) > 0 {
|
||||
windowCosts = make(map[int64]float64)
|
||||
var mu sync.Mutex
|
||||
g, gctx := errgroup.WithContext(c.Request.Context())
|
||||
g.SetLimit(10) // 限制并发数
|
||||
|
||||
for i := range accounts {
|
||||
acc := &accounts[i]
|
||||
if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 {
|
||||
continue
|
||||
}
|
||||
accCopy := acc // 闭包捕获
|
||||
g.Go(func() error {
|
||||
// 使用统一的窗口开始时间计算逻辑(考虑窗口过期情况)
|
||||
startTime := accCopy.GetCurrentWindowStartTime()
|
||||
stats, err := h.accountUsageService.GetAccountWindowStats(gctx, accCopy.ID, startTime)
|
||||
if err == nil && stats != nil {
|
||||
mu.Lock()
|
||||
windowCosts[accCopy.ID] = stats.StandardCost // 使用标准费用
|
||||
mu.Unlock()
|
||||
if lite {
|
||||
// lite 模式:尝试从快照缓存读取
|
||||
cacheKey := buildWindowCostCacheKey(windowCostAccountIDs)
|
||||
if cached, ok := accountWindowCostCache.Get(cacheKey); ok {
|
||||
if costs, ok := cached.Payload.(map[int64]float64); ok {
|
||||
windowCosts = costs
|
||||
}
|
||||
return nil // 不返回错误,允许部分失败
|
||||
})
|
||||
}
|
||||
// 缓存未命中则 windowCosts 保持 nil(仅发生在服务刚启动时)
|
||||
} else {
|
||||
// 非 lite 模式:执行 PostgreSQL 聚合查询(高开销)
|
||||
windowCosts = make(map[int64]float64)
|
||||
var mu sync.Mutex
|
||||
g, gctx := errgroup.WithContext(c.Request.Context())
|
||||
g.SetLimit(10) // 限制并发数
|
||||
|
||||
for i := range accounts {
|
||||
acc := &accounts[i]
|
||||
if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 {
|
||||
continue
|
||||
}
|
||||
accCopy := acc // 闭包捕获
|
||||
g.Go(func() error {
|
||||
// 使用统一的窗口开始时间计算逻辑(考虑窗口过期情况)
|
||||
startTime := accCopy.GetCurrentWindowStartTime()
|
||||
stats, err := h.accountUsageService.GetAccountWindowStats(gctx, accCopy.ID, startTime)
|
||||
if err == nil && stats != nil {
|
||||
mu.Lock()
|
||||
windowCosts[accCopy.ID] = stats.StandardCost // 使用标准费用
|
||||
mu.Unlock()
|
||||
}
|
||||
return nil // 不返回错误,允许部分失败
|
||||
})
|
||||
}
|
||||
_ = g.Wait()
|
||||
|
||||
// 查询完毕后写入快照缓存,供 lite 模式使用
|
||||
cacheKey := buildWindowCostCacheKey(windowCostAccountIDs)
|
||||
accountWindowCostCache.Set(cacheKey, windowCosts)
|
||||
}
|
||||
_ = g.Wait()
|
||||
}
|
||||
|
||||
// Build response with concurrency info
|
||||
@@ -311,10 +355,17 @@ func (h *AccountHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 RPM 计数(仅当启用时)
|
||||
if rpmCounts != nil {
|
||||
if rpm, ok := rpmCounts[acc.ID]; ok {
|
||||
item.CurrentRPM = &rpm
|
||||
}
|
||||
}
|
||||
|
||||
result[i] = item
|
||||
}
|
||||
|
||||
etag := buildAccountsListETag(result, total, page, pageSize, platform, accountType, status, search)
|
||||
etag := buildAccountsListETag(result, total, page, pageSize, platform, accountType, status, search, lite)
|
||||
if etag != "" {
|
||||
c.Header("ETag", etag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
@@ -332,6 +383,7 @@ func buildAccountsListETag(
|
||||
total int64,
|
||||
page, pageSize int,
|
||||
platform, accountType, status, search string,
|
||||
lite bool,
|
||||
) string {
|
||||
payload := struct {
|
||||
Total int64 `json:"total"`
|
||||
@@ -341,6 +393,7 @@ func buildAccountsListETag(
|
||||
AccountType string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Search string `json:"search"`
|
||||
Lite bool `json:"lite"`
|
||||
Items []AccountWithConcurrency `json:"items"`
|
||||
}{
|
||||
Total: total,
|
||||
@@ -350,6 +403,7 @@ func buildAccountsListETag(
|
||||
AccountType: accountType,
|
||||
Status: status,
|
||||
Search: search,
|
||||
Lite: lite,
|
||||
Items: items,
|
||||
}
|
||||
raw, err := json.Marshal(payload)
|
||||
@@ -453,6 +507,8 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
||||
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||
return
|
||||
}
|
||||
// base_rpm 输入校验:负值归零,超过 10000 截断
|
||||
sanitizeExtraBaseRPM(req.Extra)
|
||||
|
||||
// 确定是否跳过混合渠道检查
|
||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||
@@ -469,6 +525,7 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
||||
Concurrency: req.Concurrency,
|
||||
Priority: req.Priority,
|
||||
RateMultiplier: req.RateMultiplier,
|
||||
LoadFactor: req.LoadFactor,
|
||||
GroupIDs: req.GroupIDs,
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
AutoPauseOnExpired: req.AutoPauseOnExpired,
|
||||
@@ -522,6 +579,8 @@ func (h *AccountHandler) Update(c *gin.Context) {
|
||||
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||
return
|
||||
}
|
||||
// base_rpm 输入校验:负值归零,超过 10000 截断
|
||||
sanitizeExtraBaseRPM(req.Extra)
|
||||
|
||||
// 确定是否跳过混合渠道检查
|
||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||
@@ -536,6 +595,7 @@ func (h *AccountHandler) Update(c *gin.Context) {
|
||||
Concurrency: req.Concurrency, // 指针类型,nil 表示未提供
|
||||
Priority: req.Priority, // 指针类型,nil 表示未提供
|
||||
RateMultiplier: req.RateMultiplier,
|
||||
LoadFactor: req.LoadFactor,
|
||||
Status: req.Status,
|
||||
GroupIDs: req.GroupIDs,
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
@@ -904,6 +964,9 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
// base_rpm 输入校验:负值归零,超过 10000 截断
|
||||
sanitizeExtraBaseRPM(item.Extra)
|
||||
|
||||
skipCheck := item.ConfirmMixedChannelRisk != nil && *item.ConfirmMixedChannelRisk
|
||||
|
||||
account, err := h.adminService.CreateAccount(ctx, &service.CreateAccountInput{
|
||||
@@ -1048,6 +1111,8 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
||||
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||
return
|
||||
}
|
||||
// base_rpm 输入校验:负值归零,超过 10000 截断
|
||||
sanitizeExtraBaseRPM(req.Extra)
|
||||
|
||||
// 确定是否跳过混合渠道检查
|
||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||
@@ -1057,6 +1122,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
||||
req.Concurrency != nil ||
|
||||
req.Priority != nil ||
|
||||
req.RateMultiplier != nil ||
|
||||
req.LoadFactor != nil ||
|
||||
req.Status != "" ||
|
||||
req.Schedulable != nil ||
|
||||
req.GroupIDs != nil ||
|
||||
@@ -1075,6 +1141,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
||||
Concurrency: req.Concurrency,
|
||||
Priority: req.Priority,
|
||||
RateMultiplier: req.RateMultiplier,
|
||||
LoadFactor: req.LoadFactor,
|
||||
Status: req.Status,
|
||||
Schedulable: req.Schedulable,
|
||||
GroupIDs: req.GroupIDs,
|
||||
@@ -1083,6 +1150,14 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
||||
SkipMixedChannelCheck: skipCheck,
|
||||
})
|
||||
if err != nil {
|
||||
var mixedErr *service.MixedChannelError
|
||||
if errors.As(err, &mixedErr) {
|
||||
c.JSON(409, gin.H{
|
||||
"error": "mixed_channel_warning",
|
||||
"message": mixedErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
@@ -1276,6 +1351,29 @@ func (h *AccountHandler) ClearRateLimit(c *gin.Context) {
|
||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||
}
|
||||
|
||||
// ResetQuota handles resetting account quota usage
|
||||
// POST /api/v1/admin/accounts/:id/reset-quota
|
||||
func (h *AccountHandler) ResetQuota(c *gin.Context) {
|
||||
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid account ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.adminService.ResetAccountQuota(c.Request.Context(), accountID); err != nil {
|
||||
response.InternalError(c, "Failed to reset account quota: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||
}
|
||||
|
||||
// GetTempUnschedulable handles getting temporary unschedulable status
|
||||
// GET /api/v1/admin/accounts/:id/temp-unschedulable
|
||||
func (h *AccountHandler) GetTempUnschedulable(c *gin.Context) {
|
||||
@@ -1337,6 +1435,57 @@ func (h *AccountHandler) GetTodayStats(c *gin.Context) {
|
||||
response.Success(c, stats)
|
||||
}
|
||||
|
||||
// BatchTodayStatsRequest 批量今日统计请求体。
|
||||
type BatchTodayStatsRequest struct {
|
||||
AccountIDs []int64 `json:"account_ids" binding:"required"`
|
||||
}
|
||||
|
||||
// GetBatchTodayStats 批量获取多个账号的今日统计。
|
||||
// POST /api/v1/admin/accounts/today-stats/batch
|
||||
func (h *AccountHandler) GetBatchTodayStats(c *gin.Context) {
|
||||
var req BatchTodayStatsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
accountIDs := normalizeInt64IDList(req.AccountIDs)
|
||||
if len(accountIDs) == 0 {
|
||||
response.Success(c, gin.H{"stats": map[string]any{}})
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := buildAccountTodayStatsBatchCacheKey(accountIDs)
|
||||
if cached, ok := accountTodayStatsBatchCache.Get(cacheKey); ok {
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
if ifNoneMatchMatched(c.GetHeader("If-None-Match"), cached.ETag) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.accountUsageService.GetTodayStatsBatch(c.Request.Context(), accountIDs)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
payload := gin.H{"stats": stats}
|
||||
cached := accountTodayStatsBatchCache.Set(cacheKey, payload)
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, payload)
|
||||
}
|
||||
|
||||
// SetSchedulableRequest represents the request body for setting schedulable status
|
||||
type SetSchedulableRequest struct {
|
||||
Schedulable bool `json:"schedulable"`
|
||||
@@ -1678,3 +1827,22 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
|
||||
func (h *AccountHandler) GetAntigravityDefaultModelMapping(c *gin.Context) {
|
||||
response.Success(c, domain.DefaultAntigravityModelMapping)
|
||||
}
|
||||
|
||||
// sanitizeExtraBaseRPM 对 extra map 中的 base_rpm 值进行范围校验和归一化。
|
||||
// 负值归零,超过 10000 截断为 10000。extra 为 nil 或不含 base_rpm 时无操作。
|
||||
func sanitizeExtraBaseRPM(extra map[string]any) {
|
||||
if extra == nil {
|
||||
return
|
||||
}
|
||||
raw, ok := extra["base_rpm"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v := service.ParseExtraInt(raw)
|
||||
if v < 0 {
|
||||
v = 0
|
||||
} else if v > 10000 {
|
||||
v = 10000
|
||||
}
|
||||
extra["base_rpm"] = v
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ import (
|
||||
func setupAccountMixedChannelRouter(adminSvc *stubAdminService) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
accountHandler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
accountHandler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router.POST("/api/v1/admin/accounts/check-mixed-channel", accountHandler.CheckMixedChannel)
|
||||
router.POST("/api/v1/admin/accounts", accountHandler.Create)
|
||||
router.PUT("/api/v1/admin/accounts/:id", accountHandler.Update)
|
||||
router.POST("/api/v1/admin/accounts/bulk-update", accountHandler.BulkUpdate)
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -145,3 +146,53 @@ func TestAccountHandlerUpdateMixedChannelConflictSimplifiedResponse(t *testing.T
|
||||
require.False(t, hasDetails)
|
||||
require.False(t, hasRequireConfirmation)
|
||||
}
|
||||
|
||||
func TestAccountHandlerBulkUpdateMixedChannelConflict(t *testing.T) {
|
||||
adminSvc := newStubAdminService()
|
||||
adminSvc.bulkUpdateAccountErr = &service.MixedChannelError{
|
||||
GroupID: 27,
|
||||
GroupName: "claude-max",
|
||||
CurrentPlatform: "Antigravity",
|
||||
OtherPlatform: "Anthropic",
|
||||
}
|
||||
router := setupAccountMixedChannelRouter(adminSvc)
|
||||
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"account_ids": []int64{1, 2, 3},
|
||||
"group_ids": []int64{27},
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusConflict, rec.Code)
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, "mixed_channel_warning", resp["error"])
|
||||
require.Contains(t, resp["message"], "claude-max")
|
||||
}
|
||||
|
||||
func TestAccountHandlerBulkUpdateMixedChannelConfirmSkips(t *testing.T) {
|
||||
adminSvc := newStubAdminService()
|
||||
router := setupAccountMixedChannelRouter(adminSvc)
|
||||
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"account_ids": []int64{1, 2},
|
||||
"group_ids": []int64{27},
|
||||
"confirm_mixed_channel_risk": true,
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, float64(0), resp["code"])
|
||||
data, ok := resp["data"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, float64(2), data["success"])
|
||||
require.Equal(t, float64(0), data["failed"])
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestAccountHandler_Create_AnthropicAPIKeyPassthroughExtraForwarded(t *testi
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
router := gin.New()
|
||||
|
||||
25
backend/internal/handler/admin/account_today_stats_cache.go
Normal file
25
backend/internal/handler/admin/account_today_stats_cache.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var accountTodayStatsBatchCache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
func buildAccountTodayStatsBatchCacheKey(accountIDs []int64) string {
|
||||
if len(accountIDs) == 0 {
|
||||
return "accounts_today_stats_empty"
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(accountIDs) * 6)
|
||||
_, _ = b.WriteString("accounts_today_stats:")
|
||||
for i, id := range accountIDs {
|
||||
if i > 0 {
|
||||
_ = b.WriteByte(',')
|
||||
}
|
||||
_, _ = b.WriteString(strconv.FormatInt(id, 10))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
25
backend/internal/handler/admin/account_window_cost_cache.go
Normal file
25
backend/internal/handler/admin/account_window_cost_cache.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var accountWindowCostCache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
func buildWindowCostCacheKey(accountIDs []int64) string {
|
||||
if len(accountIDs) == 0 {
|
||||
return "accounts_window_cost_empty"
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(accountIDs) * 6)
|
||||
_, _ = b.WriteString("accounts_window_cost:")
|
||||
for i, id := range accountIDs {
|
||||
if i > 0 {
|
||||
_ = b.WriteByte(',')
|
||||
}
|
||||
_, _ = b.WriteString(strconv.FormatInt(id, 10))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
|
||||
userHandler := NewUserHandler(adminSvc, nil)
|
||||
groupHandler := NewGroupHandler(adminSvc)
|
||||
proxyHandler := NewProxyHandler(adminSvc)
|
||||
redeemHandler := NewRedeemHandler(adminSvc)
|
||||
redeemHandler := NewRedeemHandler(adminSvc, nil)
|
||||
|
||||
router.GET("/api/v1/admin/users", userHandler.List)
|
||||
router.GET("/api/v1/admin/users/:id", userHandler.GetByID)
|
||||
|
||||
@@ -10,22 +10,23 @@ import (
|
||||
)
|
||||
|
||||
type stubAdminService struct {
|
||||
users []service.User
|
||||
apiKeys []service.APIKey
|
||||
groups []service.Group
|
||||
accounts []service.Account
|
||||
proxies []service.Proxy
|
||||
proxyCounts []service.ProxyWithAccountCount
|
||||
redeems []service.RedeemCode
|
||||
createdAccounts []*service.CreateAccountInput
|
||||
createdProxies []*service.CreateProxyInput
|
||||
updatedProxyIDs []int64
|
||||
updatedProxies []*service.UpdateProxyInput
|
||||
testedProxyIDs []int64
|
||||
createAccountErr error
|
||||
updateAccountErr error
|
||||
checkMixedErr error
|
||||
lastMixedCheck struct {
|
||||
users []service.User
|
||||
apiKeys []service.APIKey
|
||||
groups []service.Group
|
||||
accounts []service.Account
|
||||
proxies []service.Proxy
|
||||
proxyCounts []service.ProxyWithAccountCount
|
||||
redeems []service.RedeemCode
|
||||
createdAccounts []*service.CreateAccountInput
|
||||
createdProxies []*service.CreateProxyInput
|
||||
updatedProxyIDs []int64
|
||||
updatedProxies []*service.UpdateProxyInput
|
||||
testedProxyIDs []int64
|
||||
createAccountErr error
|
||||
updateAccountErr error
|
||||
bulkUpdateAccountErr error
|
||||
checkMixedErr error
|
||||
lastMixedCheck struct {
|
||||
accountID int64
|
||||
platform string
|
||||
groupIDs []int64
|
||||
@@ -235,7 +236,10 @@ func (s *stubAdminService) SetAccountSchedulable(ctx context.Context, id int64,
|
||||
}
|
||||
|
||||
func (s *stubAdminService) BulkUpdateAccounts(ctx context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
|
||||
return &service.BulkUpdateAccountsResult{Success: 1, Failed: 0, SuccessIDs: []int64{1}}, nil
|
||||
if s.bulkUpdateAccountErr != nil {
|
||||
return nil, s.bulkUpdateAccountErr
|
||||
}
|
||||
return &service.BulkUpdateAccountsResult{Success: len(input.AccountIDs), Failed: 0, SuccessIDs: input.AccountIDs}, nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error {
|
||||
@@ -403,5 +407,27 @@ func (s *stubAdminService) UpdateGroupSortOrders(ctx context.Context, updates []
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID int64, groupID *int64) (*service.AdminUpdateAPIKeyGroupIDResult, error) {
|
||||
for i := range s.apiKeys {
|
||||
if s.apiKeys[i].ID == keyID {
|
||||
k := s.apiKeys[i]
|
||||
if groupID != nil {
|
||||
if *groupID == 0 {
|
||||
k.GroupID = nil
|
||||
} else {
|
||||
gid := *groupID
|
||||
k.GroupID = &gid
|
||||
}
|
||||
}
|
||||
return &service.AdminUpdateAPIKeyGroupIDResult{APIKey: &k}, nil
|
||||
}
|
||||
}
|
||||
return nil, service.ErrAPIKeyNotFound
|
||||
}
|
||||
|
||||
func (s *stubAdminService) ResetAccountQuota(ctx context.Context, id int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure stub implements interface.
|
||||
var _ service.AdminService = (*stubAdminService)(nil)
|
||||
|
||||
63
backend/internal/handler/admin/apikey_handler.go
Normal file
63
backend/internal/handler/admin/apikey_handler.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminAPIKeyHandler handles admin API key management
|
||||
type AdminAPIKeyHandler struct {
|
||||
adminService service.AdminService
|
||||
}
|
||||
|
||||
// NewAdminAPIKeyHandler creates a new admin API key handler
|
||||
func NewAdminAPIKeyHandler(adminService service.AdminService) *AdminAPIKeyHandler {
|
||||
return &AdminAPIKeyHandler{
|
||||
adminService: adminService,
|
||||
}
|
||||
}
|
||||
|
||||
// AdminUpdateAPIKeyGroupRequest represents the request to update an API key's group
|
||||
type AdminUpdateAPIKeyGroupRequest struct {
|
||||
GroupID *int64 `json:"group_id"` // nil=不修改, 0=解绑, >0=绑定到目标分组
|
||||
}
|
||||
|
||||
// UpdateGroup handles updating an API key's group binding
|
||||
// PUT /api/v1/admin/api-keys/:id
|
||||
func (h *AdminAPIKeyHandler) UpdateGroup(c *gin.Context) {
|
||||
keyID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid API key ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req AdminUpdateAPIKeyGroupRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.adminService.AdminUpdateAPIKeyGroupID(c.Request.Context(), keyID, req.GroupID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
APIKey *dto.APIKey `json:"api_key"`
|
||||
AutoGrantedGroupAccess bool `json:"auto_granted_group_access"`
|
||||
GrantedGroupID *int64 `json:"granted_group_id,omitempty"`
|
||||
GrantedGroupName string `json:"granted_group_name,omitempty"`
|
||||
}{
|
||||
APIKey: dto.APIKeyFromService(result.APIKey),
|
||||
AutoGrantedGroupAccess: result.AutoGrantedGroupAccess,
|
||||
GrantedGroupID: result.GrantedGroupID,
|
||||
GrantedGroupName: result.GrantedGroupName,
|
||||
}
|
||||
response.Success(c, resp)
|
||||
}
|
||||
202
backend/internal/handler/admin/apikey_handler_test.go
Normal file
202
backend/internal/handler/admin/apikey_handler_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupAPIKeyHandler(adminSvc service.AdminService) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
h := NewAdminAPIKeyHandler(adminSvc)
|
||||
router.PUT("/api/v1/admin/api-keys/:id", h.UpdateGroup)
|
||||
return router
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_InvalidID(t *testing.T) {
|
||||
router := setupAPIKeyHandler(newStubAdminService())
|
||||
body := `{"group_id": 2}`
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/abc", bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
require.Contains(t, rec.Body.String(), "Invalid API key ID")
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_InvalidJSON(t *testing.T) {
|
||||
router := setupAPIKeyHandler(newStubAdminService())
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(`{bad json`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
require.Contains(t, rec.Body.String(), "Invalid request")
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_KeyNotFound(t *testing.T) {
|
||||
router := setupAPIKeyHandler(newStubAdminService())
|
||||
body := `{"group_id": 2}`
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/999", bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
// ErrAPIKeyNotFound maps to 404
|
||||
require.Equal(t, http.StatusNotFound, rec.Code)
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_BindGroup(t *testing.T) {
|
||||
router := setupAPIKeyHandler(newStubAdminService())
|
||||
body := `{"group_id": 2}`
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, 0, resp.Code)
|
||||
|
||||
var data struct {
|
||||
APIKey struct {
|
||||
ID int64 `json:"id"`
|
||||
GroupID *int64 `json:"group_id"`
|
||||
} `json:"api_key"`
|
||||
AutoGrantedGroupAccess bool `json:"auto_granted_group_access"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &data))
|
||||
require.Equal(t, int64(10), data.APIKey.ID)
|
||||
require.NotNil(t, data.APIKey.GroupID)
|
||||
require.Equal(t, int64(2), *data.APIKey.GroupID)
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_Unbind(t *testing.T) {
|
||||
svc := newStubAdminService()
|
||||
gid := int64(2)
|
||||
svc.apiKeys[0].GroupID = &gid
|
||||
router := setupAPIKeyHandler(svc)
|
||||
body := `{"group_id": 0}`
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
var resp struct {
|
||||
Data struct {
|
||||
APIKey struct {
|
||||
GroupID *int64 `json:"group_id"`
|
||||
} `json:"api_key"`
|
||||
} `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Nil(t, resp.Data.APIKey.GroupID)
|
||||
}
|
||||
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_ServiceError(t *testing.T) {
|
||||
svc := &failingUpdateGroupService{
|
||||
stubAdminService: newStubAdminService(),
|
||||
err: errors.New("internal failure"),
|
||||
}
|
||||
router := setupAPIKeyHandler(svc)
|
||||
body := `{"group_id": 2}`
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
}
|
||||
|
||||
// H2: empty body → group_id is nil → no-op, returns original key
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_EmptyBody_NoChange(t *testing.T) {
|
||||
router := setupAPIKeyHandler(newStubAdminService())
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(`{}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
APIKey struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"api_key"`
|
||||
} `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||
require.Equal(t, 0, resp.Code)
|
||||
require.Equal(t, int64(10), resp.Data.APIKey.ID)
|
||||
}
|
||||
|
||||
// M2: service returns GROUP_NOT_ACTIVE → handler maps to 400
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_GroupNotActive(t *testing.T) {
|
||||
svc := &failingUpdateGroupService{
|
||||
stubAdminService: newStubAdminService(),
|
||||
err: infraerrors.BadRequest("GROUP_NOT_ACTIVE", "target group is not active"),
|
||||
}
|
||||
router := setupAPIKeyHandler(svc)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(`{"group_id": 5}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
require.Contains(t, rec.Body.String(), "GROUP_NOT_ACTIVE")
|
||||
}
|
||||
|
||||
// M2: service returns INVALID_GROUP_ID → handler maps to 400
|
||||
func TestAdminAPIKeyHandler_UpdateGroup_NegativeGroupID(t *testing.T) {
|
||||
svc := &failingUpdateGroupService{
|
||||
stubAdminService: newStubAdminService(),
|
||||
err: infraerrors.BadRequest("INVALID_GROUP_ID", "group_id must be non-negative"),
|
||||
}
|
||||
router := setupAPIKeyHandler(svc)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/api-keys/10", bytes.NewBufferString(`{"group_id": -5}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
require.Contains(t, rec.Body.String(), "INVALID_GROUP_ID")
|
||||
}
|
||||
|
||||
// failingUpdateGroupService overrides AdminUpdateAPIKeyGroupID to return an error.
|
||||
type failingUpdateGroupService struct {
|
||||
*stubAdminService
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *failingUpdateGroupService) AdminUpdateAPIKeyGroupID(_ context.Context, _ int64, _ *int64) (*service.AdminUpdateAPIKeyGroupIDResult, error) {
|
||||
return nil, f.err
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (f *failingAdminService) UpdateAccount(ctx context.Context, id int64, input
|
||||
func setupAccountHandlerWithService(adminSvc service.AdminService) (*gin.Engine, *AccountHandler) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
handler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
handler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router.POST("/api/v1/admin/accounts/batch-update-credentials", handler.BatchUpdateCredentials)
|
||||
return router, handler
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
@@ -186,7 +188,7 @@ func (h *DashboardHandler) GetRealtimeMetrics(c *gin.Context) {
|
||||
|
||||
// GetUsageTrend handles getting usage trend data
|
||||
// GET /api/v1/admin/dashboard/trend
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), user_id, api_key_id, model, account_id, group_id, stream, billing_type
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), user_id, api_key_id, model, account_id, group_id, request_type, stream, billing_type
|
||||
func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||
startTime, endTime := parseTimeRange(c)
|
||||
granularity := c.DefaultQuery("granularity", "day")
|
||||
@@ -194,6 +196,7 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||
// Parse optional filter params
|
||||
var userID, apiKeyID, accountID, groupID int64
|
||||
var model string
|
||||
var requestType *int16
|
||||
var stream *bool
|
||||
var billingType *int8
|
||||
|
||||
@@ -220,9 +223,20 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||
if modelStr := c.Query("model"); modelStr != "" {
|
||||
model = modelStr
|
||||
}
|
||||
if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
} else if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if streamVal, err := strconv.ParseBool(streamStr); err == nil {
|
||||
stream = &streamVal
|
||||
} else {
|
||||
response.BadRequest(c, "Invalid stream value, use true or false")
|
||||
return
|
||||
}
|
||||
}
|
||||
if billingTypeStr := c.Query("billing_type"); billingTypeStr != "" {
|
||||
@@ -235,7 +249,7 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, stream, billingType)
|
||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, requestType, stream, billingType)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get usage trend")
|
||||
return
|
||||
@@ -251,12 +265,13 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||
|
||||
// GetModelStats handles getting model usage statistics
|
||||
// GET /api/v1/admin/dashboard/models
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), user_id, api_key_id, account_id, group_id, stream, billing_type
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), user_id, api_key_id, account_id, group_id, request_type, stream, billing_type
|
||||
func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
||||
startTime, endTime := parseTimeRange(c)
|
||||
|
||||
// Parse optional filter params
|
||||
var userID, apiKeyID, accountID, groupID int64
|
||||
var requestType *int16
|
||||
var stream *bool
|
||||
var billingType *int8
|
||||
|
||||
@@ -280,9 +295,20 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
||||
groupID = id
|
||||
}
|
||||
}
|
||||
if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
} else if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if streamVal, err := strconv.ParseBool(streamStr); err == nil {
|
||||
stream = &streamVal
|
||||
} else {
|
||||
response.BadRequest(c, "Invalid stream value, use true or false")
|
||||
return
|
||||
}
|
||||
}
|
||||
if billingTypeStr := c.Query("billing_type"); billingTypeStr != "" {
|
||||
@@ -295,7 +321,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, stream, billingType)
|
||||
stats, err := h.dashboardService.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get model statistics")
|
||||
return
|
||||
@@ -308,6 +334,76 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetGroupStats handles getting group usage statistics
|
||||
// GET /api/v1/admin/dashboard/groups
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), user_id, api_key_id, account_id, group_id, request_type, stream, billing_type
|
||||
func (h *DashboardHandler) GetGroupStats(c *gin.Context) {
|
||||
startTime, endTime := parseTimeRange(c)
|
||||
|
||||
var userID, apiKeyID, accountID, groupID int64
|
||||
var requestType *int16
|
||||
var stream *bool
|
||||
var billingType *int8
|
||||
|
||||
if userIDStr := c.Query("user_id"); userIDStr != "" {
|
||||
if id, err := strconv.ParseInt(userIDStr, 10, 64); err == nil {
|
||||
userID = id
|
||||
}
|
||||
}
|
||||
if apiKeyIDStr := c.Query("api_key_id"); apiKeyIDStr != "" {
|
||||
if id, err := strconv.ParseInt(apiKeyIDStr, 10, 64); err == nil {
|
||||
apiKeyID = id
|
||||
}
|
||||
}
|
||||
if accountIDStr := c.Query("account_id"); accountIDStr != "" {
|
||||
if id, err := strconv.ParseInt(accountIDStr, 10, 64); err == nil {
|
||||
accountID = id
|
||||
}
|
||||
}
|
||||
if groupIDStr := c.Query("group_id"); groupIDStr != "" {
|
||||
if id, err := strconv.ParseInt(groupIDStr, 10, 64); err == nil {
|
||||
groupID = id
|
||||
}
|
||||
}
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
} else if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if streamVal, err := strconv.ParseBool(streamStr); err == nil {
|
||||
stream = &streamVal
|
||||
} else {
|
||||
response.BadRequest(c, "Invalid stream value, use true or false")
|
||||
return
|
||||
}
|
||||
}
|
||||
if billingTypeStr := c.Query("billing_type"); billingTypeStr != "" {
|
||||
if v, err := strconv.ParseInt(billingTypeStr, 10, 8); err == nil {
|
||||
bt := int8(v)
|
||||
billingType = &bt
|
||||
} else {
|
||||
response.BadRequest(c, "Invalid billing_type")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetGroupStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get group statistics")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"groups": stats,
|
||||
"start_date": startTime.Format("2006-01-02"),
|
||||
"end_date": endTime.Add(-24 * time.Hour).Format("2006-01-02"),
|
||||
})
|
||||
}
|
||||
|
||||
// GetAPIKeyUsageTrend handles getting API key usage trend data
|
||||
// GET /api/v1/admin/dashboard/api-keys-trend
|
||||
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), limit (default 5)
|
||||
@@ -365,6 +461,9 @@ type BatchUsersUsageRequest struct {
|
||||
UserIDs []int64 `json:"user_ids" binding:"required"`
|
||||
}
|
||||
|
||||
var dashboardBatchUsersUsageCache = newSnapshotCache(30 * time.Second)
|
||||
var dashboardBatchAPIKeysUsageCache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
// GetBatchUsersUsage handles getting usage stats for multiple users
|
||||
// POST /api/v1/admin/dashboard/users-usage
|
||||
func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
|
||||
@@ -374,18 +473,34 @@ func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.UserIDs) == 0 {
|
||||
userIDs := normalizeInt64IDList(req.UserIDs)
|
||||
if len(userIDs) == 0 {
|
||||
response.Success(c, gin.H{"stats": map[string]any{}})
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetBatchUserUsageStats(c.Request.Context(), req.UserIDs, time.Time{}, time.Time{})
|
||||
keyRaw, _ := json.Marshal(struct {
|
||||
UserIDs []int64 `json:"user_ids"`
|
||||
}{
|
||||
UserIDs: userIDs,
|
||||
})
|
||||
cacheKey := string(keyRaw)
|
||||
if cached, ok := dashboardBatchUsersUsageCache.Get(cacheKey); ok {
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetBatchUserUsageStats(c.Request.Context(), userIDs, time.Time{}, time.Time{})
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get user usage stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"stats": stats})
|
||||
payload := gin.H{"stats": stats}
|
||||
dashboardBatchUsersUsageCache.Set(cacheKey, payload)
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, payload)
|
||||
}
|
||||
|
||||
// BatchAPIKeysUsageRequest represents the request body for batch api key usage stats
|
||||
@@ -402,16 +517,32 @@ func (h *DashboardHandler) GetBatchAPIKeysUsage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.APIKeyIDs) == 0 {
|
||||
apiKeyIDs := normalizeInt64IDList(req.APIKeyIDs)
|
||||
if len(apiKeyIDs) == 0 {
|
||||
response.Success(c, gin.H{"stats": map[string]any{}})
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetBatchAPIKeyUsageStats(c.Request.Context(), req.APIKeyIDs, time.Time{}, time.Time{})
|
||||
keyRaw, _ := json.Marshal(struct {
|
||||
APIKeyIDs []int64 `json:"api_key_ids"`
|
||||
}{
|
||||
APIKeyIDs: apiKeyIDs,
|
||||
})
|
||||
cacheKey := string(keyRaw)
|
||||
if cached, ok := dashboardBatchAPIKeysUsageCache.Get(cacheKey); ok {
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.dashboardService.GetBatchAPIKeyUsageStats(c.Request.Context(), apiKeyIDs, time.Time{}, time.Time{})
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get API key usage stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"stats": stats})
|
||||
payload := gin.H{"stats": stats}
|
||||
dashboardBatchAPIKeysUsageCache.Set(cacheKey, payload)
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, payload)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type dashboardUsageRepoCapture struct {
|
||||
service.UsageLogRepository
|
||||
trendRequestType *int16
|
||||
trendStream *bool
|
||||
modelRequestType *int16
|
||||
modelStream *bool
|
||||
}
|
||||
|
||||
func (s *dashboardUsageRepoCapture) GetUsageTrendWithFilters(
|
||||
ctx context.Context,
|
||||
startTime, endTime time.Time,
|
||||
granularity string,
|
||||
userID, apiKeyID, accountID, groupID int64,
|
||||
model string,
|
||||
requestType *int16,
|
||||
stream *bool,
|
||||
billingType *int8,
|
||||
) ([]usagestats.TrendDataPoint, error) {
|
||||
s.trendRequestType = requestType
|
||||
s.trendStream = stream
|
||||
return []usagestats.TrendDataPoint{}, nil
|
||||
}
|
||||
|
||||
func (s *dashboardUsageRepoCapture) GetModelStatsWithFilters(
|
||||
ctx context.Context,
|
||||
startTime, endTime time.Time,
|
||||
userID, apiKeyID, accountID, groupID int64,
|
||||
requestType *int16,
|
||||
stream *bool,
|
||||
billingType *int8,
|
||||
) ([]usagestats.ModelStat, error) {
|
||||
s.modelRequestType = requestType
|
||||
s.modelStream = stream
|
||||
return []usagestats.ModelStat{}, nil
|
||||
}
|
||||
|
||||
func newDashboardRequestTypeTestRouter(repo *dashboardUsageRepoCapture) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
dashboardSvc := service.NewDashboardService(repo, nil, nil, nil)
|
||||
handler := NewDashboardHandler(dashboardSvc, nil)
|
||||
router := gin.New()
|
||||
router.GET("/admin/dashboard/trend", handler.GetUsageTrend)
|
||||
router.GET("/admin/dashboard/models", handler.GetModelStats)
|
||||
return router
|
||||
}
|
||||
|
||||
func TestDashboardTrendRequestTypePriority(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/trend?request_type=ws_v2&stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NotNil(t, repo.trendRequestType)
|
||||
require.Equal(t, int16(service.RequestTypeWSV2), *repo.trendRequestType)
|
||||
require.Nil(t, repo.trendStream)
|
||||
}
|
||||
|
||||
func TestDashboardTrendInvalidRequestType(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/trend?request_type=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestDashboardTrendInvalidStream(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/trend?stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestDashboardModelStatsRequestTypePriority(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/models?request_type=sync&stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NotNil(t, repo.modelRequestType)
|
||||
require.Equal(t, int16(service.RequestTypeSync), *repo.modelRequestType)
|
||||
require.Nil(t, repo.modelStream)
|
||||
}
|
||||
|
||||
func TestDashboardModelStatsInvalidRequestType(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/models?request_type=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestDashboardModelStatsInvalidStream(t *testing.T) {
|
||||
repo := &dashboardUsageRepoCapture{}
|
||||
router := newDashboardRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/models?stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
292
backend/internal/handler/admin/dashboard_snapshot_v2_handler.go
Normal file
292
backend/internal/handler/admin/dashboard_snapshot_v2_handler.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var dashboardSnapshotV2Cache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
type dashboardSnapshotV2Stats struct {
|
||||
usagestats.DashboardStats
|
||||
Uptime int64 `json:"uptime"`
|
||||
}
|
||||
|
||||
type dashboardSnapshotV2Response struct {
|
||||
GeneratedAt string `json:"generated_at"`
|
||||
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
Granularity string `json:"granularity"`
|
||||
|
||||
Stats *dashboardSnapshotV2Stats `json:"stats,omitempty"`
|
||||
Trend []usagestats.TrendDataPoint `json:"trend,omitempty"`
|
||||
Models []usagestats.ModelStat `json:"models,omitempty"`
|
||||
Groups []usagestats.GroupStat `json:"groups,omitempty"`
|
||||
UsersTrend []usagestats.UserUsageTrendPoint `json:"users_trend,omitempty"`
|
||||
}
|
||||
|
||||
type dashboardSnapshotV2Filters struct {
|
||||
UserID int64
|
||||
APIKeyID int64
|
||||
AccountID int64
|
||||
GroupID int64
|
||||
Model string
|
||||
RequestType *int16
|
||||
Stream *bool
|
||||
BillingType *int8
|
||||
}
|
||||
|
||||
type dashboardSnapshotV2CacheKey struct {
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
Granularity string `json:"granularity"`
|
||||
UserID int64 `json:"user_id"`
|
||||
APIKeyID int64 `json:"api_key_id"`
|
||||
AccountID int64 `json:"account_id"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
Model string `json:"model"`
|
||||
RequestType *int16 `json:"request_type"`
|
||||
Stream *bool `json:"stream"`
|
||||
BillingType *int8 `json:"billing_type"`
|
||||
IncludeStats bool `json:"include_stats"`
|
||||
IncludeTrend bool `json:"include_trend"`
|
||||
IncludeModels bool `json:"include_models"`
|
||||
IncludeGroups bool `json:"include_groups"`
|
||||
IncludeUsersTrend bool `json:"include_users_trend"`
|
||||
UsersTrendLimit int `json:"users_trend_limit"`
|
||||
}
|
||||
|
||||
func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
||||
startTime, endTime := parseTimeRange(c)
|
||||
granularity := strings.TrimSpace(c.DefaultQuery("granularity", "day"))
|
||||
if granularity != "hour" {
|
||||
granularity = "day"
|
||||
}
|
||||
|
||||
includeStats := parseBoolQueryWithDefault(c.Query("include_stats"), true)
|
||||
includeTrend := parseBoolQueryWithDefault(c.Query("include_trend"), true)
|
||||
includeModels := parseBoolQueryWithDefault(c.Query("include_model_stats"), true)
|
||||
includeGroups := parseBoolQueryWithDefault(c.Query("include_group_stats"), false)
|
||||
includeUsersTrend := parseBoolQueryWithDefault(c.Query("include_users_trend"), false)
|
||||
usersTrendLimit := 12
|
||||
if raw := strings.TrimSpace(c.Query("users_trend_limit")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 50 {
|
||||
usersTrendLimit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
filters, err := parseDashboardSnapshotV2Filters(c)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
keyRaw, _ := json.Marshal(dashboardSnapshotV2CacheKey{
|
||||
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||
Granularity: granularity,
|
||||
UserID: filters.UserID,
|
||||
APIKeyID: filters.APIKeyID,
|
||||
AccountID: filters.AccountID,
|
||||
GroupID: filters.GroupID,
|
||||
Model: filters.Model,
|
||||
RequestType: filters.RequestType,
|
||||
Stream: filters.Stream,
|
||||
BillingType: filters.BillingType,
|
||||
IncludeStats: includeStats,
|
||||
IncludeTrend: includeTrend,
|
||||
IncludeModels: includeModels,
|
||||
IncludeGroups: includeGroups,
|
||||
IncludeUsersTrend: includeUsersTrend,
|
||||
UsersTrendLimit: usersTrendLimit,
|
||||
})
|
||||
cacheKey := string(keyRaw)
|
||||
|
||||
if cached, ok := dashboardSnapshotV2Cache.Get(cacheKey); ok {
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
if ifNoneMatchMatched(c.GetHeader("If-None-Match"), cached.ETag) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &dashboardSnapshotV2Response{
|
||||
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
StartDate: startTime.Format("2006-01-02"),
|
||||
EndDate: endTime.Add(-24 * time.Hour).Format("2006-01-02"),
|
||||
Granularity: granularity,
|
||||
}
|
||||
|
||||
if includeStats {
|
||||
stats, err := h.dashboardService.GetDashboardStats(c.Request.Context())
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get dashboard statistics")
|
||||
return
|
||||
}
|
||||
resp.Stats = &dashboardSnapshotV2Stats{
|
||||
DashboardStats: *stats,
|
||||
Uptime: int64(time.Since(h.startTime).Seconds()),
|
||||
}
|
||||
}
|
||||
|
||||
if includeTrend {
|
||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(
|
||||
c.Request.Context(),
|
||||
startTime,
|
||||
endTime,
|
||||
granularity,
|
||||
filters.UserID,
|
||||
filters.APIKeyID,
|
||||
filters.AccountID,
|
||||
filters.GroupID,
|
||||
filters.Model,
|
||||
filters.RequestType,
|
||||
filters.Stream,
|
||||
filters.BillingType,
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get usage trend")
|
||||
return
|
||||
}
|
||||
resp.Trend = trend
|
||||
}
|
||||
|
||||
if includeModels {
|
||||
models, err := h.dashboardService.GetModelStatsWithFilters(
|
||||
c.Request.Context(),
|
||||
startTime,
|
||||
endTime,
|
||||
filters.UserID,
|
||||
filters.APIKeyID,
|
||||
filters.AccountID,
|
||||
filters.GroupID,
|
||||
filters.RequestType,
|
||||
filters.Stream,
|
||||
filters.BillingType,
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get model statistics")
|
||||
return
|
||||
}
|
||||
resp.Models = models
|
||||
}
|
||||
|
||||
if includeGroups {
|
||||
groups, err := h.dashboardService.GetGroupStatsWithFilters(
|
||||
c.Request.Context(),
|
||||
startTime,
|
||||
endTime,
|
||||
filters.UserID,
|
||||
filters.APIKeyID,
|
||||
filters.AccountID,
|
||||
filters.GroupID,
|
||||
filters.RequestType,
|
||||
filters.Stream,
|
||||
filters.BillingType,
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get group statistics")
|
||||
return
|
||||
}
|
||||
resp.Groups = groups
|
||||
}
|
||||
|
||||
if includeUsersTrend {
|
||||
usersTrend, err := h.dashboardService.GetUserUsageTrend(
|
||||
c.Request.Context(),
|
||||
startTime,
|
||||
endTime,
|
||||
granularity,
|
||||
usersTrendLimit,
|
||||
)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "Failed to get user usage trend")
|
||||
return
|
||||
}
|
||||
resp.UsersTrend = usersTrend
|
||||
}
|
||||
|
||||
cached := dashboardSnapshotV2Cache.Set(cacheKey, resp)
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, resp)
|
||||
}
|
||||
|
||||
func parseDashboardSnapshotV2Filters(c *gin.Context) (*dashboardSnapshotV2Filters, error) {
|
||||
filters := &dashboardSnapshotV2Filters{
|
||||
Model: strings.TrimSpace(c.Query("model")),
|
||||
}
|
||||
|
||||
if userIDStr := strings.TrimSpace(c.Query("user_id")); userIDStr != "" {
|
||||
id, err := strconv.ParseInt(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters.UserID = id
|
||||
}
|
||||
if apiKeyIDStr := strings.TrimSpace(c.Query("api_key_id")); apiKeyIDStr != "" {
|
||||
id, err := strconv.ParseInt(apiKeyIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters.APIKeyID = id
|
||||
}
|
||||
if accountIDStr := strings.TrimSpace(c.Query("account_id")); accountIDStr != "" {
|
||||
id, err := strconv.ParseInt(accountIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters.AccountID = id
|
||||
}
|
||||
if groupIDStr := strings.TrimSpace(c.Query("group_id")); groupIDStr != "" {
|
||||
id, err := strconv.ParseInt(groupIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters.GroupID = id
|
||||
}
|
||||
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := int16(parsed)
|
||||
filters.RequestType = &value
|
||||
} else if streamStr := strings.TrimSpace(c.Query("stream")); streamStr != "" {
|
||||
streamVal, err := strconv.ParseBool(streamStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters.Stream = &streamVal
|
||||
}
|
||||
|
||||
if billingTypeStr := strings.TrimSpace(c.Query("billing_type")); billingTypeStr != "" {
|
||||
v, err := strconv.ParseInt(billingTypeStr, 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bt := int8(v)
|
||||
filters.BillingType = &bt
|
||||
}
|
||||
|
||||
return filters, nil
|
||||
}
|
||||
545
backend/internal/handler/admin/data_management_handler.go
Normal file
545
backend/internal/handler/admin/data_management_handler.go
Normal file
@@ -0,0 +1,545 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DataManagementHandler struct {
|
||||
dataManagementService dataManagementService
|
||||
}
|
||||
|
||||
func NewDataManagementHandler(dataManagementService *service.DataManagementService) *DataManagementHandler {
|
||||
return &DataManagementHandler{dataManagementService: dataManagementService}
|
||||
}
|
||||
|
||||
type dataManagementService interface {
|
||||
GetConfig(ctx context.Context) (service.DataManagementConfig, error)
|
||||
UpdateConfig(ctx context.Context, cfg service.DataManagementConfig) (service.DataManagementConfig, error)
|
||||
ValidateS3(ctx context.Context, cfg service.DataManagementS3Config) (service.DataManagementTestS3Result, error)
|
||||
CreateBackupJob(ctx context.Context, input service.DataManagementCreateBackupJobInput) (service.DataManagementBackupJob, error)
|
||||
ListSourceProfiles(ctx context.Context, sourceType string) ([]service.DataManagementSourceProfile, error)
|
||||
CreateSourceProfile(ctx context.Context, input service.DataManagementCreateSourceProfileInput) (service.DataManagementSourceProfile, error)
|
||||
UpdateSourceProfile(ctx context.Context, input service.DataManagementUpdateSourceProfileInput) (service.DataManagementSourceProfile, error)
|
||||
DeleteSourceProfile(ctx context.Context, sourceType, profileID string) error
|
||||
SetActiveSourceProfile(ctx context.Context, sourceType, profileID string) (service.DataManagementSourceProfile, error)
|
||||
ListS3Profiles(ctx context.Context) ([]service.DataManagementS3Profile, error)
|
||||
CreateS3Profile(ctx context.Context, input service.DataManagementCreateS3ProfileInput) (service.DataManagementS3Profile, error)
|
||||
UpdateS3Profile(ctx context.Context, input service.DataManagementUpdateS3ProfileInput) (service.DataManagementS3Profile, error)
|
||||
DeleteS3Profile(ctx context.Context, profileID string) error
|
||||
SetActiveS3Profile(ctx context.Context, profileID string) (service.DataManagementS3Profile, error)
|
||||
ListBackupJobs(ctx context.Context, input service.DataManagementListBackupJobsInput) (service.DataManagementListBackupJobsResult, error)
|
||||
GetBackupJob(ctx context.Context, jobID string) (service.DataManagementBackupJob, error)
|
||||
EnsureAgentEnabled(ctx context.Context) error
|
||||
GetAgentHealth(ctx context.Context) service.DataManagementAgentHealth
|
||||
}
|
||||
|
||||
type TestS3ConnectionRequest struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region" binding:"required"`
|
||||
Bucket string `json:"bucket" binding:"required"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
UseSSL bool `json:"use_ssl"`
|
||||
}
|
||||
|
||||
type CreateBackupJobRequest struct {
|
||||
BackupType string `json:"backup_type" binding:"required,oneof=postgres redis full"`
|
||||
UploadToS3 bool `json:"upload_to_s3"`
|
||||
S3ProfileID string `json:"s3_profile_id"`
|
||||
PostgresID string `json:"postgres_profile_id"`
|
||||
RedisID string `json:"redis_profile_id"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
type CreateSourceProfileRequest struct {
|
||||
ProfileID string `json:"profile_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Config service.DataManagementSourceConfig `json:"config" binding:"required"`
|
||||
SetActive bool `json:"set_active"`
|
||||
}
|
||||
|
||||
type UpdateSourceProfileRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Config service.DataManagementSourceConfig `json:"config" binding:"required"`
|
||||
}
|
||||
|
||||
type CreateS3ProfileRequest struct {
|
||||
ProfileID string `json:"profile_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
UseSSL bool `json:"use_ssl"`
|
||||
SetActive bool `json:"set_active"`
|
||||
}
|
||||
|
||||
type UpdateS3ProfileRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
UseSSL bool `json:"use_ssl"`
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) GetAgentHealth(c *gin.Context) {
|
||||
health := h.getAgentHealth(c)
|
||||
payload := gin.H{
|
||||
"enabled": health.Enabled,
|
||||
"reason": health.Reason,
|
||||
"socket_path": health.SocketPath,
|
||||
}
|
||||
if health.Agent != nil {
|
||||
payload["agent"] = gin.H{
|
||||
"status": health.Agent.Status,
|
||||
"version": health.Agent.Version,
|
||||
"uptime_seconds": health.Agent.UptimeSeconds,
|
||||
}
|
||||
}
|
||||
response.Success(c, payload)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) GetConfig(c *gin.Context) {
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
cfg, err := h.dataManagementService.GetConfig(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, cfg)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) UpdateConfig(c *gin.Context) {
|
||||
var req service.DataManagementConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
cfg, err := h.dataManagementService.UpdateConfig(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, cfg)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) TestS3(c *gin.Context) {
|
||||
var req TestS3ConnectionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
result, err := h.dataManagementService.ValidateS3(c.Request.Context(), service.DataManagementS3Config{
|
||||
Enabled: true,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
UseSSL: req.UseSSL,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"ok": result.OK, "message": result.Message})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) CreateBackupJob(c *gin.Context) {
|
||||
var req CreateBackupJobRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.IdempotencyKey = normalizeBackupIdempotencyKey(c.GetHeader("X-Idempotency-Key"), req.IdempotencyKey)
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
|
||||
triggeredBy := "admin:unknown"
|
||||
if subject, ok := middleware2.GetAuthSubjectFromContext(c); ok {
|
||||
triggeredBy = "admin:" + strconv.FormatInt(subject.UserID, 10)
|
||||
}
|
||||
job, err := h.dataManagementService.CreateBackupJob(c.Request.Context(), service.DataManagementCreateBackupJobInput{
|
||||
BackupType: req.BackupType,
|
||||
UploadToS3: req.UploadToS3,
|
||||
S3ProfileID: req.S3ProfileID,
|
||||
PostgresID: req.PostgresID,
|
||||
RedisID: req.RedisID,
|
||||
TriggeredBy: triggeredBy,
|
||||
IdempotencyKey: req.IdempotencyKey,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"job_id": job.JobID, "status": job.Status})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) ListSourceProfiles(c *gin.Context) {
|
||||
sourceType := strings.TrimSpace(c.Param("source_type"))
|
||||
if sourceType == "" {
|
||||
response.BadRequest(c, "Invalid source_type")
|
||||
return
|
||||
}
|
||||
if sourceType != "postgres" && sourceType != "redis" {
|
||||
response.BadRequest(c, "source_type must be postgres or redis")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
items, err := h.dataManagementService.ListSourceProfiles(c.Request.Context(), sourceType)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) CreateSourceProfile(c *gin.Context) {
|
||||
sourceType := strings.TrimSpace(c.Param("source_type"))
|
||||
if sourceType != "postgres" && sourceType != "redis" {
|
||||
response.BadRequest(c, "source_type must be postgres or redis")
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateSourceProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
profile, err := h.dataManagementService.CreateSourceProfile(c.Request.Context(), service.DataManagementCreateSourceProfileInput{
|
||||
SourceType: sourceType,
|
||||
ProfileID: req.ProfileID,
|
||||
Name: req.Name,
|
||||
Config: req.Config,
|
||||
SetActive: req.SetActive,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) UpdateSourceProfile(c *gin.Context) {
|
||||
sourceType := strings.TrimSpace(c.Param("source_type"))
|
||||
if sourceType != "postgres" && sourceType != "redis" {
|
||||
response.BadRequest(c, "source_type must be postgres or redis")
|
||||
return
|
||||
}
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSourceProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
profile, err := h.dataManagementService.UpdateSourceProfile(c.Request.Context(), service.DataManagementUpdateSourceProfileInput{
|
||||
SourceType: sourceType,
|
||||
ProfileID: profileID,
|
||||
Name: req.Name,
|
||||
Config: req.Config,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) DeleteSourceProfile(c *gin.Context) {
|
||||
sourceType := strings.TrimSpace(c.Param("source_type"))
|
||||
if sourceType != "postgres" && sourceType != "redis" {
|
||||
response.BadRequest(c, "source_type must be postgres or redis")
|
||||
return
|
||||
}
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
if err := h.dataManagementService.DeleteSourceProfile(c.Request.Context(), sourceType, profileID); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) SetActiveSourceProfile(c *gin.Context) {
|
||||
sourceType := strings.TrimSpace(c.Param("source_type"))
|
||||
if sourceType != "postgres" && sourceType != "redis" {
|
||||
response.BadRequest(c, "source_type must be postgres or redis")
|
||||
return
|
||||
}
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
profile, err := h.dataManagementService.SetActiveSourceProfile(c.Request.Context(), sourceType, profileID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) ListS3Profiles(c *gin.Context) {
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
|
||||
items, err := h.dataManagementService.ListS3Profiles(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) CreateS3Profile(c *gin.Context) {
|
||||
var req CreateS3ProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := h.dataManagementService.CreateS3Profile(c.Request.Context(), service.DataManagementCreateS3ProfileInput{
|
||||
ProfileID: req.ProfileID,
|
||||
Name: req.Name,
|
||||
SetActive: req.SetActive,
|
||||
S3: service.DataManagementS3Config{
|
||||
Enabled: req.Enabled,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
UseSSL: req.UseSSL,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) UpdateS3Profile(c *gin.Context) {
|
||||
var req UpdateS3ProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := h.dataManagementService.UpdateS3Profile(c.Request.Context(), service.DataManagementUpdateS3ProfileInput{
|
||||
ProfileID: profileID,
|
||||
Name: req.Name,
|
||||
S3: service.DataManagementS3Config{
|
||||
Enabled: req.Enabled,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
UseSSL: req.UseSSL,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) DeleteS3Profile(c *gin.Context) {
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
if err := h.dataManagementService.DeleteS3Profile(c.Request.Context(), profileID); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) SetActiveS3Profile(c *gin.Context) {
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Invalid profile_id")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
profile, err := h.dataManagementService.SetActiveS3Profile(c.Request.Context(), profileID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, profile)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) ListBackupJobs(c *gin.Context) {
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
|
||||
pageSize := int32(20)
|
||||
if raw := strings.TrimSpace(c.Query("page_size")); raw != "" {
|
||||
v, err := strconv.Atoi(raw)
|
||||
if err != nil || v <= 0 {
|
||||
response.BadRequest(c, "Invalid page_size")
|
||||
return
|
||||
}
|
||||
pageSize = int32(v)
|
||||
}
|
||||
|
||||
result, err := h.dataManagementService.ListBackupJobs(c.Request.Context(), service.DataManagementListBackupJobsInput{
|
||||
PageSize: pageSize,
|
||||
PageToken: c.Query("page_token"),
|
||||
Status: c.Query("status"),
|
||||
BackupType: c.Query("backup_type"),
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) GetBackupJob(c *gin.Context) {
|
||||
jobID := strings.TrimSpace(c.Param("job_id"))
|
||||
if jobID == "" {
|
||||
response.BadRequest(c, "Invalid backup job ID")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.requireAgentEnabled(c) {
|
||||
return
|
||||
}
|
||||
job, err := h.dataManagementService.GetBackupJob(c.Request.Context(), jobID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, job)
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) requireAgentEnabled(c *gin.Context) bool {
|
||||
if h.dataManagementService == nil {
|
||||
err := infraerrors.ServiceUnavailable(
|
||||
service.DataManagementAgentUnavailableReason,
|
||||
"data management agent service is not configured",
|
||||
).WithMetadata(map[string]string{"socket_path": service.DefaultDataManagementAgentSocketPath})
|
||||
response.ErrorFrom(c, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if err := h.dataManagementService.EnsureAgentEnabled(c.Request.Context()); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *DataManagementHandler) getAgentHealth(c *gin.Context) service.DataManagementAgentHealth {
|
||||
if h.dataManagementService == nil {
|
||||
return service.DataManagementAgentHealth{
|
||||
Enabled: false,
|
||||
Reason: service.DataManagementAgentUnavailableReason,
|
||||
SocketPath: service.DefaultDataManagementAgentSocketPath,
|
||||
}
|
||||
}
|
||||
return h.dataManagementService.GetAgentHealth(c.Request.Context())
|
||||
}
|
||||
|
||||
func normalizeBackupIdempotencyKey(headerValue, bodyValue string) string {
|
||||
headerKey := strings.TrimSpace(headerValue)
|
||||
if headerKey != "" {
|
||||
return headerKey
|
||||
}
|
||||
return strings.TrimSpace(bodyValue)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type apiEnvelope struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Reason string `json:"reason"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func TestDataManagementHandler_AgentHealthAlways200(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
svc := service.NewDataManagementServiceWithOptions(filepath.Join(t.TempDir(), "missing.sock"), 50*time.Millisecond)
|
||||
h := NewDataManagementHandler(svc)
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/api/v1/admin/data-management/agent/health", h.GetAgentHealth)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/data-management/agent/health", nil)
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
var envelope apiEnvelope
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &envelope))
|
||||
require.Equal(t, 0, envelope.Code)
|
||||
|
||||
var data struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Reason string `json:"reason"`
|
||||
SocketPath string `json:"socket_path"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(envelope.Data, &data))
|
||||
require.False(t, data.Enabled)
|
||||
require.Equal(t, service.DataManagementDeprecatedReason, data.Reason)
|
||||
require.Equal(t, svc.SocketPath(), data.SocketPath)
|
||||
}
|
||||
|
||||
func TestDataManagementHandler_NonHealthRouteReturns503WhenDisabled(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
svc := service.NewDataManagementServiceWithOptions(filepath.Join(t.TempDir(), "missing.sock"), 50*time.Millisecond)
|
||||
h := NewDataManagementHandler(svc)
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/api/v1/admin/data-management/config", h.GetConfig)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/data-management/config", nil)
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusServiceUnavailable, rec.Code)
|
||||
|
||||
var envelope apiEnvelope
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &envelope))
|
||||
require.Equal(t, http.StatusServiceUnavailable, envelope.Code)
|
||||
require.Equal(t, service.DataManagementDeprecatedReason, envelope.Reason)
|
||||
}
|
||||
|
||||
func TestNormalizeBackupIdempotencyKey(t *testing.T) {
|
||||
require.Equal(t, "from-header", normalizeBackupIdempotencyKey("from-header", "from-body"))
|
||||
require.Equal(t, "from-body", normalizeBackupIdempotencyKey(" ", " from-body "))
|
||||
require.Equal(t, "", normalizeBackupIdempotencyKey("", ""))
|
||||
}
|
||||
@@ -51,6 +51,8 @@ type CreateGroupRequest struct {
|
||||
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
||||
// 支持的模型系列(仅 antigravity 平台使用)
|
||||
SupportedModelScopes []string `json:"supported_model_scopes"`
|
||||
// Sora 存储配额
|
||||
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes"`
|
||||
// 从指定分组复制账号(创建后自动绑定)
|
||||
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
||||
}
|
||||
@@ -84,6 +86,8 @@ type UpdateGroupRequest struct {
|
||||
MCPXMLInject *bool `json:"mcp_xml_inject"`
|
||||
// 支持的模型系列(仅 antigravity 平台使用)
|
||||
SupportedModelScopes *[]string `json:"supported_model_scopes"`
|
||||
// Sora 存储配额
|
||||
SoraStorageQuotaBytes *int64 `json:"sora_storage_quota_bytes"`
|
||||
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
|
||||
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
||||
}
|
||||
@@ -198,6 +202,7 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
||||
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||
MCPXMLInject: req.MCPXMLInject,
|
||||
SupportedModelScopes: req.SupportedModelScopes,
|
||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -248,6 +253,7 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
||||
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||
MCPXMLInject: req.MCPXMLInject,
|
||||
SupportedModelScopes: req.SupportedModelScopes,
|
||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
25
backend/internal/handler/admin/id_list_utils.go
Normal file
25
backend/internal/handler/admin/id_list_utils.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package admin
|
||||
|
||||
import "sort"
|
||||
|
||||
func normalizeInt64IDList(ids []int64) []int64 {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]int64, 0, len(ids))
|
||||
seen := make(map[int64]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
if id <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
|
||||
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
|
||||
return out
|
||||
}
|
||||
57
backend/internal/handler/admin/id_list_utils_test.go
Normal file
57
backend/internal/handler/admin/id_list_utils_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
//go:build unit
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNormalizeInt64IDList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []int64
|
||||
want []int64
|
||||
}{
|
||||
{"nil input", nil, nil},
|
||||
{"empty input", []int64{}, nil},
|
||||
{"single element", []int64{5}, []int64{5}},
|
||||
{"already sorted unique", []int64{1, 2, 3}, []int64{1, 2, 3}},
|
||||
{"duplicates removed", []int64{3, 1, 3, 2, 1}, []int64{1, 2, 3}},
|
||||
{"zero filtered", []int64{0, 1, 2}, []int64{1, 2}},
|
||||
{"negative filtered", []int64{-5, -1, 3}, []int64{3}},
|
||||
{"all invalid", []int64{0, -1, -2}, []int64{}},
|
||||
{"sorted output", []int64{9, 3, 7, 1}, []int64{1, 3, 7, 9}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := normalizeInt64IDList(tc.in)
|
||||
if tc.want == nil {
|
||||
require.Nil(t, got)
|
||||
} else {
|
||||
require.Equal(t, tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAccountTodayStatsBatchCacheKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ids []int64
|
||||
want string
|
||||
}{
|
||||
{"empty", nil, "accounts_today_stats_empty"},
|
||||
{"single", []int64{42}, "accounts_today_stats:42"},
|
||||
{"multiple", []int64{1, 2, 3}, "accounts_today_stats:1,2,3"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := buildAccountTodayStatsBatchCacheKey(tc.ids)
|
||||
require.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
@@ -47,7 +48,12 @@ func (h *OpenAIOAuthHandler) GenerateAuthURL(c *gin.Context) {
|
||||
req = OpenAIGenerateAuthURLRequest{}
|
||||
}
|
||||
|
||||
result, err := h.openaiOAuthService.GenerateAuthURL(c.Request.Context(), req.ProxyID, req.RedirectURI)
|
||||
result, err := h.openaiOAuthService.GenerateAuthURL(
|
||||
c.Request.Context(),
|
||||
req.ProxyID,
|
||||
req.RedirectURI,
|
||||
oauthPlatformFromPath(c),
|
||||
)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
@@ -123,7 +129,14 @@ func (h *OpenAIOAuthHandler) RefreshToken(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
tokenInfo, err := h.openaiOAuthService.RefreshTokenWithClientID(c.Request.Context(), refreshToken, proxyURL, strings.TrimSpace(req.ClientID))
|
||||
// 未指定 client_id 时,根据请求路径平台自动设置默认值,避免 repository 层盲猜
|
||||
clientID := strings.TrimSpace(req.ClientID)
|
||||
if clientID == "" {
|
||||
platform := oauthPlatformFromPath(c)
|
||||
clientID, _ = openai.OAuthClientConfigByPlatform(platform)
|
||||
}
|
||||
|
||||
tokenInfo, err := h.openaiOAuthService.RefreshTokenWithClientID(c.Request.Context(), refreshToken, proxyURL, clientID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
|
||||
145
backend/internal/handler/admin/ops_snapshot_v2_handler.go
Normal file
145
backend/internal/handler/admin/ops_snapshot_v2_handler.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var opsDashboardSnapshotV2Cache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
type opsDashboardSnapshotV2Response struct {
|
||||
GeneratedAt string `json:"generated_at"`
|
||||
|
||||
Overview *service.OpsDashboardOverview `json:"overview"`
|
||||
ThroughputTrend *service.OpsThroughputTrendResponse `json:"throughput_trend"`
|
||||
ErrorTrend *service.OpsErrorTrendResponse `json:"error_trend"`
|
||||
}
|
||||
|
||||
type opsDashboardSnapshotV2CacheKey struct {
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
Platform string `json:"platform"`
|
||||
GroupID *int64 `json:"group_id"`
|
||||
QueryMode service.OpsQueryMode `json:"mode"`
|
||||
BucketSecond int `json:"bucket_second"`
|
||||
}
|
||||
|
||||
// GetDashboardSnapshotV2 returns ops dashboard core snapshot in one request.
|
||||
// GET /api/v1/admin/ops/dashboard/snapshot-v2
|
||||
func (h *OpsHandler) GetDashboardSnapshotV2(c *gin.Context) {
|
||||
if h.opsService == nil {
|
||||
response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
|
||||
return
|
||||
}
|
||||
if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
filter := &service.OpsDashboardFilter{
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Platform: strings.TrimSpace(c.Query("platform")),
|
||||
QueryMode: parseOpsQueryMode(c),
|
||||
}
|
||||
if v := strings.TrimSpace(c.Query("group_id")); v != "" {
|
||||
id, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.BadRequest(c, "Invalid group_id")
|
||||
return
|
||||
}
|
||||
filter.GroupID = &id
|
||||
}
|
||||
bucketSeconds := pickThroughputBucketSeconds(endTime.Sub(startTime))
|
||||
|
||||
keyRaw, _ := json.Marshal(opsDashboardSnapshotV2CacheKey{
|
||||
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||
Platform: filter.Platform,
|
||||
GroupID: filter.GroupID,
|
||||
QueryMode: filter.QueryMode,
|
||||
BucketSecond: bucketSeconds,
|
||||
})
|
||||
cacheKey := string(keyRaw)
|
||||
|
||||
if cached, ok := opsDashboardSnapshotV2Cache.Get(cacheKey); ok {
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
if ifNoneMatchMatched(c.GetHeader("If-None-Match"), cached.ETag) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
overview *service.OpsDashboardOverview
|
||||
trend *service.OpsThroughputTrendResponse
|
||||
errTrend *service.OpsErrorTrendResponse
|
||||
)
|
||||
g, gctx := errgroup.WithContext(c.Request.Context())
|
||||
g.Go(func() error {
|
||||
f := *filter
|
||||
result, err := h.opsService.GetDashboardOverview(gctx, &f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
overview = result
|
||||
return nil
|
||||
})
|
||||
g.Go(func() error {
|
||||
f := *filter
|
||||
result, err := h.opsService.GetThroughputTrend(gctx, &f, bucketSeconds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trend = result
|
||||
return nil
|
||||
})
|
||||
g.Go(func() error {
|
||||
f := *filter
|
||||
result, err := h.opsService.GetErrorTrend(gctx, &f, bucketSeconds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errTrend = result
|
||||
return nil
|
||||
})
|
||||
if err := g.Wait(); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &opsDashboardSnapshotV2Response{
|
||||
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
Overview: overview,
|
||||
ThroughputTrend: trend,
|
||||
ErrorTrend: errTrend,
|
||||
}
|
||||
|
||||
cached := opsDashboardSnapshotV2Cache.Set(cacheKey, resp)
|
||||
if cached.ETag != "" {
|
||||
c.Header("ETag", cached.ETag)
|
||||
c.Header("Vary", "If-None-Match")
|
||||
}
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, resp)
|
||||
}
|
||||
@@ -62,7 +62,8 @@ const (
|
||||
)
|
||||
|
||||
var wsConnCount atomic.Int32
|
||||
var wsConnCountByIP sync.Map // map[string]*atomic.Int32
|
||||
var wsConnCountByIPMu sync.Mutex
|
||||
var wsConnCountByIP = make(map[string]int32)
|
||||
|
||||
const qpsWSIdleStopDelay = 30 * time.Second
|
||||
|
||||
@@ -389,42 +390,31 @@ func tryAcquireOpsWSIPSlot(clientIP string, limit int32) bool {
|
||||
if strings.TrimSpace(clientIP) == "" || limit <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
v, _ := wsConnCountByIP.LoadOrStore(clientIP, &atomic.Int32{})
|
||||
counter, ok := v.(*atomic.Int32)
|
||||
if !ok {
|
||||
wsConnCountByIPMu.Lock()
|
||||
defer wsConnCountByIPMu.Unlock()
|
||||
current := wsConnCountByIP[clientIP]
|
||||
if current >= limit {
|
||||
return false
|
||||
}
|
||||
|
||||
for {
|
||||
current := counter.Load()
|
||||
if current >= limit {
|
||||
return false
|
||||
}
|
||||
if counter.CompareAndSwap(current, current+1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
wsConnCountByIP[clientIP] = current + 1
|
||||
return true
|
||||
}
|
||||
|
||||
func releaseOpsWSIPSlot(clientIP string) {
|
||||
if strings.TrimSpace(clientIP) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := wsConnCountByIP.Load(clientIP)
|
||||
wsConnCountByIPMu.Lock()
|
||||
defer wsConnCountByIPMu.Unlock()
|
||||
current, ok := wsConnCountByIP[clientIP]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
counter, ok := v.(*atomic.Int32)
|
||||
if !ok {
|
||||
if current <= 1 {
|
||||
delete(wsConnCountByIP, clientIP)
|
||||
return
|
||||
}
|
||||
next := counter.Add(-1)
|
||||
if next <= 0 {
|
||||
// Best-effort cleanup; safe even if a new slot was acquired concurrently.
|
||||
wsConnCountByIP.Delete(clientIP)
|
||||
}
|
||||
wsConnCountByIP[clientIP] = current - 1
|
||||
}
|
||||
|
||||
func handleQPSWebSocket(parentCtx context.Context, conn *websocket.Conn) {
|
||||
|
||||
@@ -64,9 +64,9 @@ func (h *ProxyHandler) List(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]dto.ProxyWithAccountCount, 0, len(proxies))
|
||||
out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
out = append(out, *dto.ProxyWithAccountCountFromService(&proxies[i]))
|
||||
out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i]))
|
||||
}
|
||||
response.Paginated(c, out, total, page, pageSize)
|
||||
}
|
||||
@@ -83,9 +83,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
out := make([]dto.ProxyWithAccountCount, 0, len(proxies))
|
||||
out := make([]dto.AdminProxyWithAccountCount, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
out = append(out, *dto.ProxyWithAccountCountFromService(&proxies[i]))
|
||||
out = append(out, *dto.ProxyWithAccountCountFromServiceAdmin(&proxies[i]))
|
||||
}
|
||||
response.Success(c, out)
|
||||
return
|
||||
@@ -97,9 +97,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]dto.Proxy, 0, len(proxies))
|
||||
out := make([]dto.AdminProxy, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
out = append(out, *dto.ProxyFromService(&proxies[i]))
|
||||
out = append(out, *dto.ProxyFromServiceAdmin(&proxies[i]))
|
||||
}
|
||||
response.Success(c, out)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func (h *ProxyHandler) GetByID(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.ProxyFromService(proxy))
|
||||
response.Success(c, dto.ProxyFromServiceAdmin(proxy))
|
||||
}
|
||||
|
||||
// Create handles creating a new proxy
|
||||
@@ -143,7 +143,7 @@ func (h *ProxyHandler) Create(c *gin.Context) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.ProxyFromService(proxy), nil
|
||||
return dto.ProxyFromServiceAdmin(proxy), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (h *ProxyHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.ProxyFromService(proxy))
|
||||
response.Success(c, dto.ProxyFromServiceAdmin(proxy))
|
||||
}
|
||||
|
||||
// Delete handles deleting a proxy
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
@@ -17,13 +19,15 @@ import (
|
||||
|
||||
// RedeemHandler handles admin redeem code management
|
||||
type RedeemHandler struct {
|
||||
adminService service.AdminService
|
||||
adminService service.AdminService
|
||||
redeemService *service.RedeemService
|
||||
}
|
||||
|
||||
// NewRedeemHandler creates a new admin redeem handler
|
||||
func NewRedeemHandler(adminService service.AdminService) *RedeemHandler {
|
||||
func NewRedeemHandler(adminService service.AdminService, redeemService *service.RedeemService) *RedeemHandler {
|
||||
return &RedeemHandler{
|
||||
adminService: adminService,
|
||||
adminService: adminService,
|
||||
redeemService: redeemService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +40,15 @@ type GenerateRedeemCodesRequest struct {
|
||||
ValidityDays int `json:"validity_days" binding:"omitempty,max=36500"` // 订阅类型使用,默认30天,最大100年
|
||||
}
|
||||
|
||||
// CreateAndRedeemCodeRequest represents creating a fixed code and redeeming it for a target user.
|
||||
type CreateAndRedeemCodeRequest struct {
|
||||
Code string `json:"code" binding:"required,min=3,max=128"`
|
||||
Type string `json:"type" binding:"required,oneof=balance concurrency subscription invitation"`
|
||||
Value float64 `json:"value" binding:"required,gt=0"`
|
||||
UserID int64 `json:"user_id" binding:"required,gt=0"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// List handles listing all redeem codes with pagination
|
||||
// GET /api/v1/admin/redeem-codes
|
||||
func (h *RedeemHandler) List(c *gin.Context) {
|
||||
@@ -109,6 +122,81 @@ func (h *RedeemHandler) Generate(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// CreateAndRedeem creates a fixed redeem code and redeems it for a target user in one step.
|
||||
// POST /api/v1/admin/redeem-codes/create-and-redeem
|
||||
func (h *RedeemHandler) CreateAndRedeem(c *gin.Context) {
|
||||
if h.redeemService == nil {
|
||||
response.InternalError(c, "redeem service not configured")
|
||||
return
|
||||
}
|
||||
|
||||
var req CreateAndRedeemCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
req.Code = strings.TrimSpace(req.Code)
|
||||
|
||||
executeAdminIdempotentJSON(c, "admin.redeem_codes.create_and_redeem", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
||||
existing, err := h.redeemService.GetByCode(ctx, req.Code)
|
||||
if err == nil {
|
||||
return h.resolveCreateAndRedeemExisting(ctx, existing, req.UserID)
|
||||
}
|
||||
if !errors.Is(err, service.ErrRedeemCodeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createErr := h.redeemService.CreateCode(ctx, &service.RedeemCode{
|
||||
Code: req.Code,
|
||||
Type: req.Type,
|
||||
Value: req.Value,
|
||||
Status: service.StatusUnused,
|
||||
Notes: req.Notes,
|
||||
})
|
||||
if createErr != nil {
|
||||
// Unique code race: if code now exists, use idempotent semantics by used_by.
|
||||
existingAfterCreateErr, getErr := h.redeemService.GetByCode(ctx, req.Code)
|
||||
if getErr == nil {
|
||||
return h.resolveCreateAndRedeemExisting(ctx, existingAfterCreateErr, req.UserID)
|
||||
}
|
||||
return nil, createErr
|
||||
}
|
||||
|
||||
redeemed, redeemErr := h.redeemService.Redeem(ctx, req.UserID, req.Code)
|
||||
if redeemErr != nil {
|
||||
return nil, redeemErr
|
||||
}
|
||||
return gin.H{"redeem_code": dto.RedeemCodeFromServiceAdmin(redeemed)}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (h *RedeemHandler) resolveCreateAndRedeemExisting(ctx context.Context, existing *service.RedeemCode, userID int64) (any, error) {
|
||||
if existing == nil {
|
||||
return nil, infraerrors.Conflict("REDEEM_CODE_CONFLICT", "redeem code conflict")
|
||||
}
|
||||
|
||||
// If previous run created the code but crashed before redeem, redeem it now.
|
||||
if existing.CanUse() {
|
||||
redeemed, err := h.redeemService.Redeem(ctx, userID, existing.Code)
|
||||
if err == nil {
|
||||
return gin.H{"redeem_code": dto.RedeemCodeFromServiceAdmin(redeemed)}, nil
|
||||
}
|
||||
if !errors.Is(err, service.ErrRedeemCodeUsed) {
|
||||
return nil, err
|
||||
}
|
||||
latest, getErr := h.redeemService.GetByCode(ctx, existing.Code)
|
||||
if getErr == nil {
|
||||
existing = latest
|
||||
}
|
||||
}
|
||||
|
||||
if existing.UsedBy != nil && *existing.UsedBy == userID {
|
||||
return gin.H{"redeem_code": dto.RedeemCodeFromServiceAdmin(existing)}, nil
|
||||
}
|
||||
|
||||
return nil, infraerrors.Conflict("REDEEM_CODE_CONFLICT", "redeem code already used by another user")
|
||||
}
|
||||
|
||||
// Delete handles deleting a redeem code
|
||||
// DELETE /api/v1/admin/redeem-codes/:id
|
||||
func (h *RedeemHandler) Delete(c *gin.Context) {
|
||||
|
||||
155
backend/internal/handler/admin/scheduled_test_handler.go
Normal file
155
backend/internal/handler/admin/scheduled_test_handler.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ScheduledTestHandler handles admin scheduled-test-plan management.
|
||||
type ScheduledTestHandler struct {
|
||||
scheduledTestSvc *service.ScheduledTestService
|
||||
}
|
||||
|
||||
// NewScheduledTestHandler creates a new ScheduledTestHandler.
|
||||
func NewScheduledTestHandler(scheduledTestSvc *service.ScheduledTestService) *ScheduledTestHandler {
|
||||
return &ScheduledTestHandler{scheduledTestSvc: scheduledTestSvc}
|
||||
}
|
||||
|
||||
type createScheduledTestPlanRequest struct {
|
||||
AccountID int64 `json:"account_id" binding:"required"`
|
||||
ModelID string `json:"model_id"`
|
||||
CronExpression string `json:"cron_expression" binding:"required"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
MaxResults int `json:"max_results"`
|
||||
}
|
||||
|
||||
type updateScheduledTestPlanRequest struct {
|
||||
ModelID string `json:"model_id"`
|
||||
CronExpression string `json:"cron_expression"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
MaxResults int `json:"max_results"`
|
||||
}
|
||||
|
||||
// ListByAccount GET /admin/accounts/:id/scheduled-test-plans
|
||||
func (h *ScheduledTestHandler) ListByAccount(c *gin.Context) {
|
||||
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
plans, err := h.scheduledTestSvc.ListPlansByAccount(c.Request.Context(), accountID)
|
||||
if err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, plans)
|
||||
}
|
||||
|
||||
// Create POST /admin/scheduled-test-plans
|
||||
func (h *ScheduledTestHandler) Create(c *gin.Context) {
|
||||
var req createScheduledTestPlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan := &service.ScheduledTestPlan{
|
||||
AccountID: req.AccountID,
|
||||
ModelID: req.ModelID,
|
||||
CronExpression: req.CronExpression,
|
||||
Enabled: true,
|
||||
MaxResults: req.MaxResults,
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
plan.Enabled = *req.Enabled
|
||||
}
|
||||
|
||||
created, err := h.scheduledTestSvc.CreatePlan(c.Request.Context(), plan)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, created)
|
||||
}
|
||||
|
||||
// Update PUT /admin/scheduled-test-plans/:id
|
||||
func (h *ScheduledTestHandler) Update(c *gin.Context) {
|
||||
planID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid plan id")
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := h.scheduledTestSvc.GetPlan(c.Request.Context(), planID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "plan not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req updateScheduledTestPlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.ModelID != "" {
|
||||
existing.ModelID = req.ModelID
|
||||
}
|
||||
if req.CronExpression != "" {
|
||||
existing.CronExpression = req.CronExpression
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
existing.Enabled = *req.Enabled
|
||||
}
|
||||
if req.MaxResults > 0 {
|
||||
existing.MaxResults = req.MaxResults
|
||||
}
|
||||
|
||||
updated, err := h.scheduledTestSvc.UpdatePlan(c.Request.Context(), existing)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, updated)
|
||||
}
|
||||
|
||||
// Delete DELETE /admin/scheduled-test-plans/:id
|
||||
func (h *ScheduledTestHandler) Delete(c *gin.Context) {
|
||||
planID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid plan id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.scheduledTestSvc.DeletePlan(c.Request.Context(), planID); err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||
}
|
||||
|
||||
// ListResults GET /admin/scheduled-test-plans/:id/results
|
||||
func (h *ScheduledTestHandler) ListResults(c *gin.Context) {
|
||||
planID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid plan id")
|
||||
return
|
||||
}
|
||||
|
||||
limit := 50
|
||||
if l, err := strconv.Atoi(c.Query("limit")); err == nil && l > 0 {
|
||||
limit = l
|
||||
}
|
||||
|
||||
results, err := h.scheduledTestSvc.ListResults(c.Request.Context(), planID, limit)
|
||||
if err != nil {
|
||||
response.InternalError(c, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, results)
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,21 +20,38 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// semverPattern 预编译 semver 格式校验正则
|
||||
var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
|
||||
|
||||
// menuItemIDPattern validates custom menu item IDs: alphanumeric, hyphens, underscores only.
|
||||
var menuItemIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
|
||||
// generateMenuItemID generates a short random hex ID for a custom menu item.
|
||||
func generateMenuItemID() (string, error) {
|
||||
b := make([]byte, 8)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("generate menu item ID: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// SettingHandler 系统设置处理器
|
||||
type SettingHandler struct {
|
||||
settingService *service.SettingService
|
||||
emailService *service.EmailService
|
||||
turnstileService *service.TurnstileService
|
||||
opsService *service.OpsService
|
||||
soraS3Storage *service.SoraS3Storage
|
||||
}
|
||||
|
||||
// NewSettingHandler 创建系统设置处理器
|
||||
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService) *SettingHandler {
|
||||
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, soraS3Storage *service.SoraS3Storage) *SettingHandler {
|
||||
return &SettingHandler{
|
||||
settingService: settingService,
|
||||
emailService: emailService,
|
||||
turnstileService: turnstileService,
|
||||
opsService: opsService,
|
||||
soraS3Storage: soraS3Storage,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +66,18 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
|
||||
// Check if ops monitoring is enabled (respects config.ops.enabled)
|
||||
opsEnabled := h.opsService != nil && h.opsService.IsMonitoringEnabled(c.Request.Context())
|
||||
defaultSubscriptions := make([]dto.DefaultSubscriptionSetting, 0, len(settings.DefaultSubscriptions))
|
||||
for _, sub := range settings.DefaultSubscriptions {
|
||||
defaultSubscriptions = append(defaultSubscriptions, dto.DefaultSubscriptionSetting{
|
||||
GroupID: sub.GroupID,
|
||||
ValidityDays: sub.ValidityDays,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, dto.SystemSettings{
|
||||
RegistrationEnabled: settings.RegistrationEnabled,
|
||||
EmailVerifyEnabled: settings.EmailVerifyEnabled,
|
||||
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
|
||||
PromoCodeEnabled: settings.PromoCodeEnabled,
|
||||
PasswordResetEnabled: settings.PasswordResetEnabled,
|
||||
InvitationCodeEnabled: settings.InvitationCodeEnabled,
|
||||
@@ -76,8 +107,11 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
HideCcsImportButton: settings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||
SoraClientEnabled: settings.SoraClientEnabled,
|
||||
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
|
||||
DefaultConcurrency: settings.DefaultConcurrency,
|
||||
DefaultBalance: settings.DefaultBalance,
|
||||
DefaultSubscriptions: defaultSubscriptions,
|
||||
EnableModelFallback: settings.EnableModelFallback,
|
||||
FallbackModelAnthropic: settings.FallbackModelAnthropic,
|
||||
FallbackModelOpenAI: settings.FallbackModelOpenAI,
|
||||
@@ -89,18 +123,21 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
||||
OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
|
||||
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
||||
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
||||
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSettingsRequest 更新设置请求
|
||||
type UpdateSettingsRequest struct {
|
||||
// 注册设置
|
||||
RegistrationEnabled bool `json:"registration_enabled"`
|
||||
EmailVerifyEnabled bool `json:"email_verify_enabled"`
|
||||
PromoCodeEnabled bool `json:"promo_code_enabled"`
|
||||
PasswordResetEnabled bool `json:"password_reset_enabled"`
|
||||
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
|
||||
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
|
||||
RegistrationEnabled bool `json:"registration_enabled"`
|
||||
EmailVerifyEnabled bool `json:"email_verify_enabled"`
|
||||
RegistrationEmailSuffixWhitelist []string `json:"registration_email_suffix_whitelist"`
|
||||
PromoCodeEnabled bool `json:"promo_code_enabled"`
|
||||
PasswordResetEnabled bool `json:"password_reset_enabled"`
|
||||
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
|
||||
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
|
||||
|
||||
// 邮件服务设置
|
||||
SMTPHost string `json:"smtp_host"`
|
||||
@@ -123,20 +160,23 @@ type UpdateSettingsRequest struct {
|
||||
LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"`
|
||||
|
||||
// OEM设置
|
||||
SiteName string `json:"site_name"`
|
||||
SiteLogo string `json:"site_logo"`
|
||||
SiteSubtitle string `json:"site_subtitle"`
|
||||
APIBaseURL string `json:"api_base_url"`
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
||||
SiteName string `json:"site_name"`
|
||||
SiteLogo string `json:"site_logo"`
|
||||
SiteSubtitle string `json:"site_subtitle"`
|
||||
APIBaseURL string `json:"api_base_url"`
|
||||
ContactInfo string `json:"contact_info"`
|
||||
DocURL string `json:"doc_url"`
|
||||
HomeContent string `json:"home_content"`
|
||||
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||
PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"`
|
||||
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
||||
SoraClientEnabled bool `json:"sora_client_enabled"`
|
||||
CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"`
|
||||
|
||||
// 默认配置
|
||||
DefaultConcurrency int `json:"default_concurrency"`
|
||||
DefaultBalance float64 `json:"default_balance"`
|
||||
DefaultConcurrency int `json:"default_concurrency"`
|
||||
DefaultBalance float64 `json:"default_balance"`
|
||||
DefaultSubscriptions []dto.DefaultSubscriptionSetting `json:"default_subscriptions"`
|
||||
|
||||
// Model fallback configuration
|
||||
EnableModelFallback bool `json:"enable_model_fallback"`
|
||||
@@ -154,6 +194,11 @@ type UpdateSettingsRequest struct {
|
||||
OpsRealtimeMonitoringEnabled *bool `json:"ops_realtime_monitoring_enabled"`
|
||||
OpsQueryModeDefault *string `json:"ops_query_mode_default"`
|
||||
OpsMetricsIntervalSeconds *int `json:"ops_metrics_interval_seconds"`
|
||||
|
||||
MinClaudeCodeVersion string `json:"min_claude_code_version"`
|
||||
|
||||
// 分组隔离
|
||||
AllowUngroupedKeyScheduling bool `json:"allow_ungrouped_key_scheduling"`
|
||||
}
|
||||
|
||||
// UpdateSettings 更新系统设置
|
||||
@@ -181,6 +226,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
if req.SMTPPort <= 0 {
|
||||
req.SMTPPort = 587
|
||||
}
|
||||
req.DefaultSubscriptions = normalizeDefaultSubscriptions(req.DefaultSubscriptions)
|
||||
|
||||
// Turnstile 参数验证
|
||||
if req.TurnstileEnabled {
|
||||
@@ -276,6 +322,84 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义菜单项验证
|
||||
const (
|
||||
maxCustomMenuItems = 20
|
||||
maxMenuItemLabelLen = 50
|
||||
maxMenuItemURLLen = 2048
|
||||
maxMenuItemIconSVGLen = 10 * 1024 // 10KB
|
||||
maxMenuItemIDLen = 32
|
||||
)
|
||||
|
||||
customMenuJSON := previousSettings.CustomMenuItems
|
||||
if req.CustomMenuItems != nil {
|
||||
items := *req.CustomMenuItems
|
||||
if len(items) > maxCustomMenuItems {
|
||||
response.BadRequest(c, "Too many custom menu items (max 20)")
|
||||
return
|
||||
}
|
||||
for i, item := range items {
|
||||
if strings.TrimSpace(item.Label) == "" {
|
||||
response.BadRequest(c, "Custom menu item label is required")
|
||||
return
|
||||
}
|
||||
if len(item.Label) > maxMenuItemLabelLen {
|
||||
response.BadRequest(c, "Custom menu item label is too long (max 50 characters)")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(item.URL) == "" {
|
||||
response.BadRequest(c, "Custom menu item URL is required")
|
||||
return
|
||||
}
|
||||
if len(item.URL) > maxMenuItemURLLen {
|
||||
response.BadRequest(c, "Custom menu item URL is too long (max 2048 characters)")
|
||||
return
|
||||
}
|
||||
if err := config.ValidateAbsoluteHTTPURL(strings.TrimSpace(item.URL)); err != nil {
|
||||
response.BadRequest(c, "Custom menu item URL must be an absolute http(s) URL")
|
||||
return
|
||||
}
|
||||
if item.Visibility != "user" && item.Visibility != "admin" {
|
||||
response.BadRequest(c, "Custom menu item visibility must be 'user' or 'admin'")
|
||||
return
|
||||
}
|
||||
if len(item.IconSVG) > maxMenuItemIconSVGLen {
|
||||
response.BadRequest(c, "Custom menu item icon SVG is too large (max 10KB)")
|
||||
return
|
||||
}
|
||||
// Auto-generate ID if missing
|
||||
if strings.TrimSpace(item.ID) == "" {
|
||||
id, err := generateMenuItemID()
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "Failed to generate menu item ID")
|
||||
return
|
||||
}
|
||||
items[i].ID = id
|
||||
} else if len(item.ID) > maxMenuItemIDLen {
|
||||
response.BadRequest(c, "Custom menu item ID is too long (max 32 characters)")
|
||||
return
|
||||
} else if !menuItemIDPattern.MatchString(item.ID) {
|
||||
response.BadRequest(c, "Custom menu item ID contains invalid characters (only a-z, A-Z, 0-9, - and _ are allowed)")
|
||||
return
|
||||
}
|
||||
}
|
||||
// ID uniqueness check
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
for _, item := range items {
|
||||
if _, exists := seen[item.ID]; exists {
|
||||
response.BadRequest(c, "Duplicate custom menu item ID: "+item.ID)
|
||||
return
|
||||
}
|
||||
seen[item.ID] = struct{}{}
|
||||
}
|
||||
menuBytes, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Failed to serialize custom menu items")
|
||||
return
|
||||
}
|
||||
customMenuJSON = string(menuBytes)
|
||||
}
|
||||
|
||||
// Ops metrics collector interval validation (seconds).
|
||||
if req.OpsMetricsIntervalSeconds != nil {
|
||||
v := *req.OpsMetricsIntervalSeconds
|
||||
@@ -287,47 +411,68 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
req.OpsMetricsIntervalSeconds = &v
|
||||
}
|
||||
defaultSubscriptions := make([]service.DefaultSubscriptionSetting, 0, len(req.DefaultSubscriptions))
|
||||
for _, sub := range req.DefaultSubscriptions {
|
||||
defaultSubscriptions = append(defaultSubscriptions, service.DefaultSubscriptionSetting{
|
||||
GroupID: sub.GroupID,
|
||||
ValidityDays: sub.ValidityDays,
|
||||
})
|
||||
}
|
||||
|
||||
// 验证最低版本号格式(空字符串=禁用,或合法 semver)
|
||||
if req.MinClaudeCodeVersion != "" {
|
||||
if !semverPattern.MatchString(req.MinClaudeCodeVersion) {
|
||||
response.Error(c, http.StatusBadRequest, "min_claude_code_version must be empty or a valid semver (e.g. 2.1.63)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
settings := &service.SystemSettings{
|
||||
RegistrationEnabled: req.RegistrationEnabled,
|
||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||
PromoCodeEnabled: req.PromoCodeEnabled,
|
||||
PasswordResetEnabled: req.PasswordResetEnabled,
|
||||
InvitationCodeEnabled: req.InvitationCodeEnabled,
|
||||
TotpEnabled: req.TotpEnabled,
|
||||
SMTPHost: req.SMTPHost,
|
||||
SMTPPort: req.SMTPPort,
|
||||
SMTPUsername: req.SMTPUsername,
|
||||
SMTPPassword: req.SMTPPassword,
|
||||
SMTPFrom: req.SMTPFrom,
|
||||
SMTPFromName: req.SMTPFromName,
|
||||
SMTPUseTLS: req.SMTPUseTLS,
|
||||
TurnstileEnabled: req.TurnstileEnabled,
|
||||
TurnstileSiteKey: req.TurnstileSiteKey,
|
||||
TurnstileSecretKey: req.TurnstileSecretKey,
|
||||
LinuxDoConnectEnabled: req.LinuxDoConnectEnabled,
|
||||
LinuxDoConnectClientID: req.LinuxDoConnectClientID,
|
||||
LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret,
|
||||
LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL,
|
||||
SiteName: req.SiteName,
|
||||
SiteLogo: req.SiteLogo,
|
||||
SiteSubtitle: req.SiteSubtitle,
|
||||
APIBaseURL: req.APIBaseURL,
|
||||
ContactInfo: req.ContactInfo,
|
||||
DocURL: req.DocURL,
|
||||
HomeContent: req.HomeContent,
|
||||
HideCcsImportButton: req.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: purchaseEnabled,
|
||||
PurchaseSubscriptionURL: purchaseURL,
|
||||
DefaultConcurrency: req.DefaultConcurrency,
|
||||
DefaultBalance: req.DefaultBalance,
|
||||
EnableModelFallback: req.EnableModelFallback,
|
||||
FallbackModelAnthropic: req.FallbackModelAnthropic,
|
||||
FallbackModelOpenAI: req.FallbackModelOpenAI,
|
||||
FallbackModelGemini: req.FallbackModelGemini,
|
||||
FallbackModelAntigravity: req.FallbackModelAntigravity,
|
||||
EnableIdentityPatch: req.EnableIdentityPatch,
|
||||
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
||||
RegistrationEnabled: req.RegistrationEnabled,
|
||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||
RegistrationEmailSuffixWhitelist: req.RegistrationEmailSuffixWhitelist,
|
||||
PromoCodeEnabled: req.PromoCodeEnabled,
|
||||
PasswordResetEnabled: req.PasswordResetEnabled,
|
||||
InvitationCodeEnabled: req.InvitationCodeEnabled,
|
||||
TotpEnabled: req.TotpEnabled,
|
||||
SMTPHost: req.SMTPHost,
|
||||
SMTPPort: req.SMTPPort,
|
||||
SMTPUsername: req.SMTPUsername,
|
||||
SMTPPassword: req.SMTPPassword,
|
||||
SMTPFrom: req.SMTPFrom,
|
||||
SMTPFromName: req.SMTPFromName,
|
||||
SMTPUseTLS: req.SMTPUseTLS,
|
||||
TurnstileEnabled: req.TurnstileEnabled,
|
||||
TurnstileSiteKey: req.TurnstileSiteKey,
|
||||
TurnstileSecretKey: req.TurnstileSecretKey,
|
||||
LinuxDoConnectEnabled: req.LinuxDoConnectEnabled,
|
||||
LinuxDoConnectClientID: req.LinuxDoConnectClientID,
|
||||
LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret,
|
||||
LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL,
|
||||
SiteName: req.SiteName,
|
||||
SiteLogo: req.SiteLogo,
|
||||
SiteSubtitle: req.SiteSubtitle,
|
||||
APIBaseURL: req.APIBaseURL,
|
||||
ContactInfo: req.ContactInfo,
|
||||
DocURL: req.DocURL,
|
||||
HomeContent: req.HomeContent,
|
||||
HideCcsImportButton: req.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: purchaseEnabled,
|
||||
PurchaseSubscriptionURL: purchaseURL,
|
||||
SoraClientEnabled: req.SoraClientEnabled,
|
||||
CustomMenuItems: customMenuJSON,
|
||||
DefaultConcurrency: req.DefaultConcurrency,
|
||||
DefaultBalance: req.DefaultBalance,
|
||||
DefaultSubscriptions: defaultSubscriptions,
|
||||
EnableModelFallback: req.EnableModelFallback,
|
||||
FallbackModelAnthropic: req.FallbackModelAnthropic,
|
||||
FallbackModelOpenAI: req.FallbackModelOpenAI,
|
||||
FallbackModelGemini: req.FallbackModelGemini,
|
||||
FallbackModelAntigravity: req.FallbackModelAntigravity,
|
||||
EnableIdentityPatch: req.EnableIdentityPatch,
|
||||
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
||||
MinClaudeCodeVersion: req.MinClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling,
|
||||
OpsMonitoringEnabled: func() bool {
|
||||
if req.OpsMonitoringEnabled != nil {
|
||||
return *req.OpsMonitoringEnabled
|
||||
@@ -367,10 +512,18 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
updatedDefaultSubscriptions := make([]dto.DefaultSubscriptionSetting, 0, len(updatedSettings.DefaultSubscriptions))
|
||||
for _, sub := range updatedSettings.DefaultSubscriptions {
|
||||
updatedDefaultSubscriptions = append(updatedDefaultSubscriptions, dto.DefaultSubscriptionSetting{
|
||||
GroupID: sub.GroupID,
|
||||
ValidityDays: sub.ValidityDays,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, dto.SystemSettings{
|
||||
RegistrationEnabled: updatedSettings.RegistrationEnabled,
|
||||
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
|
||||
RegistrationEmailSuffixWhitelist: updatedSettings.RegistrationEmailSuffixWhitelist,
|
||||
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
|
||||
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
|
||||
InvitationCodeEnabled: updatedSettings.InvitationCodeEnabled,
|
||||
@@ -400,8 +553,11 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
HideCcsImportButton: updatedSettings.HideCcsImportButton,
|
||||
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
|
||||
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
|
||||
SoraClientEnabled: updatedSettings.SoraClientEnabled,
|
||||
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
|
||||
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
||||
DefaultBalance: updatedSettings.DefaultBalance,
|
||||
DefaultSubscriptions: updatedDefaultSubscriptions,
|
||||
EnableModelFallback: updatedSettings.EnableModelFallback,
|
||||
FallbackModelAnthropic: updatedSettings.FallbackModelAnthropic,
|
||||
FallbackModelOpenAI: updatedSettings.FallbackModelOpenAI,
|
||||
@@ -413,6 +569,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
OpsRealtimeMonitoringEnabled: updatedSettings.OpsRealtimeMonitoringEnabled,
|
||||
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
|
||||
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
|
||||
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
|
||||
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -444,6 +602,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.EmailVerifyEnabled != after.EmailVerifyEnabled {
|
||||
changed = append(changed, "email_verify_enabled")
|
||||
}
|
||||
if !equalStringSlice(before.RegistrationEmailSuffixWhitelist, after.RegistrationEmailSuffixWhitelist) {
|
||||
changed = append(changed, "registration_email_suffix_whitelist")
|
||||
}
|
||||
if before.PasswordResetEnabled != after.PasswordResetEnabled {
|
||||
changed = append(changed, "password_reset_enabled")
|
||||
}
|
||||
@@ -522,6 +683,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.DefaultBalance != after.DefaultBalance {
|
||||
changed = append(changed, "default_balance")
|
||||
}
|
||||
if !equalDefaultSubscriptions(before.DefaultSubscriptions, after.DefaultSubscriptions) {
|
||||
changed = append(changed, "default_subscriptions")
|
||||
}
|
||||
if before.EnableModelFallback != after.EnableModelFallback {
|
||||
changed = append(changed, "enable_model_fallback")
|
||||
}
|
||||
@@ -555,9 +719,65 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.OpsMetricsIntervalSeconds != after.OpsMetricsIntervalSeconds {
|
||||
changed = append(changed, "ops_metrics_interval_seconds")
|
||||
}
|
||||
if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
|
||||
changed = append(changed, "min_claude_code_version")
|
||||
}
|
||||
if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
|
||||
changed = append(changed, "allow_ungrouped_key_scheduling")
|
||||
}
|
||||
if before.PurchaseSubscriptionEnabled != after.PurchaseSubscriptionEnabled {
|
||||
changed = append(changed, "purchase_subscription_enabled")
|
||||
}
|
||||
if before.PurchaseSubscriptionURL != after.PurchaseSubscriptionURL {
|
||||
changed = append(changed, "purchase_subscription_url")
|
||||
}
|
||||
if before.CustomMenuItems != after.CustomMenuItems {
|
||||
changed = append(changed, "custom_menu_items")
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func normalizeDefaultSubscriptions(input []dto.DefaultSubscriptionSetting) []dto.DefaultSubscriptionSetting {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
normalized := make([]dto.DefaultSubscriptionSetting, 0, len(input))
|
||||
for _, item := range input {
|
||||
if item.GroupID <= 0 || item.ValidityDays <= 0 {
|
||||
continue
|
||||
}
|
||||
if item.ValidityDays > service.MaxValidityDays {
|
||||
item.ValidityDays = service.MaxValidityDays
|
||||
}
|
||||
normalized = append(normalized, item)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func equalStringSlice(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i].GroupID != b[i].GroupID || a[i].ValidityDays != b[i].ValidityDays {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestSMTPRequest 测试SMTP连接请求
|
||||
type TestSMTPRequest struct {
|
||||
SMTPHost string `json:"smtp_host" binding:"required"`
|
||||
@@ -599,7 +819,7 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
|
||||
|
||||
err := h.emailService.TestSMTPConnectionWithConfig(config)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
response.BadRequest(c, "SMTP connection test failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -685,7 +905,7 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
|
||||
`
|
||||
|
||||
if err := h.emailService.SendEmailWithConfig(config, req.Email, subject, body); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
response.BadRequest(c, "Failed to send test email: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -750,6 +970,384 @@ func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func toSoraS3SettingsDTO(settings *service.SoraS3Settings) dto.SoraS3Settings {
|
||||
if settings == nil {
|
||||
return dto.SoraS3Settings{}
|
||||
}
|
||||
return dto.SoraS3Settings{
|
||||
Enabled: settings.Enabled,
|
||||
Endpoint: settings.Endpoint,
|
||||
Region: settings.Region,
|
||||
Bucket: settings.Bucket,
|
||||
AccessKeyID: settings.AccessKeyID,
|
||||
SecretAccessKeyConfigured: settings.SecretAccessKeyConfigured,
|
||||
Prefix: settings.Prefix,
|
||||
ForcePathStyle: settings.ForcePathStyle,
|
||||
CDNURL: settings.CDNURL,
|
||||
DefaultStorageQuotaBytes: settings.DefaultStorageQuotaBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func toSoraS3ProfileDTO(profile service.SoraS3Profile) dto.SoraS3Profile {
|
||||
return dto.SoraS3Profile{
|
||||
ProfileID: profile.ProfileID,
|
||||
Name: profile.Name,
|
||||
IsActive: profile.IsActive,
|
||||
Enabled: profile.Enabled,
|
||||
Endpoint: profile.Endpoint,
|
||||
Region: profile.Region,
|
||||
Bucket: profile.Bucket,
|
||||
AccessKeyID: profile.AccessKeyID,
|
||||
SecretAccessKeyConfigured: profile.SecretAccessKeyConfigured,
|
||||
Prefix: profile.Prefix,
|
||||
ForcePathStyle: profile.ForcePathStyle,
|
||||
CDNURL: profile.CDNURL,
|
||||
DefaultStorageQuotaBytes: profile.DefaultStorageQuotaBytes,
|
||||
UpdatedAt: profile.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func validateSoraS3RequiredWhenEnabled(enabled bool, endpoint, bucket, accessKeyID, secretAccessKey string, hasStoredSecret bool) error {
|
||||
if !enabled {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(endpoint) == "" {
|
||||
return fmt.Errorf("S3 Endpoint is required when enabled")
|
||||
}
|
||||
if strings.TrimSpace(bucket) == "" {
|
||||
return fmt.Errorf("S3 Bucket is required when enabled")
|
||||
}
|
||||
if strings.TrimSpace(accessKeyID) == "" {
|
||||
return fmt.Errorf("S3 Access Key ID is required when enabled")
|
||||
}
|
||||
if strings.TrimSpace(secretAccessKey) != "" || hasStoredSecret {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("S3 Secret Access Key is required when enabled")
|
||||
}
|
||||
|
||||
func findSoraS3ProfileByID(items []service.SoraS3Profile, profileID string) *service.SoraS3Profile {
|
||||
for idx := range items {
|
||||
if items[idx].ProfileID == profileID {
|
||||
return &items[idx]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSoraS3Settings 获取 Sora S3 存储配置(兼容旧单配置接口)
|
||||
// GET /api/v1/admin/settings/sora-s3
|
||||
func (h *SettingHandler) GetSoraS3Settings(c *gin.Context) {
|
||||
settings, err := h.settingService.GetSoraS3Settings(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, toSoraS3SettingsDTO(settings))
|
||||
}
|
||||
|
||||
// ListSoraS3Profiles 获取 Sora S3 多配置
|
||||
// GET /api/v1/admin/settings/sora-s3/profiles
|
||||
func (h *SettingHandler) ListSoraS3Profiles(c *gin.Context) {
|
||||
result, err := h.settingService.ListSoraS3Profiles(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
items := make([]dto.SoraS3Profile, 0, len(result.Items))
|
||||
for idx := range result.Items {
|
||||
items = append(items, toSoraS3ProfileDTO(result.Items[idx]))
|
||||
}
|
||||
response.Success(c, dto.ListSoraS3ProfilesResponse{
|
||||
ActiveProfileID: result.ActiveProfileID,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSoraS3SettingsRequest 更新/测试 Sora S3 配置请求(兼容旧接口)
|
||||
type UpdateSoraS3SettingsRequest struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
CDNURL string `json:"cdn_url"`
|
||||
DefaultStorageQuotaBytes int64 `json:"default_storage_quota_bytes"`
|
||||
}
|
||||
|
||||
type CreateSoraS3ProfileRequest struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
Name string `json:"name"`
|
||||
SetActive bool `json:"set_active"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
CDNURL string `json:"cdn_url"`
|
||||
DefaultStorageQuotaBytes int64 `json:"default_storage_quota_bytes"`
|
||||
}
|
||||
|
||||
type UpdateSoraS3ProfileRequest struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Region string `json:"region"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
Prefix string `json:"prefix"`
|
||||
ForcePathStyle bool `json:"force_path_style"`
|
||||
CDNURL string `json:"cdn_url"`
|
||||
DefaultStorageQuotaBytes int64 `json:"default_storage_quota_bytes"`
|
||||
}
|
||||
|
||||
// CreateSoraS3Profile 创建 Sora S3 配置
|
||||
// POST /api/v1/admin/settings/sora-s3/profiles
|
||||
func (h *SettingHandler) CreateSoraS3Profile(c *gin.Context) {
|
||||
var req CreateSoraS3ProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.DefaultStorageQuotaBytes < 0 {
|
||||
req.DefaultStorageQuotaBytes = 0
|
||||
}
|
||||
if strings.TrimSpace(req.Name) == "" {
|
||||
response.BadRequest(c, "Name is required")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.ProfileID) == "" {
|
||||
response.BadRequest(c, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
if err := validateSoraS3RequiredWhenEnabled(req.Enabled, req.Endpoint, req.Bucket, req.AccessKeyID, req.SecretAccessKey, false); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
created, err := h.settingService.CreateSoraS3Profile(c.Request.Context(), &service.SoraS3Profile{
|
||||
ProfileID: req.ProfileID,
|
||||
Name: req.Name,
|
||||
Enabled: req.Enabled,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
CDNURL: req.CDNURL,
|
||||
DefaultStorageQuotaBytes: req.DefaultStorageQuotaBytes,
|
||||
}, req.SetActive)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, toSoraS3ProfileDTO(*created))
|
||||
}
|
||||
|
||||
// UpdateSoraS3Profile 更新 Sora S3 配置
|
||||
// PUT /api/v1/admin/settings/sora-s3/profiles/:profile_id
|
||||
func (h *SettingHandler) UpdateSoraS3Profile(c *gin.Context) {
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSoraS3ProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.DefaultStorageQuotaBytes < 0 {
|
||||
req.DefaultStorageQuotaBytes = 0
|
||||
}
|
||||
if strings.TrimSpace(req.Name) == "" {
|
||||
response.BadRequest(c, "Name is required")
|
||||
return
|
||||
}
|
||||
|
||||
existingList, err := h.settingService.ListSoraS3Profiles(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
existing := findSoraS3ProfileByID(existingList.Items, profileID)
|
||||
if existing == nil {
|
||||
response.ErrorFrom(c, service.ErrSoraS3ProfileNotFound)
|
||||
return
|
||||
}
|
||||
if err := validateSoraS3RequiredWhenEnabled(req.Enabled, req.Endpoint, req.Bucket, req.AccessKeyID, req.SecretAccessKey, existing.SecretAccessKeyConfigured); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
updated, updateErr := h.settingService.UpdateSoraS3Profile(c.Request.Context(), profileID, &service.SoraS3Profile{
|
||||
Name: req.Name,
|
||||
Enabled: req.Enabled,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
CDNURL: req.CDNURL,
|
||||
DefaultStorageQuotaBytes: req.DefaultStorageQuotaBytes,
|
||||
})
|
||||
if updateErr != nil {
|
||||
response.ErrorFrom(c, updateErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, toSoraS3ProfileDTO(*updated))
|
||||
}
|
||||
|
||||
// DeleteSoraS3Profile 删除 Sora S3 配置
|
||||
// DELETE /api/v1/admin/settings/sora-s3/profiles/:profile_id
|
||||
func (h *SettingHandler) DeleteSoraS3Profile(c *gin.Context) {
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
if err := h.settingService.DeleteSoraS3Profile(c.Request.Context(), profileID); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
// SetActiveSoraS3Profile 切换激活 Sora S3 配置
|
||||
// POST /api/v1/admin/settings/sora-s3/profiles/:profile_id/activate
|
||||
func (h *SettingHandler) SetActiveSoraS3Profile(c *gin.Context) {
|
||||
profileID := strings.TrimSpace(c.Param("profile_id"))
|
||||
if profileID == "" {
|
||||
response.BadRequest(c, "Profile ID is required")
|
||||
return
|
||||
}
|
||||
active, err := h.settingService.SetActiveSoraS3Profile(c.Request.Context(), profileID)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, toSoraS3ProfileDTO(*active))
|
||||
}
|
||||
|
||||
// UpdateSoraS3Settings 更新 Sora S3 存储配置(兼容旧单配置接口)
|
||||
// PUT /api/v1/admin/settings/sora-s3
|
||||
func (h *SettingHandler) UpdateSoraS3Settings(c *gin.Context) {
|
||||
var req UpdateSoraS3SettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := h.settingService.GetSoraS3Settings(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.DefaultStorageQuotaBytes < 0 {
|
||||
req.DefaultStorageQuotaBytes = 0
|
||||
}
|
||||
if err := validateSoraS3RequiredWhenEnabled(req.Enabled, req.Endpoint, req.Bucket, req.AccessKeyID, req.SecretAccessKey, existing.SecretAccessKeyConfigured); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
settings := &service.SoraS3Settings{
|
||||
Enabled: req.Enabled,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
CDNURL: req.CDNURL,
|
||||
DefaultStorageQuotaBytes: req.DefaultStorageQuotaBytes,
|
||||
}
|
||||
if err := h.settingService.SetSoraS3Settings(c.Request.Context(), settings); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedSettings, err := h.settingService.GetSoraS3Settings(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, toSoraS3SettingsDTO(updatedSettings))
|
||||
}
|
||||
|
||||
// TestSoraS3Connection 测试 Sora S3 连接(HeadBucket)
|
||||
// POST /api/v1/admin/settings/sora-s3/test
|
||||
func (h *SettingHandler) TestSoraS3Connection(c *gin.Context) {
|
||||
if h.soraS3Storage == nil {
|
||||
response.Error(c, 500, "S3 存储服务未初始化")
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSoraS3SettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
if !req.Enabled {
|
||||
response.BadRequest(c, "S3 未启用,无法测试连接")
|
||||
return
|
||||
}
|
||||
|
||||
if req.SecretAccessKey == "" {
|
||||
if req.ProfileID != "" {
|
||||
profiles, err := h.settingService.ListSoraS3Profiles(c.Request.Context())
|
||||
if err == nil {
|
||||
profile := findSoraS3ProfileByID(profiles.Items, req.ProfileID)
|
||||
if profile != nil {
|
||||
req.SecretAccessKey = profile.SecretAccessKey
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.SecretAccessKey == "" {
|
||||
existing, err := h.settingService.GetSoraS3Settings(c.Request.Context())
|
||||
if err == nil {
|
||||
req.SecretAccessKey = existing.SecretAccessKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testCfg := &service.SoraS3Settings{
|
||||
Enabled: true,
|
||||
Endpoint: req.Endpoint,
|
||||
Region: req.Region,
|
||||
Bucket: req.Bucket,
|
||||
AccessKeyID: req.AccessKeyID,
|
||||
SecretAccessKey: req.SecretAccessKey,
|
||||
Prefix: req.Prefix,
|
||||
ForcePathStyle: req.ForcePathStyle,
|
||||
CDNURL: req.CDNURL,
|
||||
}
|
||||
if err := h.soraS3Storage.TestConnectionWithSettings(c.Request.Context(), testCfg); err != nil {
|
||||
response.Error(c, 400, "S3 连接测试失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"message": "S3 连接成功"})
|
||||
}
|
||||
|
||||
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
|
||||
type UpdateStreamTimeoutSettingsRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
95
backend/internal/handler/admin/snapshot_cache.go
Normal file
95
backend/internal/handler/admin/snapshot_cache.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type snapshotCacheEntry struct {
|
||||
ETag string
|
||||
Payload any
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type snapshotCache struct {
|
||||
mu sync.RWMutex
|
||||
ttl time.Duration
|
||||
items map[string]snapshotCacheEntry
|
||||
}
|
||||
|
||||
func newSnapshotCache(ttl time.Duration) *snapshotCache {
|
||||
if ttl <= 0 {
|
||||
ttl = 30 * time.Second
|
||||
}
|
||||
return &snapshotCache{
|
||||
ttl: ttl,
|
||||
items: make(map[string]snapshotCacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *snapshotCache) Get(key string) (snapshotCacheEntry, bool) {
|
||||
if c == nil || key == "" {
|
||||
return snapshotCacheEntry{}, false
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
c.mu.RLock()
|
||||
entry, ok := c.items[key]
|
||||
c.mu.RUnlock()
|
||||
if !ok {
|
||||
return snapshotCacheEntry{}, false
|
||||
}
|
||||
if now.After(entry.ExpiresAt) {
|
||||
c.mu.Lock()
|
||||
delete(c.items, key)
|
||||
c.mu.Unlock()
|
||||
return snapshotCacheEntry{}, false
|
||||
}
|
||||
return entry, true
|
||||
}
|
||||
|
||||
func (c *snapshotCache) Set(key string, payload any) snapshotCacheEntry {
|
||||
if c == nil {
|
||||
return snapshotCacheEntry{}
|
||||
}
|
||||
entry := snapshotCacheEntry{
|
||||
ETag: buildETagFromAny(payload),
|
||||
Payload: payload,
|
||||
ExpiresAt: time.Now().Add(c.ttl),
|
||||
}
|
||||
if key == "" {
|
||||
return entry
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.items[key] = entry
|
||||
c.mu.Unlock()
|
||||
return entry
|
||||
}
|
||||
|
||||
func buildETagFromAny(payload any) string {
|
||||
raw, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
sum := sha256.Sum256(raw)
|
||||
return "\"" + hex.EncodeToString(sum[:]) + "\""
|
||||
}
|
||||
|
||||
func parseBoolQueryWithDefault(raw string, def bool) bool {
|
||||
value := strings.TrimSpace(strings.ToLower(raw))
|
||||
if value == "" {
|
||||
return def
|
||||
}
|
||||
switch value {
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
case "0", "false", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return def
|
||||
}
|
||||
}
|
||||
128
backend/internal/handler/admin/snapshot_cache_test.go
Normal file
128
backend/internal/handler/admin/snapshot_cache_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
//go:build unit
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSnapshotCache_SetAndGet(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
|
||||
entry := c.Set("key1", map[string]string{"hello": "world"})
|
||||
require.NotEmpty(t, entry.ETag)
|
||||
require.NotNil(t, entry.Payload)
|
||||
|
||||
got, ok := c.Get("key1")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, entry.ETag, got.ETag)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_Expiration(t *testing.T) {
|
||||
c := newSnapshotCache(1 * time.Millisecond)
|
||||
|
||||
c.Set("key1", "value")
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
_, ok := c.Get("key1")
|
||||
require.False(t, ok, "expired entry should not be returned")
|
||||
}
|
||||
|
||||
func TestSnapshotCache_GetEmptyKey(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
_, ok := c.Get("")
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_GetMiss(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
_, ok := c.Get("nonexistent")
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_NilReceiver(t *testing.T) {
|
||||
var c *snapshotCache
|
||||
_, ok := c.Get("key")
|
||||
require.False(t, ok)
|
||||
|
||||
entry := c.Set("key", "value")
|
||||
require.Empty(t, entry.ETag)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_SetEmptyKey(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
|
||||
// Set with empty key should return entry but not store it
|
||||
entry := c.Set("", "value")
|
||||
require.NotEmpty(t, entry.ETag)
|
||||
|
||||
_, ok := c.Get("")
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_DefaultTTL(t *testing.T) {
|
||||
c := newSnapshotCache(0)
|
||||
require.Equal(t, 30*time.Second, c.ttl)
|
||||
|
||||
c2 := newSnapshotCache(-1 * time.Second)
|
||||
require.Equal(t, 30*time.Second, c2.ttl)
|
||||
}
|
||||
|
||||
func TestSnapshotCache_ETagDeterministic(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
payload := map[string]int{"a": 1, "b": 2}
|
||||
|
||||
entry1 := c.Set("k1", payload)
|
||||
entry2 := c.Set("k2", payload)
|
||||
require.Equal(t, entry1.ETag, entry2.ETag, "same payload should produce same ETag")
|
||||
}
|
||||
|
||||
func TestSnapshotCache_ETagFormat(t *testing.T) {
|
||||
c := newSnapshotCache(5 * time.Second)
|
||||
entry := c.Set("k", "test")
|
||||
// ETag should be quoted hex string: "abcdef..."
|
||||
require.True(t, len(entry.ETag) > 2)
|
||||
require.Equal(t, byte('"'), entry.ETag[0])
|
||||
require.Equal(t, byte('"'), entry.ETag[len(entry.ETag)-1])
|
||||
}
|
||||
|
||||
func TestBuildETagFromAny_UnmarshalablePayload(t *testing.T) {
|
||||
// channels are not JSON-serializable
|
||||
etag := buildETagFromAny(make(chan int))
|
||||
require.Empty(t, etag)
|
||||
}
|
||||
|
||||
func TestParseBoolQueryWithDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
def bool
|
||||
want bool
|
||||
}{
|
||||
{"empty returns default true", "", true, true},
|
||||
{"empty returns default false", "", false, false},
|
||||
{"1", "1", false, true},
|
||||
{"true", "true", false, true},
|
||||
{"TRUE", "TRUE", false, true},
|
||||
{"yes", "yes", false, true},
|
||||
{"on", "on", false, true},
|
||||
{"0", "0", true, false},
|
||||
{"false", "false", true, false},
|
||||
{"FALSE", "FALSE", true, false},
|
||||
{"no", "no", true, false},
|
||||
{"off", "off", true, false},
|
||||
{"whitespace trimmed", " true ", false, true},
|
||||
{"unknown returns default true", "maybe", true, true},
|
||||
{"unknown returns default false", "maybe", false, false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := parseBoolQueryWithDefault(tc.raw, tc.def)
|
||||
require.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,92 @@ func TestUsageHandlerCreateCleanupTaskInvalidEndDate(t *testing.T) {
|
||||
require.Equal(t, http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
|
||||
func TestUsageHandlerCreateCleanupTaskInvalidRequestType(t *testing.T) {
|
||||
repo := &cleanupRepoStub{}
|
||||
cfg := &config.Config{UsageCleanup: config.UsageCleanupConfig{Enabled: true, MaxRangeDays: 31}}
|
||||
cleanupService := service.NewUsageCleanupService(repo, nil, nil, cfg)
|
||||
router := setupCleanupRouter(cleanupService, 88)
|
||||
|
||||
payload := map[string]any{
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2024-01-02",
|
||||
"timezone": "UTC",
|
||||
"request_type": "invalid",
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/usage/cleanup-tasks", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
|
||||
func TestUsageHandlerCreateCleanupTaskRequestTypePriority(t *testing.T) {
|
||||
repo := &cleanupRepoStub{}
|
||||
cfg := &config.Config{UsageCleanup: config.UsageCleanupConfig{Enabled: true, MaxRangeDays: 31}}
|
||||
cleanupService := service.NewUsageCleanupService(repo, nil, nil, cfg)
|
||||
router := setupCleanupRouter(cleanupService, 99)
|
||||
|
||||
payload := map[string]any{
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2024-01-02",
|
||||
"timezone": "UTC",
|
||||
"request_type": "ws_v2",
|
||||
"stream": false,
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/usage/cleanup-tasks", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
repo.mu.Lock()
|
||||
defer repo.mu.Unlock()
|
||||
require.Len(t, repo.created, 1)
|
||||
created := repo.created[0]
|
||||
require.NotNil(t, created.Filters.RequestType)
|
||||
require.Equal(t, int16(service.RequestTypeWSV2), *created.Filters.RequestType)
|
||||
require.Nil(t, created.Filters.Stream)
|
||||
}
|
||||
|
||||
func TestUsageHandlerCreateCleanupTaskWithLegacyStream(t *testing.T) {
|
||||
repo := &cleanupRepoStub{}
|
||||
cfg := &config.Config{UsageCleanup: config.UsageCleanupConfig{Enabled: true, MaxRangeDays: 31}}
|
||||
cleanupService := service.NewUsageCleanupService(repo, nil, nil, cfg)
|
||||
router := setupCleanupRouter(cleanupService, 99)
|
||||
|
||||
payload := map[string]any{
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2024-01-02",
|
||||
"timezone": "UTC",
|
||||
"stream": true,
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/usage/cleanup-tasks", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
repo.mu.Lock()
|
||||
defer repo.mu.Unlock()
|
||||
require.Len(t, repo.created, 1)
|
||||
created := repo.created[0]
|
||||
require.Nil(t, created.Filters.RequestType)
|
||||
require.NotNil(t, created.Filters.Stream)
|
||||
require.True(t, *created.Filters.Stream)
|
||||
}
|
||||
|
||||
func TestUsageHandlerCreateCleanupTaskSuccess(t *testing.T) {
|
||||
repo := &cleanupRepoStub{}
|
||||
cfg := &config.Config{UsageCleanup: config.UsageCleanupConfig{Enabled: true, MaxRangeDays: 31}}
|
||||
|
||||
@@ -51,6 +51,7 @@ type CreateUsageCleanupTaskRequest struct {
|
||||
AccountID *int64 `json:"account_id"`
|
||||
GroupID *int64 `json:"group_id"`
|
||||
Model *string `json:"model"`
|
||||
RequestType *string `json:"request_type"`
|
||||
Stream *bool `json:"stream"`
|
||||
BillingType *int8 `json:"billing_type"`
|
||||
Timezone string `json:"timezone"`
|
||||
@@ -60,6 +61,15 @@ type CreateUsageCleanupTaskRequest struct {
|
||||
// GET /api/v1/admin/usage
|
||||
func (h *UsageHandler) List(c *gin.Context) {
|
||||
page, pageSize := response.ParsePagination(c)
|
||||
exactTotal := false
|
||||
if exactTotalRaw := strings.TrimSpace(c.Query("exact_total")); exactTotalRaw != "" {
|
||||
parsed, err := strconv.ParseBool(exactTotalRaw)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid exact_total value, use true or false")
|
||||
return
|
||||
}
|
||||
exactTotal = parsed
|
||||
}
|
||||
|
||||
// Parse filters
|
||||
var userID, apiKeyID, accountID, groupID int64
|
||||
@@ -101,8 +111,17 @@ func (h *UsageHandler) List(c *gin.Context) {
|
||||
|
||||
model := c.Query("model")
|
||||
|
||||
var requestType *int16
|
||||
var stream *bool
|
||||
if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
} else if streamStr := c.Query("stream"); streamStr != "" {
|
||||
val, err := strconv.ParseBool(streamStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid stream value, use true or false")
|
||||
@@ -152,10 +171,12 @@ func (h *UsageHandler) List(c *gin.Context) {
|
||||
AccountID: accountID,
|
||||
GroupID: groupID,
|
||||
Model: model,
|
||||
RequestType: requestType,
|
||||
Stream: stream,
|
||||
BillingType: billingType,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
ExactTotal: exactTotal,
|
||||
}
|
||||
|
||||
records, result, err := h.usageService.ListWithFilters(c.Request.Context(), params, filters)
|
||||
@@ -214,8 +235,17 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
||||
|
||||
model := c.Query("model")
|
||||
|
||||
var requestType *int16
|
||||
var stream *bool
|
||||
if streamStr := c.Query("stream"); streamStr != "" {
|
||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
} else if streamStr := c.Query("stream"); streamStr != "" {
|
||||
val, err := strconv.ParseBool(streamStr)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid stream value, use true or false")
|
||||
@@ -278,6 +308,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
||||
AccountID: accountID,
|
||||
GroupID: groupID,
|
||||
Model: model,
|
||||
RequestType: requestType,
|
||||
Stream: stream,
|
||||
BillingType: billingType,
|
||||
StartTime: &startTime,
|
||||
@@ -432,6 +463,19 @@ func (h *UsageHandler) CreateCleanupTask(c *gin.Context) {
|
||||
}
|
||||
endTime = endTime.Add(24*time.Hour - time.Nanosecond)
|
||||
|
||||
var requestType *int16
|
||||
stream := req.Stream
|
||||
if req.RequestType != nil {
|
||||
parsed, err := service.ParseUsageRequestType(*req.RequestType)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
value := int16(parsed)
|
||||
requestType = &value
|
||||
stream = nil
|
||||
}
|
||||
|
||||
filters := service.UsageCleanupFilters{
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
@@ -440,7 +484,8 @@ func (h *UsageHandler) CreateCleanupTask(c *gin.Context) {
|
||||
AccountID: req.AccountID,
|
||||
GroupID: req.GroupID,
|
||||
Model: req.Model,
|
||||
Stream: req.Stream,
|
||||
RequestType: requestType,
|
||||
Stream: stream,
|
||||
BillingType: req.BillingType,
|
||||
}
|
||||
|
||||
@@ -464,9 +509,13 @@ func (h *UsageHandler) CreateCleanupTask(c *gin.Context) {
|
||||
if filters.Model != nil {
|
||||
model = *filters.Model
|
||||
}
|
||||
var stream any
|
||||
var streamValue any
|
||||
if filters.Stream != nil {
|
||||
stream = *filters.Stream
|
||||
streamValue = *filters.Stream
|
||||
}
|
||||
var requestTypeName any
|
||||
if filters.RequestType != nil {
|
||||
requestTypeName = service.RequestTypeFromInt16(*filters.RequestType).String()
|
||||
}
|
||||
var billingType any
|
||||
if filters.BillingType != nil {
|
||||
@@ -481,7 +530,7 @@ func (h *UsageHandler) CreateCleanupTask(c *gin.Context) {
|
||||
Body: req,
|
||||
}
|
||||
executeAdminIdempotentJSON(c, "admin.usage.cleanup_tasks.create", idempotencyPayload, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
||||
logger.LegacyPrintf("handler.admin.usage", "[UsageCleanup] 请求创建清理任务: operator=%d start=%s end=%s user_id=%v api_key_id=%v account_id=%v group_id=%v model=%v stream=%v billing_type=%v tz=%q",
|
||||
logger.LegacyPrintf("handler.admin.usage", "[UsageCleanup] 请求创建清理任务: operator=%d start=%s end=%s user_id=%v api_key_id=%v account_id=%v group_id=%v model=%v request_type=%v stream=%v billing_type=%v tz=%q",
|
||||
subject.UserID,
|
||||
filters.StartTime.Format(time.RFC3339),
|
||||
filters.EndTime.Format(time.RFC3339),
|
||||
@@ -490,7 +539,8 @@ func (h *UsageHandler) CreateCleanupTask(c *gin.Context) {
|
||||
accountID,
|
||||
groupID,
|
||||
model,
|
||||
stream,
|
||||
requestTypeName,
|
||||
streamValue,
|
||||
billingType,
|
||||
req.Timezone,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type adminUsageRepoCapture struct {
|
||||
service.UsageLogRepository
|
||||
listFilters usagestats.UsageLogFilters
|
||||
statsFilters usagestats.UsageLogFilters
|
||||
}
|
||||
|
||||
func (s *adminUsageRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]service.UsageLog, *pagination.PaginationResult, error) {
|
||||
s.listFilters = filters
|
||||
return []service.UsageLog{}, &pagination.PaginationResult{
|
||||
Total: 0,
|
||||
Page: params.Page,
|
||||
PageSize: params.PageSize,
|
||||
Pages: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *adminUsageRepoCapture) GetStatsWithFilters(ctx context.Context, filters usagestats.UsageLogFilters) (*usagestats.UsageStats, error) {
|
||||
s.statsFilters = filters
|
||||
return &usagestats.UsageStats{}, nil
|
||||
}
|
||||
|
||||
func newAdminUsageRequestTypeTestRouter(repo *adminUsageRepoCapture) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
usageSvc := service.NewUsageService(repo, nil, nil, nil)
|
||||
handler := NewUsageHandler(usageSvc, nil, nil, nil)
|
||||
router := gin.New()
|
||||
router.GET("/admin/usage", handler.List)
|
||||
router.GET("/admin/usage/stats", handler.Stats)
|
||||
return router
|
||||
}
|
||||
|
||||
func TestAdminUsageListRequestTypePriority(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage?request_type=ws_v2&stream=false", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NotNil(t, repo.listFilters.RequestType)
|
||||
require.Equal(t, int16(service.RequestTypeWSV2), *repo.listFilters.RequestType)
|
||||
require.Nil(t, repo.listFilters.Stream)
|
||||
}
|
||||
|
||||
func TestAdminUsageListInvalidRequestType(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage?request_type=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestAdminUsageListInvalidStream(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage?stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestAdminUsageListExactTotalTrue(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage?exact_total=true", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.True(t, repo.listFilters.ExactTotal)
|
||||
}
|
||||
|
||||
func TestAdminUsageListInvalidExactTotal(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage?exact_total=oops", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestAdminUsageStatsRequestTypePriority(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage/stats?request_type=stream&stream=bad", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NotNil(t, repo.statsFilters.RequestType)
|
||||
require.Equal(t, int16(service.RequestTypeStream), *repo.statsFilters.RequestType)
|
||||
require.Nil(t, repo.statsFilters.Stream)
|
||||
}
|
||||
|
||||
func TestAdminUsageStatsInvalidRequestType(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage/stats?request_type=oops", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestAdminUsageStatsInvalidStream(t *testing.T) {
|
||||
repo := &adminUsageRepoCapture{}
|
||||
router := newAdminUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/usage/stats?stream=oops", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
@@ -67,6 +69,8 @@ type BatchUserAttributesResponse struct {
|
||||
Attributes map[int64]map[int64]string `json:"attributes"`
|
||||
}
|
||||
|
||||
var userAttributesBatchCache = newSnapshotCache(30 * time.Second)
|
||||
|
||||
// AttributeDefinitionResponse represents attribute definition response
|
||||
type AttributeDefinitionResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -327,16 +331,32 @@ func (h *UserAttributeHandler) GetBatchUserAttributes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.UserIDs) == 0 {
|
||||
userIDs := normalizeInt64IDList(req.UserIDs)
|
||||
if len(userIDs) == 0 {
|
||||
response.Success(c, BatchUserAttributesResponse{Attributes: map[int64]map[int64]string{}})
|
||||
return
|
||||
}
|
||||
|
||||
attrs, err := h.attrService.GetBatchUserAttributes(c.Request.Context(), req.UserIDs)
|
||||
keyRaw, _ := json.Marshal(struct {
|
||||
UserIDs []int64 `json:"user_ids"`
|
||||
}{
|
||||
UserIDs: userIDs,
|
||||
})
|
||||
cacheKey := string(keyRaw)
|
||||
if cached, ok := userAttributesBatchCache.Get(cacheKey); ok {
|
||||
c.Header("X-Snapshot-Cache", "hit")
|
||||
response.Success(c, cached.Payload)
|
||||
return
|
||||
}
|
||||
|
||||
attrs, err := h.attrService.GetBatchUserAttributes(c.Request.Context(), userIDs)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, BatchUserAttributesResponse{Attributes: attrs})
|
||||
payload := BatchUserAttributesResponse{Attributes: attrs}
|
||||
userAttributesBatchCache.Set(cacheKey, payload)
|
||||
c.Header("X-Snapshot-Cache", "miss")
|
||||
response.Success(c, payload)
|
||||
}
|
||||
|
||||
@@ -34,13 +34,14 @@ func NewUserHandler(adminService service.AdminService, concurrencyService *servi
|
||||
|
||||
// CreateUserRequest represents admin create user request
|
||||
type CreateUserRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Username string `json:"username"`
|
||||
Notes string `json:"notes"`
|
||||
Balance float64 `json:"balance"`
|
||||
Concurrency int `json:"concurrency"`
|
||||
AllowedGroups []int64 `json:"allowed_groups"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Username string `json:"username"`
|
||||
Notes string `json:"notes"`
|
||||
Balance float64 `json:"balance"`
|
||||
Concurrency int `json:"concurrency"`
|
||||
AllowedGroups []int64 `json:"allowed_groups"`
|
||||
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes"`
|
||||
}
|
||||
|
||||
// UpdateUserRequest represents admin update user request
|
||||
@@ -56,7 +57,8 @@ type UpdateUserRequest struct {
|
||||
AllowedGroups *[]int64 `json:"allowed_groups"`
|
||||
// GroupRates 用户专属分组倍率配置
|
||||
// map[groupID]*rate,nil 表示删除该分组的专属倍率
|
||||
GroupRates map[int64]*float64 `json:"group_rates"`
|
||||
GroupRates map[int64]*float64 `json:"group_rates"`
|
||||
SoraStorageQuotaBytes *int64 `json:"sora_storage_quota_bytes"`
|
||||
}
|
||||
|
||||
// UpdateBalanceRequest represents balance update request
|
||||
@@ -89,6 +91,10 @@ func (h *UserHandler) List(c *gin.Context) {
|
||||
Search: search,
|
||||
Attributes: parseAttributeFilters(c),
|
||||
}
|
||||
if raw, ok := c.GetQuery("include_subscriptions"); ok {
|
||||
includeSubscriptions := parseBoolQueryWithDefault(raw, true)
|
||||
filters.IncludeSubscriptions = &includeSubscriptions
|
||||
}
|
||||
|
||||
users, total, err := h.adminService.ListUsers(c.Request.Context(), page, pageSize, filters)
|
||||
if err != nil {
|
||||
@@ -174,13 +180,14 @@ func (h *UserHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
user, err := h.adminService.CreateUser(c.Request.Context(), &service.CreateUserInput{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
Username: req.Username,
|
||||
Notes: req.Notes,
|
||||
Balance: req.Balance,
|
||||
Concurrency: req.Concurrency,
|
||||
AllowedGroups: req.AllowedGroups,
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
Username: req.Username,
|
||||
Notes: req.Notes,
|
||||
Balance: req.Balance,
|
||||
Concurrency: req.Concurrency,
|
||||
AllowedGroups: req.AllowedGroups,
|
||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
@@ -207,15 +214,16 @@ func (h *UserHandler) Update(c *gin.Context) {
|
||||
|
||||
// 使用指针类型直接传递,nil 表示未提供该字段
|
||||
user, err := h.adminService.UpdateUser(c.Request.Context(), userID, &service.UpdateUserInput{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
Username: req.Username,
|
||||
Notes: req.Notes,
|
||||
Balance: req.Balance,
|
||||
Concurrency: req.Concurrency,
|
||||
Status: req.Status,
|
||||
AllowedGroups: req.AllowedGroups,
|
||||
GroupRates: req.GroupRates,
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
Username: req.Username,
|
||||
Notes: req.Notes,
|
||||
Balance: req.Balance,
|
||||
Concurrency: req.Concurrency,
|
||||
Status: req.Status,
|
||||
AllowedGroups: req.AllowedGroups,
|
||||
GroupRates: req.GroupRates,
|
||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
|
||||
@@ -4,6 +4,7 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
@@ -36,6 +37,11 @@ type CreateAPIKeyRequest struct {
|
||||
IPBlacklist []string `json:"ip_blacklist"` // IP 黑名单
|
||||
Quota *float64 `json:"quota"` // 配额限制 (USD)
|
||||
ExpiresInDays *int `json:"expires_in_days"` // 过期天数
|
||||
|
||||
// Rate limit fields (0 = unlimited)
|
||||
RateLimit5h *float64 `json:"rate_limit_5h"`
|
||||
RateLimit1d *float64 `json:"rate_limit_1d"`
|
||||
RateLimit7d *float64 `json:"rate_limit_7d"`
|
||||
}
|
||||
|
||||
// UpdateAPIKeyRequest represents the update API key request payload
|
||||
@@ -48,6 +54,12 @@ type UpdateAPIKeyRequest struct {
|
||||
Quota *float64 `json:"quota"` // 配额限制 (USD), 0=无限制
|
||||
ExpiresAt *string `json:"expires_at"` // 过期时间 (ISO 8601)
|
||||
ResetQuota *bool `json:"reset_quota"` // 重置已用配额
|
||||
|
||||
// Rate limit fields (nil = no change, 0 = unlimited)
|
||||
RateLimit5h *float64 `json:"rate_limit_5h"`
|
||||
RateLimit1d *float64 `json:"rate_limit_1d"`
|
||||
RateLimit7d *float64 `json:"rate_limit_7d"`
|
||||
ResetRateLimitUsage *bool `json:"reset_rate_limit_usage"` // 重置限速用量
|
||||
}
|
||||
|
||||
// List handles listing user's API keys with pagination
|
||||
@@ -62,7 +74,23 @@ func (h *APIKeyHandler) List(c *gin.Context) {
|
||||
page, pageSize := response.ParsePagination(c)
|
||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
||||
|
||||
keys, result, err := h.apiKeyService.List(c.Request.Context(), subject.UserID, params)
|
||||
// Parse filter parameters
|
||||
var filters service.APIKeyListFilters
|
||||
if search := strings.TrimSpace(c.Query("search")); search != "" {
|
||||
if len(search) > 100 {
|
||||
search = search[:100]
|
||||
}
|
||||
filters.Search = search
|
||||
}
|
||||
filters.Status = c.Query("status")
|
||||
if groupIDStr := c.Query("group_id"); groupIDStr != "" {
|
||||
gid, err := strconv.ParseInt(groupIDStr, 10, 64)
|
||||
if err == nil {
|
||||
filters.GroupID = &gid
|
||||
}
|
||||
}
|
||||
|
||||
keys, result, err := h.apiKeyService.List(c.Request.Context(), subject.UserID, params, filters)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
@@ -131,6 +159,15 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
|
||||
if req.Quota != nil {
|
||||
svcReq.Quota = *req.Quota
|
||||
}
|
||||
if req.RateLimit5h != nil {
|
||||
svcReq.RateLimit5h = *req.RateLimit5h
|
||||
}
|
||||
if req.RateLimit1d != nil {
|
||||
svcReq.RateLimit1d = *req.RateLimit1d
|
||||
}
|
||||
if req.RateLimit7d != nil {
|
||||
svcReq.RateLimit7d = *req.RateLimit7d
|
||||
}
|
||||
|
||||
executeUserIdempotentJSON(c, "user.api_keys.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
||||
key, err := h.apiKeyService.Create(ctx, subject.UserID, svcReq)
|
||||
@@ -163,10 +200,14 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
svcReq := service.UpdateAPIKeyRequest{
|
||||
IPWhitelist: req.IPWhitelist,
|
||||
IPBlacklist: req.IPBlacklist,
|
||||
Quota: req.Quota,
|
||||
ResetQuota: req.ResetQuota,
|
||||
IPWhitelist: req.IPWhitelist,
|
||||
IPBlacklist: req.IPBlacklist,
|
||||
Quota: req.Quota,
|
||||
ResetQuota: req.ResetQuota,
|
||||
RateLimit5h: req.RateLimit5h,
|
||||
RateLimit1d: req.RateLimit1d,
|
||||
RateLimit7d: req.RateLimit7d,
|
||||
ResetRateLimitUsage: req.ResetRateLimitUsage,
|
||||
}
|
||||
if req.Name != "" {
|
||||
svcReq.Name = &req.Name
|
||||
|
||||
@@ -113,9 +113,8 @@ func (h *AuthHandler) Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Turnstile 验证 — 始终执行,防止绕过
|
||||
// TODO: 确认前端在提交邮箱验证码注册时也传递了 turnstile_token
|
||||
if err := h.authService.VerifyTurnstile(c.Request.Context(), req.TurnstileToken, ip.GetClientIP(c)); err != nil {
|
||||
// Turnstile 验证(邮箱验证码注册场景避免重复校验一次性 token)
|
||||
if err := h.authService.VerifyTurnstileForRegister(c.Request.Context(), req.TurnstileToken, ip.GetClientIP(c), req.VerifyCode); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user