mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-05 16:00:21 +08:00
Compare commits
472 Commits
feat/api-k
...
v0.1.66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b1d63a786 | ||
|
|
e204b4d81f | ||
|
|
325ed747d8 | ||
|
|
cbf3dba28d | ||
|
|
4329f72abf | ||
|
|
ad1cdba338 | ||
|
|
016c3915d7 | ||
|
|
79fa18132b | ||
|
|
673caf41a0 | ||
|
|
c441638fc0 | ||
|
|
ae18397ca6 | ||
|
|
426ce616c0 | ||
|
|
5cda979209 | ||
|
|
cc7e67b01a | ||
|
|
6999a9c011 | ||
|
|
bbdc8663d3 | ||
|
|
4bfeeecb05 | ||
|
|
bbc7b4aeed | ||
|
|
d3062b2e46 | ||
|
|
b7777fb46c | ||
|
|
35f39ca291 | ||
|
|
f2e206700c | ||
|
|
9bee0a2071 | ||
|
|
b7f69844e1 | ||
|
|
c3d1891ccd | ||
|
|
4d8f2db924 | ||
|
|
6599b366dc | ||
|
|
ba16ace697 | ||
|
|
cadca752c4 | ||
|
|
edf215e6fd | ||
|
|
e12dd079fd | ||
|
|
04a509d45e | ||
|
|
269a659200 | ||
|
|
2c31bf46b5 | ||
|
|
8f6639f825 | ||
|
|
fc17d9d7df | ||
|
|
ab092e88a8 | ||
|
|
56a1e29cdd | ||
|
|
0059a232a6 | ||
|
|
45676fdc8d | ||
|
|
e32c5f534f | ||
|
|
426d691c95 | ||
|
|
e9a4c8ab97 | ||
|
|
a55cfebd09 | ||
|
|
34cc02f8c7 | ||
|
|
624d9fddb7 | ||
|
|
47fbe43324 | ||
|
|
1245f07a2d | ||
|
|
839975b0cf | ||
|
|
8c1233393f | ||
|
|
9cdb0568cc | ||
|
|
74e05b83ea | ||
|
|
4ded9e7d49 | ||
|
|
716272a1e2 | ||
|
|
9cc8352593 | ||
|
|
43a1031e38 | ||
|
|
a5547b2f30 | ||
|
|
b0aa23540b | ||
|
|
ffaa6c4a17 | ||
|
|
fbf72f0ec4 | ||
|
|
909b8a8f9c | ||
|
|
4a0fe3b143 | ||
|
|
a1292fac81 | ||
|
|
7f98be4f91 | ||
|
|
fd73b8875d | ||
|
|
f9ab1daa3c | ||
|
|
d27b847442 | ||
|
|
dac6bc2228 | ||
|
|
4bd3dbf2ce | ||
|
|
226df1c23a | ||
|
|
2665230a09 | ||
|
|
4f0c2b794c | ||
|
|
e756064c19 | ||
|
|
17dfb0af01 | ||
|
|
ff74f517df | ||
|
|
477a9a180f | ||
|
|
da48df06d2 | ||
|
|
39fad63ccf | ||
|
|
5602d02b1b | ||
|
|
81989eed1c | ||
|
|
192efb84a0 | ||
|
|
8672347f93 | ||
|
|
5e5d4a513b | ||
|
|
88b6358472 | ||
|
|
dd8d5e2c42 | ||
|
|
d91e2328fb | ||
|
|
2a16735495 | ||
|
|
292f25f9ca | ||
|
|
c92e37775a | ||
|
|
f6ed3d1456 | ||
|
|
84686753e8 | ||
|
|
91f01309da | ||
|
|
57a1fc9d33 | ||
|
|
c95a864975 | ||
|
|
7a83db6180 | ||
|
|
a8513da7ff | ||
|
|
53534d3956 | ||
|
|
cc07a0e295 | ||
|
|
e7bc62500b | ||
|
|
c8fb9ef3a5 | ||
|
|
eb5e6214bc | ||
|
|
568d6ee10e | ||
|
|
6aef1af76e | ||
|
|
a54852e129 | ||
|
|
668118def1 | ||
|
|
73e6b160f8 | ||
|
|
6fec141de6 | ||
|
|
31cde6c555 | ||
|
|
b1a980f344 | ||
|
|
00d9fbd220 | ||
|
|
4f4c9679bf | ||
|
|
3dab71729d | ||
|
|
2f6f758670 | ||
|
|
090c8981dd | ||
|
|
fbb572948d | ||
|
|
a652b513d3 | ||
|
|
ccfeaeb22d | ||
|
|
4c12799a95 | ||
|
|
0f8d42c577 | ||
|
|
03c7578713 | ||
|
|
de6797c560 | ||
|
|
46ae08ecb7 | ||
|
|
2028cc29b7 | ||
|
|
f6360e0bf3 | ||
|
|
9abda1bc59 | ||
|
|
2a94cc76a6 | ||
|
|
150b315a7b | ||
|
|
a07174c191 | ||
|
|
fb839ae6ca | ||
|
|
bdc426a774 | ||
|
|
32fff3798c | ||
|
|
2b02c6635d | ||
|
|
771baa66ee | ||
|
|
a82029b0cf | ||
|
|
0c2a901af4 | ||
|
|
bd18f4b8ef | ||
|
|
bf7b79f2f0 | ||
|
|
45e8598d32 | ||
|
|
8391d480c9 | ||
|
|
d17f853a5f | ||
|
|
ef5a41057f | ||
|
|
c115c9e048 | ||
|
|
6941315432 | ||
|
|
8b071cc665 | ||
|
|
959f6c538a | ||
|
|
217b3b59c0 | ||
|
|
ec916a3197 | ||
|
|
22eb72e0f9 | ||
|
|
07ba64c666 | ||
|
|
f22bc59fe3 | ||
|
|
0ce8666cc0 | ||
|
|
5427a9e422 | ||
|
|
5e9f5efbe3 | ||
|
|
a7a0017aa8 | ||
|
|
9078b17a41 | ||
|
|
14a3694a9a | ||
|
|
b9b4db3df5 | ||
|
|
bc1d7edc58 | ||
|
|
5a6f60a954 | ||
|
|
a61cc2cb24 | ||
|
|
31933c8a60 | ||
|
|
78bccd032d | ||
|
|
ae21db77ec | ||
|
|
ac7503d95f | ||
|
|
69c4b17a9b | ||
|
|
a7165b0f73 | ||
|
|
cc0fca35ec | ||
|
|
dae0d5321f | ||
|
|
34415db7ed | ||
|
|
28e46e0e7c | ||
|
|
7379423325 | ||
|
|
74a3c74514 | ||
|
|
3d6d131889 | ||
|
|
b0569d873a | ||
|
|
d9433699db | ||
|
|
92234857f7 | ||
|
|
8efa361728 | ||
|
|
1be3eacad5 | ||
|
|
34d6b0a601 | ||
|
|
eb432a49ed | ||
|
|
04811c00cb | ||
|
|
06093d4f79 | ||
|
|
2055a60bcb | ||
|
|
cc892744bc | ||
|
|
577ee16108 | ||
|
|
392a8ac7ea | ||
|
|
226920064b | ||
|
|
19865b865f | ||
|
|
e3f812c2fe | ||
|
|
c9f79dee66 | ||
|
|
c659788022 | ||
|
|
aeb987ceb1 | ||
|
|
b478982484 | ||
|
|
fe71ee57b3 | ||
|
|
fba3d21a35 | ||
|
|
455576300c | ||
|
|
821968903c | ||
|
|
452fa53c0d | ||
|
|
95fe1e818f | ||
|
|
a61042bca0 | ||
|
|
b4abfae4de | ||
|
|
c02c8646a6 | ||
|
|
3ff2ca8d41 | ||
|
|
415840088e | ||
|
|
c4f6c89b65 | ||
|
|
539b41f421 | ||
|
|
b2ff326ced | ||
|
|
8b95d16220 | ||
|
|
a478822b8e | ||
|
|
23aa69f56f | ||
|
|
b36f3db9de | ||
|
|
e93f086485 | ||
|
|
930e9ee55c | ||
|
|
38961ba10e | ||
|
|
93b5b7474b | ||
|
|
f862ddc9ff | ||
|
|
b59032304c | ||
|
|
3ba4d535e3 | ||
|
|
5b37e9aea4 | ||
|
|
1820389a05 | ||
|
|
35e3a89385 | ||
|
|
5f890e85e7 | ||
|
|
10bc7f7042 | ||
|
|
a65fd9dee8 | ||
|
|
1bb4c76deb | ||
|
|
aab44f9fc8 | ||
|
|
0a848e7578 | ||
|
|
90bce60b85 | ||
|
|
c22d51ee41 | ||
|
|
a458e684bc | ||
|
|
87b4662993 | ||
|
|
3a100339b9 | ||
|
|
47eb3c8888 | ||
|
|
4672a6fac3 | ||
|
|
82743704e4 | ||
|
|
cc2d064ab4 | ||
|
|
27214f8657 | ||
|
|
28de614dfb | ||
|
|
850183c269 | ||
|
|
2a5ef6d3f5 | ||
|
|
1d231c6cc3 | ||
|
|
20c71acb3b | ||
|
|
52ad7c6e9c | ||
|
|
5aaaffe4d1 | ||
|
|
5354ba3662 | ||
|
|
2daf13c4c8 | ||
|
|
16a90f3d3a | ||
|
|
8a0ff15242 | ||
|
|
8c993dfd35 | ||
|
|
2a6fb1e456 | ||
|
|
9e6cd36af4 | ||
|
|
f25f992a30 | ||
|
|
841d7ef2f2 | ||
|
|
a7a49be850 | ||
|
|
d5eab7da3b | ||
|
|
9b10241561 | ||
|
|
76448ab555 | ||
|
|
9584af5cb4 | ||
|
|
6fabddcb0b | ||
|
|
5efeabb0c6 | ||
|
|
806f402bba | ||
|
|
5432087d96 | ||
|
|
02cb14c7b8 | ||
|
|
9bdb45be7c | ||
|
|
514c0562e0 | ||
|
|
371275ec34 | ||
|
|
ec24a3c361 | ||
|
|
d89e797bfc | ||
|
|
55e469c7fe | ||
|
|
fb99ceacc7 | ||
|
|
daf10907e4 | ||
|
|
b3b2868f55 | ||
|
|
25b00abca1 | ||
|
|
8d0767352b | ||
|
|
918a253851 | ||
|
|
63711067e6 | ||
|
|
7158b38897 | ||
|
|
7f317b9093 | ||
|
|
7c4309ea24 | ||
|
|
5013290486 | ||
|
|
8cf3e9a620 | ||
|
|
060699c3b8 | ||
|
|
2ca6c631ac | ||
|
|
967e25878f | ||
|
|
182683814b | ||
|
|
99cbfa1567 | ||
|
|
3f8c8d70ad | ||
|
|
9c567fad92 | ||
|
|
33f58d583d | ||
|
|
0abb3a6843 | ||
|
|
3663951d11 | ||
|
|
1e169685f4 | ||
|
|
f38a3e7585 | ||
|
|
b8da5d45ce | ||
|
|
659df6e220 | ||
|
|
d601768016 | ||
|
|
16ddc6a83b | ||
|
|
340dc9cadb | ||
|
|
55fced3942 | ||
|
|
7bbf49fd65 | ||
|
|
eea6c2d02c | ||
|
|
70eaa450db | ||
|
|
55796a118d | ||
|
|
9a22d1a690 | ||
|
|
c9d21d53e6 | ||
|
|
e1015c2759 | ||
|
|
d7fa47d732 | ||
|
|
3d6e01a58f | ||
|
|
f9713e8733 | ||
|
|
0e44829720 | ||
|
|
93db889a10 | ||
|
|
0df7385c4e | ||
|
|
1a3fa6411c | ||
|
|
64614756d1 | ||
|
|
bb1fd54d4d | ||
|
|
d85288a6c0 | ||
|
|
3402acb606 | ||
|
|
7fdc25df3c | ||
|
|
ea699cbdc2 | ||
|
|
fe6a3f4267 | ||
|
|
fe8198c8cd | ||
|
|
675e61385f | ||
|
|
67acac1082 | ||
|
|
d02e1db018 | ||
|
|
0da515071b | ||
|
|
524d80ae1c | ||
|
|
3b71bc3df1 | ||
|
|
22ef9534e0 | ||
|
|
c206d12d5c | ||
|
|
6ad29a470c | ||
|
|
2d45e61a9b | ||
|
|
b98fb013ae | ||
|
|
345a965fa3 | ||
|
|
c02c120579 | ||
|
|
f0ece82111 | ||
|
|
4da681f58a | ||
|
|
68ba866c38 | ||
|
|
9622347faa | ||
|
|
8363663ea8 | ||
|
|
b588ea194c | ||
|
|
465ba76788 | ||
|
|
cf313d5761 | ||
|
|
9618cb5643 | ||
|
|
8c1958c9ad | ||
|
|
2db34139f0 | ||
|
|
9c02ab789d | ||
|
|
e0cccf6ed2 | ||
|
|
89c1a41305 | ||
|
|
202ec21bab | ||
|
|
6dcb27632e | ||
|
|
3141aa5144 | ||
|
|
5443efd7d7 | ||
|
|
62771583e7 | ||
|
|
5526f122b7 | ||
|
|
9c144587fe | ||
|
|
098bf5a1e8 | ||
|
|
4c37ca71ee | ||
|
|
0c52809591 | ||
|
|
53e730f8d5 | ||
|
|
8e248e0853 | ||
|
|
2a0758bdfe | ||
|
|
f55ba3f6c1 | ||
|
|
db51e65b42 | ||
|
|
72a2ed958b | ||
|
|
d0b91a40d4 | ||
|
|
bd74bf7994 | ||
|
|
f28d4b78e7 | ||
|
|
7536dbfee5 | ||
|
|
b76cc583fb | ||
|
|
955af6b3ec | ||
|
|
1073317a3e | ||
|
|
839ab37d40 | ||
|
|
9dd0ef187d | ||
|
|
fd8473f267 | ||
|
|
cc4910dd30 | ||
|
|
50de5d05b0 | ||
|
|
7844dc4f2d | ||
|
|
c48795a948 | ||
|
|
19b67e89a2 | ||
|
|
f017fd97c1 | ||
|
|
ce3336e3f4 | ||
|
|
54c5788b86 | ||
|
|
4cb7b26f03 | ||
|
|
3dfb62e996 | ||
|
|
d5c711d081 | ||
|
|
73b62bb15c | ||
|
|
18b8bd43ad | ||
|
|
8fffcd8091 | ||
|
|
c8e3a476fc | ||
|
|
808cee9665 | ||
|
|
92eafbc2a6 | ||
|
|
2548800c3f | ||
|
|
9dce8a5388 | ||
|
|
76484bd5c9 | ||
|
|
e4ed35fe01 | ||
|
|
f5e45c1a8a | ||
|
|
a2f83ff032 | ||
|
|
2b2f7a6dec | ||
|
|
49c15c0d44 | ||
|
|
1b938b2003 | ||
|
|
5f80760a8c | ||
|
|
dd59e872ff | ||
|
|
aa1a3b9a74 | ||
|
|
32953405b1 | ||
|
|
c1a3dd41dd | ||
|
|
63dc6a68df | ||
|
|
a39316e004 | ||
|
|
988b4d0254 | ||
|
|
f541636840 | ||
|
|
48613558d4 | ||
|
|
4b66ee2f8f | ||
|
|
abbde130ab | ||
|
|
ccb8144557 | ||
|
|
1240c78ef6 | ||
|
|
66c8b6f2bc | ||
|
|
6271a33d08 | ||
|
|
5364011a5b | ||
|
|
d78f42d2fd | ||
|
|
1a869547d7 | ||
|
|
e4bc9f6fb0 | ||
|
|
e5857161ff | ||
|
|
abdc4f39cb | ||
|
|
7ebca553ef | ||
|
|
c2962752eb | ||
|
|
ab5839b461 | ||
|
|
89a725a433 | ||
|
|
645609d441 | ||
|
|
fc4ea65936 | ||
|
|
d75cd820b0 | ||
|
|
cb3e08dda4 | ||
|
|
44a93c1922 | ||
|
|
9cba595fd0 | ||
|
|
56fc2764e4 | ||
|
|
0c4f1762c9 | ||
|
|
c2c865b0cb | ||
|
|
a66d318820 | ||
|
|
a16f72f52e | ||
|
|
99e2391b2a | ||
|
|
80c1cdf024 | ||
|
|
9d0a4f3d68 | ||
|
|
1a641392d9 | ||
|
|
36b817d008 | ||
|
|
24d19a5f78 | ||
|
|
13ae0ce7b0 | ||
|
|
3a67002cfe | ||
|
|
eb06006d6c | ||
|
|
c48dc097ff | ||
|
|
585257d340 | ||
|
|
11bfc807d7 | ||
|
|
8ae75e7f6e | ||
|
|
fc32b57798 | ||
|
|
337a188660 | ||
|
|
11d063e3c4 | ||
|
|
e846458009 | ||
|
|
2d123a11ad | ||
|
|
c2a6ca8d3a | ||
|
|
fcdf839b6b | ||
|
|
d55dd56fd2 | ||
|
|
e0d12b46d8 | ||
|
|
f3ed95d4de | ||
|
|
5baa8b5673 | ||
|
|
bb5303272b | ||
|
|
d55866d375 | ||
|
|
4b9e47cec9 | ||
|
|
7b1cf2c495 | ||
|
|
7a06c4873e | ||
|
|
da1f3d61be | ||
|
|
dc3cd62125 | ||
|
|
bc404d4fc1 | ||
|
|
a4a0c0e2cc | ||
|
|
c7abfe67b5 | ||
|
|
4e3476a669 |
4
.github/workflows/backend-ci.yml
vendored
4
.github/workflows/backend-ci.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
- name: Verify Go version
|
- name: Verify Go version
|
||||||
run: |
|
run: |
|
||||||
go version | grep -q 'go1.25.5'
|
go version | grep -q 'go1.25.6'
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
run: make test-unit
|
run: make test-unit
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
- name: Verify Go version
|
- name: Verify Go version
|
||||||
run: |
|
run: |
|
||||||
go version | grep -q 'go1.25.5'
|
go version | grep -q 'go1.25.6'
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v9
|
uses: golangci/golangci-lint-action@v9
|
||||||
with:
|
with:
|
||||||
|
|||||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -115,7 +115,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Verify Go version
|
- name: Verify Go version
|
||||||
run: |
|
run: |
|
||||||
go version | grep -q 'go1.25.5'
|
go version | grep -q 'go1.25.6'
|
||||||
|
|
||||||
# Docker setup for GoReleaser
|
# Docker setup for GoReleaser
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
@@ -222,8 +222,9 @@ jobs:
|
|||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase
|
GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase
|
||||||
|
|
||||||
# 获取 tag message 内容
|
# 获取 tag message 内容并转义 Markdown 特殊字符
|
||||||
TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
|
TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
|
||||||
|
TAG_MESSAGE=$(echo "$TAG_MESSAGE" | sed 's/\([_*`\[]\)/\\\1/g')
|
||||||
|
|
||||||
# 限制消息长度(Telegram 消息限制 4096 字符,预留空间给头尾固定内容)
|
# 限制消息长度(Telegram 消息限制 4096 字符,预留空间给头尾固定内容)
|
||||||
if [ ${#TAG_MESSAGE} -gt 3500 ]; then
|
if [ ${#TAG_MESSAGE} -gt 3500 ]; then
|
||||||
|
|||||||
2
.github/workflows/security-scan.yml
vendored
2
.github/workflows/security-scan.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
cache-dependency-path: backend/go.sum
|
cache-dependency-path: backend/go.sum
|
||||||
- name: Verify Go version
|
- name: Verify Go version
|
||||||
run: |
|
run: |
|
||||||
go version | grep -q 'go1.25.5'
|
go version | grep -q 'go1.25.6'
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -83,6 +83,8 @@ temp/
|
|||||||
*.log
|
*.log
|
||||||
*.bak
|
*.bak
|
||||||
.cache/
|
.cache/
|
||||||
|
.dev/
|
||||||
|
.serena/
|
||||||
|
|
||||||
# ===================
|
# ===================
|
||||||
# 构建产物
|
# 构建产物
|
||||||
@@ -126,6 +128,5 @@ backend/cmd/server/server
|
|||||||
deploy/docker-compose.override.yml
|
deploy/docker-compose.override.yml
|
||||||
.gocache/
|
.gocache/
|
||||||
vite.config.js
|
vite.config.js
|
||||||
!docs/
|
|
||||||
docs/*
|
docs/*
|
||||||
!docs/dependency-security.md
|
.serena/
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
ARG NODE_IMAGE=node:24-alpine
|
ARG NODE_IMAGE=node:24-alpine
|
||||||
ARG GOLANG_IMAGE=golang:1.25.5-alpine
|
ARG GOLANG_IMAGE=golang:1.25.6-alpine
|
||||||
ARG ALPINE_IMAGE=alpine:3.20
|
ARG ALPINE_IMAGE=alpine:3.20
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
ARG GOPROXY=https://goproxy.cn,direct
|
||||||
ARG GOSUMDB=sum.golang.google.cn
|
ARG GOSUMDB=sum.golang.google.cn
|
||||||
|
|||||||
164
PR_DESCRIPTION.md
Normal file
164
PR_DESCRIPTION.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
## 概述
|
||||||
|
|
||||||
|
全面增强运维监控系统(Ops)的错误日志管理和告警静默功能,优化前端 UI 组件代码质量和用户体验。本次更新重构了核心服务层和数据访问层,提升系统可维护性和运维效率。
|
||||||
|
|
||||||
|
## 主要改动
|
||||||
|
|
||||||
|
### 1. 错误日志查询优化
|
||||||
|
|
||||||
|
**功能特性:**
|
||||||
|
- 新增 GetErrorLogByID 接口,支持按 ID 精确查询错误详情
|
||||||
|
- 优化错误日志过滤逻辑,支持多维度筛选(平台、阶段、来源、所有者等)
|
||||||
|
- 改进查询参数处理,简化代码结构
|
||||||
|
- 增强错误分类和标准化处理
|
||||||
|
- 支持错误解决状态追踪(resolved 字段)
|
||||||
|
|
||||||
|
**技术实现:**
|
||||||
|
- `ops_handler.go` - 新增单条错误日志查询接口
|
||||||
|
- `ops_repo.go` - 优化数据查询和过滤条件构建
|
||||||
|
- `ops_models.go` - 扩展错误日志数据模型
|
||||||
|
- 前端 API 接口同步更新
|
||||||
|
|
||||||
|
### 2. 告警静默功能
|
||||||
|
|
||||||
|
**功能特性:**
|
||||||
|
- 支持按规则、平台、分组、区域等维度静默告警
|
||||||
|
- 可设置静默时长和原因说明
|
||||||
|
- 静默记录可追溯,记录创建人和创建时间
|
||||||
|
- 自动过期机制,避免永久静默
|
||||||
|
|
||||||
|
**技术实现:**
|
||||||
|
- `037_ops_alert_silences.sql` - 新增告警静默表
|
||||||
|
- `ops_alerts.go` - 告警静默逻辑实现
|
||||||
|
- `ops_alerts_handler.go` - 告警静默 API 接口
|
||||||
|
- `OpsAlertEventsCard.vue` - 前端告警静默操作界面
|
||||||
|
|
||||||
|
**数据库结构:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| rule_id | BIGINT | 告警规则 ID |
|
||||||
|
| platform | VARCHAR(64) | 平台标识 |
|
||||||
|
| group_id | BIGINT | 分组 ID(可选) |
|
||||||
|
| region | VARCHAR(64) | 区域(可选) |
|
||||||
|
| until | TIMESTAMPTZ | 静默截止时间 |
|
||||||
|
| reason | TEXT | 静默原因 |
|
||||||
|
| created_by | BIGINT | 创建人 ID |
|
||||||
|
|
||||||
|
### 3. 错误分类标准化
|
||||||
|
|
||||||
|
**功能特性:**
|
||||||
|
- 统一错误阶段分类(request|auth|routing|upstream|network|internal)
|
||||||
|
- 规范错误归属分类(client|provider|platform)
|
||||||
|
- 标准化错误来源分类(client_request|upstream_http|gateway)
|
||||||
|
- 自动迁移历史数据到新分类体系
|
||||||
|
|
||||||
|
**技术实现:**
|
||||||
|
- `038_ops_errors_resolution_retry_results_and_standardize_classification.sql` - 分类标准化迁移
|
||||||
|
- 自动映射历史遗留分类到新标准
|
||||||
|
- 自动解决已恢复的上游错误(客户端状态码 < 400)
|
||||||
|
|
||||||
|
### 4. Gateway 服务集成
|
||||||
|
|
||||||
|
**功能特性:**
|
||||||
|
- 完善各 Gateway 服务的 Ops 集成
|
||||||
|
- 统一错误日志记录接口
|
||||||
|
- 增强上游错误追踪能力
|
||||||
|
|
||||||
|
**涉及服务:**
|
||||||
|
- `antigravity_gateway_service.go` - Antigravity 网关集成
|
||||||
|
- `gateway_service.go` - 通用网关集成
|
||||||
|
- `gemini_messages_compat_service.go` - Gemini 兼容层集成
|
||||||
|
- `openai_gateway_service.go` - OpenAI 网关集成
|
||||||
|
|
||||||
|
### 5. 前端 UI 优化
|
||||||
|
|
||||||
|
**代码重构:**
|
||||||
|
- 大幅简化错误详情模态框代码(从 828 行优化到 450 行)
|
||||||
|
- 优化错误日志表格组件,提升可读性
|
||||||
|
- 清理未使用的 i18n 翻译,减少冗余
|
||||||
|
- 统一组件代码风格和格式
|
||||||
|
- 优化骨架屏组件,更好匹配实际看板布局
|
||||||
|
|
||||||
|
**布局改进:**
|
||||||
|
- 修复模态框内容溢出和滚动问题
|
||||||
|
- 优化表格布局,使用 flex 布局确保正确显示
|
||||||
|
- 改进看板头部布局和交互
|
||||||
|
- 提升响应式体验
|
||||||
|
- 骨架屏支持全屏模式适配
|
||||||
|
|
||||||
|
**交互优化:**
|
||||||
|
- 优化告警事件卡片功能和展示
|
||||||
|
- 改进错误详情展示逻辑
|
||||||
|
- 增强请求详情模态框
|
||||||
|
- 完善运行时设置卡片
|
||||||
|
- 改进加载动画效果
|
||||||
|
|
||||||
|
### 6. 国际化完善
|
||||||
|
|
||||||
|
**文案补充:**
|
||||||
|
- 补充错误日志相关的英文翻译
|
||||||
|
- 添加告警静默功能的中英文文案
|
||||||
|
- 完善提示文本和错误信息
|
||||||
|
- 统一术语翻译标准
|
||||||
|
|
||||||
|
## 文件变更
|
||||||
|
|
||||||
|
**后端(26 个文件):**
|
||||||
|
- `backend/internal/handler/admin/ops_alerts_handler.go` - 告警接口增强
|
||||||
|
- `backend/internal/handler/admin/ops_handler.go` - 错误日志接口优化
|
||||||
|
- `backend/internal/handler/ops_error_logger.go` - 错误记录器增强
|
||||||
|
- `backend/internal/repository/ops_repo.go` - 数据访问层重构
|
||||||
|
- `backend/internal/repository/ops_repo_alerts.go` - 告警数据访问增强
|
||||||
|
- `backend/internal/service/ops_*.go` - 核心服务层重构(10 个文件)
|
||||||
|
- `backend/internal/service/*_gateway_service.go` - Gateway 集成(4 个文件)
|
||||||
|
- `backend/internal/server/routes/admin.go` - 路由配置更新
|
||||||
|
- `backend/migrations/*.sql` - 数据库迁移(2 个文件)
|
||||||
|
- 测试文件更新(5 个文件)
|
||||||
|
|
||||||
|
**前端(13 个文件):**
|
||||||
|
- `frontend/src/views/admin/ops/OpsDashboard.vue` - 看板主页优化
|
||||||
|
- `frontend/src/views/admin/ops/components/*.vue` - 组件重构(10 个文件)
|
||||||
|
- `frontend/src/api/admin/ops.ts` - API 接口扩展
|
||||||
|
- `frontend/src/i18n/locales/*.ts` - 国际化文本(2 个文件)
|
||||||
|
|
||||||
|
## 代码统计
|
||||||
|
|
||||||
|
- 44 个文件修改
|
||||||
|
- 3733 行新增
|
||||||
|
- 995 行删除
|
||||||
|
- 净增加 2738 行
|
||||||
|
|
||||||
|
## 核心改进
|
||||||
|
|
||||||
|
**可维护性提升:**
|
||||||
|
- 重构核心服务层,职责更清晰
|
||||||
|
- 简化前端组件代码,降低复杂度
|
||||||
|
- 统一代码风格和命名规范
|
||||||
|
- 清理冗余代码和未使用的翻译
|
||||||
|
- 标准化错误分类体系
|
||||||
|
|
||||||
|
**功能完善:**
|
||||||
|
- 告警静默功能,减少告警噪音
|
||||||
|
- 错误日志查询优化,提升运维效率
|
||||||
|
- Gateway 服务集成完善,统一监控能力
|
||||||
|
- 错误解决状态追踪,便于问题管理
|
||||||
|
|
||||||
|
**用户体验优化:**
|
||||||
|
- 修复多个 UI 布局问题
|
||||||
|
- 优化交互流程
|
||||||
|
- 完善国际化支持
|
||||||
|
- 提升响应式体验
|
||||||
|
- 改进加载状态展示
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
- ✅ 错误日志查询和过滤功能
|
||||||
|
- ✅ 告警静默创建和自动过期
|
||||||
|
- ✅ 错误分类标准化迁移
|
||||||
|
- ✅ Gateway 服务错误日志记录
|
||||||
|
- ✅ 前端组件布局和交互
|
||||||
|
- ✅ 骨架屏全屏模式适配
|
||||||
|
- ✅ 国际化文本完整性
|
||||||
|
- ✅ API 接口功能正确性
|
||||||
|
- ✅ 数据库迁移执行成功
|
||||||
130
README.md
130
README.md
@@ -18,7 +18,7 @@ English | [中文](README_CN.md)
|
|||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
Try Sub2API online: **https://v2.pincc.ai/**
|
Try Sub2API online: **https://demo.sub2api.org/**
|
||||||
|
|
||||||
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
|
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Method 2: Docker Compose
|
### Method 2: Docker Compose (Recommended)
|
||||||
|
|
||||||
Deploy with Docker Compose, including PostgreSQL and Redis containers.
|
Deploy with Docker Compose, including PostgreSQL and Redis containers.
|
||||||
|
|
||||||
@@ -137,87 +137,157 @@ Deploy with Docker Compose, including PostgreSQL and Redis containers.
|
|||||||
- Docker 20.10+
|
- Docker 20.10+
|
||||||
- Docker Compose v2+
|
- Docker Compose v2+
|
||||||
|
|
||||||
#### Installation Steps
|
#### Quick Start (One-Click Deployment)
|
||||||
|
|
||||||
|
Use the automated deployment script for easy setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create deployment directory
|
||||||
|
mkdir -p sub2api-deploy && cd sub2api-deploy
|
||||||
|
|
||||||
|
# Download and run deployment preparation script
|
||||||
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose -f docker-compose.local.yml logs -f sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
**What the script does:**
|
||||||
|
- Downloads `docker-compose.local.yml` and `.env.example`
|
||||||
|
- Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
|
||||||
|
- Creates `.env` file with auto-generated secrets
|
||||||
|
- Creates data directories (uses local directories for easy backup/migration)
|
||||||
|
- Displays generated credentials for your reference
|
||||||
|
|
||||||
|
#### Manual Deployment
|
||||||
|
|
||||||
|
If you prefer manual setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clone the repository
|
# 1. Clone the repository
|
||||||
git clone https://github.com/Wei-Shaw/sub2api.git
|
git clone https://github.com/Wei-Shaw/sub2api.git
|
||||||
cd sub2api
|
cd sub2api/deploy
|
||||||
|
|
||||||
# 2. Enter the deploy directory
|
# 2. Copy environment configuration
|
||||||
cd deploy
|
|
||||||
|
|
||||||
# 3. Copy environment configuration
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
# 4. Edit configuration (set your passwords)
|
# 3. Edit configuration (generate secure passwords)
|
||||||
nano .env
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
**Required configuration in `.env`:**
|
**Required configuration in `.env`:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# PostgreSQL password (REQUIRED - change this!)
|
# PostgreSQL password (REQUIRED)
|
||||||
POSTGRES_PASSWORD=your_secure_password_here
|
POSTGRES_PASSWORD=your_secure_password_here
|
||||||
|
|
||||||
|
# JWT Secret (RECOMMENDED - keeps users logged in after restart)
|
||||||
|
JWT_SECRET=your_jwt_secret_here
|
||||||
|
|
||||||
|
# TOTP Encryption Key (RECOMMENDED - preserves 2FA after restart)
|
||||||
|
TOTP_ENCRYPTION_KEY=your_totp_key_here
|
||||||
|
|
||||||
# Optional: Admin account
|
# Optional: Admin account
|
||||||
ADMIN_EMAIL=admin@example.com
|
ADMIN_EMAIL=admin@example.com
|
||||||
ADMIN_PASSWORD=your_admin_password
|
ADMIN_PASSWORD=your_admin_password
|
||||||
|
|
||||||
# Optional: Custom port
|
# Optional: Custom port
|
||||||
SERVER_PORT=8080
|
SERVER_PORT=8080
|
||||||
|
```
|
||||||
|
|
||||||
# Optional: Security configuration
|
**Generate secure secrets:**
|
||||||
# Enable URL allowlist validation (false to skip allowlist checks, only basic format validation)
|
```bash
|
||||||
SECURITY_URL_ALLOWLIST_ENABLED=false
|
# Generate JWT_SECRET
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
# Allow insecure HTTP URLs when allowlist is disabled (default: false, requires https)
|
# Generate TOTP_ENCRYPTION_KEY
|
||||||
# ⚠️ WARNING: Enabling this allows HTTP (plaintext) URLs which can expose API keys
|
openssl rand -hex 32
|
||||||
# Only recommended for:
|
|
||||||
# - Development/testing environments
|
|
||||||
# - Internal networks with trusted endpoints
|
|
||||||
# - When using local test servers (http://localhost)
|
|
||||||
# PRODUCTION: Keep this false or use HTTPS URLs only
|
|
||||||
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
|
|
||||||
|
|
||||||
# Allow private IP addresses for upstream/pricing/CRS (for internal deployments)
|
# Generate POSTGRES_PASSWORD
|
||||||
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=false
|
openssl rand -hex 32
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 4. Create data directories (for local version)
|
||||||
|
mkdir -p data postgres_data redis_data
|
||||||
|
|
||||||
# 5. Start all services
|
# 5. Start all services
|
||||||
|
# Option A: Local directory version (recommended - easy migration)
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# Option B: Named volumes version (simple setup)
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# 6. Check status
|
# 6. Check status
|
||||||
docker-compose ps
|
docker-compose -f docker-compose.local.yml ps
|
||||||
|
|
||||||
# 7. View logs
|
# 7. View logs
|
||||||
docker-compose logs -f sub2api
|
docker-compose -f docker-compose.local.yml logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Deployment Versions
|
||||||
|
|
||||||
|
| Version | Data Storage | Migration | Best For |
|
||||||
|
|---------|-------------|-----------|----------|
|
||||||
|
| **docker-compose.local.yml** | Local directories | ✅ Easy (tar entire directory) | Production, frequent backups |
|
||||||
|
| **docker-compose.yml** | Named volumes | ⚠️ Requires docker commands | Simple setup |
|
||||||
|
|
||||||
|
**Recommendation:** Use `docker-compose.local.yml` (deployed by script) for easier data management.
|
||||||
|
|
||||||
#### Access
|
#### Access
|
||||||
|
|
||||||
Open `http://YOUR_SERVER_IP:8080` in your browser.
|
Open `http://YOUR_SERVER_IP:8080` in your browser.
|
||||||
|
|
||||||
|
If admin password was auto-generated, find it in logs:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
||||||
|
```
|
||||||
|
|
||||||
#### Upgrade
|
#### Upgrade
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pull latest image and recreate container
|
# Pull latest image and recreate container
|
||||||
docker-compose pull
|
docker-compose -f docker-compose.local.yml pull
|
||||||
docker-compose up -d
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Easy Migration (Local Directory Version)
|
||||||
|
|
||||||
|
When using `docker-compose.local.yml`, migrate to a new server easily:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On source server
|
||||||
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
cd ..
|
||||||
|
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
||||||
|
|
||||||
|
# Transfer to new server
|
||||||
|
scp sub2api-complete.tar.gz user@new-server:/path/
|
||||||
|
|
||||||
|
# On new server
|
||||||
|
tar xzf sub2api-complete.tar.gz
|
||||||
|
cd sub2api-deploy/
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Useful Commands
|
#### Useful Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop all services
|
# Stop all services
|
||||||
docker-compose down
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
|
||||||
# Restart
|
# Restart
|
||||||
docker-compose restart
|
docker-compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
# View all logs
|
# View all logs
|
||||||
docker-compose logs -f
|
docker-compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
|
# Remove all data (caution!)
|
||||||
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
rm -rf data/ postgres_data/ redis_data/
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
135
README_CN.md
135
README_CN.md
@@ -57,6 +57,13 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## OpenAI Responses 兼容注意事项
|
||||||
|
|
||||||
|
- 当请求包含 `function_call_output` 时,需要携带 `previous_response_id`,或在 `input` 中包含带 `call_id` 的 `tool_call`/`function_call`,或带非空 `id` 且与 `function_call_output.call_id` 匹配的 `item_reference`。
|
||||||
|
- 若依赖上游历史记录,网关会强制 `store=true` 并需要复用 `previous_response_id`,以避免出现 “No tool call found for function call output” 错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 部署方式
|
## 部署方式
|
||||||
|
|
||||||
### 方式一:脚本安装(推荐)
|
### 方式一:脚本安装(推荐)
|
||||||
@@ -128,7 +135,7 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 方式二:Docker Compose
|
### 方式二:Docker Compose(推荐)
|
||||||
|
|
||||||
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
|
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
|
||||||
|
|
||||||
@@ -137,87 +144,157 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
|
|||||||
- Docker 20.10+
|
- Docker 20.10+
|
||||||
- Docker Compose v2+
|
- Docker Compose v2+
|
||||||
|
|
||||||
#### 安装步骤
|
#### 快速开始(一键部署)
|
||||||
|
|
||||||
|
使用自动化部署脚本快速搭建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建部署目录
|
||||||
|
mkdir -p sub2api-deploy && cd sub2api-deploy
|
||||||
|
|
||||||
|
# 下载并运行部署准备脚本
|
||||||
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker-compose -f docker-compose.local.yml logs -f sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
**脚本功能:**
|
||||||
|
- 下载 `docker-compose.local.yml` 和 `.env.example`
|
||||||
|
- 自动生成安全凭证(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)
|
||||||
|
- 创建 `.env` 文件并填充自动生成的密钥
|
||||||
|
- 创建数据目录(使用本地目录,便于备份和迁移)
|
||||||
|
- 显示生成的凭证供你记录
|
||||||
|
|
||||||
|
#### 手动部署
|
||||||
|
|
||||||
|
如果你希望手动配置:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 克隆仓库
|
# 1. 克隆仓库
|
||||||
git clone https://github.com/Wei-Shaw/sub2api.git
|
git clone https://github.com/Wei-Shaw/sub2api.git
|
||||||
cd sub2api
|
cd sub2api/deploy
|
||||||
|
|
||||||
# 2. 进入 deploy 目录
|
# 2. 复制环境配置文件
|
||||||
cd deploy
|
|
||||||
|
|
||||||
# 3. 复制环境配置文件
|
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
# 4. 编辑配置(设置密码等)
|
# 3. 编辑配置(生成安全密码)
|
||||||
nano .env
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
**`.env` 必须配置项:**
|
**`.env` 必须配置项:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# PostgreSQL 密码(必须修改!)
|
# PostgreSQL 密码(必需)
|
||||||
POSTGRES_PASSWORD=your_secure_password_here
|
POSTGRES_PASSWORD=your_secure_password_here
|
||||||
|
|
||||||
|
# JWT 密钥(推荐 - 重启后保持用户登录状态)
|
||||||
|
JWT_SECRET=your_jwt_secret_here
|
||||||
|
|
||||||
|
# TOTP 加密密钥(推荐 - 重启后保留双因素认证)
|
||||||
|
TOTP_ENCRYPTION_KEY=your_totp_key_here
|
||||||
|
|
||||||
# 可选:管理员账号
|
# 可选:管理员账号
|
||||||
ADMIN_EMAIL=admin@example.com
|
ADMIN_EMAIL=admin@example.com
|
||||||
ADMIN_PASSWORD=your_admin_password
|
ADMIN_PASSWORD=your_admin_password
|
||||||
|
|
||||||
# 可选:自定义端口
|
# 可选:自定义端口
|
||||||
SERVER_PORT=8080
|
SERVER_PORT=8080
|
||||||
|
```
|
||||||
|
|
||||||
# 可选:安全配置
|
**生成安全密钥:**
|
||||||
# 启用 URL 白名单验证(false 则跳过白名单检查,仅做基本格式校验)
|
```bash
|
||||||
SECURITY_URL_ALLOWLIST_ENABLED=false
|
# 生成 JWT_SECRET
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
# 关闭白名单时,是否允许 http:// URL(默认 false,只允许 https://)
|
# 生成 TOTP_ENCRYPTION_KEY
|
||||||
# ⚠️ 警告:允许 HTTP 会暴露 API 密钥(明文传输)
|
openssl rand -hex 32
|
||||||
# 仅建议在以下场景使用:
|
|
||||||
# - 开发/测试环境
|
|
||||||
# - 内部可信网络
|
|
||||||
# - 本地测试服务器(http://localhost)
|
|
||||||
# 生产环境:保持 false 或仅使用 HTTPS URL
|
|
||||||
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
|
|
||||||
|
|
||||||
# 是否允许私有 IP 地址用于上游/定价/CRS(内网部署时使用)
|
# 生成 POSTGRES_PASSWORD
|
||||||
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=false
|
openssl rand -hex 32
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 4. 创建数据目录(本地版)
|
||||||
|
mkdir -p data postgres_data redis_data
|
||||||
|
|
||||||
# 5. 启动所有服务
|
# 5. 启动所有服务
|
||||||
|
# 选项 A:本地目录版(推荐 - 易于迁移)
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# 选项 B:命名卷版(简单设置)
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# 6. 查看状态
|
# 6. 查看状态
|
||||||
docker-compose ps
|
docker-compose -f docker-compose.local.yml ps
|
||||||
|
|
||||||
# 7. 查看日志
|
# 7. 查看日志
|
||||||
docker-compose logs -f sub2api
|
docker-compose -f docker-compose.local.yml logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 部署版本对比
|
||||||
|
|
||||||
|
| 版本 | 数据存储 | 迁移便利性 | 适用场景 |
|
||||||
|
|------|---------|-----------|---------|
|
||||||
|
| **docker-compose.local.yml** | 本地目录 | ✅ 简单(打包整个目录) | 生产环境、频繁备份 |
|
||||||
|
| **docker-compose.yml** | 命名卷 | ⚠️ 需要 docker 命令 | 简单设置 |
|
||||||
|
|
||||||
|
**推荐:** 使用 `docker-compose.local.yml`(脚本部署)以便更轻松地管理数据。
|
||||||
|
|
||||||
#### 访问
|
#### 访问
|
||||||
|
|
||||||
在浏览器中打开 `http://你的服务器IP:8080`
|
在浏览器中打开 `http://你的服务器IP:8080`
|
||||||
|
|
||||||
|
如果管理员密码是自动生成的,在日志中查找:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
||||||
|
```
|
||||||
|
|
||||||
#### 升级
|
#### 升级
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 拉取最新镜像并重建容器
|
# 拉取最新镜像并重建容器
|
||||||
docker-compose pull
|
docker-compose -f docker-compose.local.yml pull
|
||||||
docker-compose up -d
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 轻松迁移(本地目录版)
|
||||||
|
|
||||||
|
使用 `docker-compose.local.yml` 时,可以轻松迁移到新服务器:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 源服务器
|
||||||
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
cd ..
|
||||||
|
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
||||||
|
|
||||||
|
# 传输到新服务器
|
||||||
|
scp sub2api-complete.tar.gz user@new-server:/path/
|
||||||
|
|
||||||
|
# 新服务器
|
||||||
|
tar xzf sub2api-complete.tar.gz
|
||||||
|
cd sub2api-deploy/
|
||||||
|
docker-compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 常用命令
|
#### 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 停止所有服务
|
# 停止所有服务
|
||||||
docker-compose down
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
|
||||||
# 重启
|
# 重启
|
||||||
docker-compose restart
|
docker-compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
# 查看所有日志
|
# 查看所有日志
|
||||||
docker-compose logs -f
|
docker-compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
|
# 删除所有数据(谨慎!)
|
||||||
|
docker-compose -f docker-compose.local.yml down
|
||||||
|
rm -rf data/ postgres_data/ redis_data/
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
2
backend/.dockerignore
Normal file
2
backend/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.cache/
|
||||||
|
.DS_Store
|
||||||
@@ -18,6 +18,12 @@ linters:
|
|||||||
list-mode: original
|
list-mode: original
|
||||||
files:
|
files:
|
||||||
- "**/internal/service/**"
|
- "**/internal/service/**"
|
||||||
|
- "!**/internal/service/ops_aggregation_service.go"
|
||||||
|
- "!**/internal/service/ops_alert_evaluator_service.go"
|
||||||
|
- "!**/internal/service/ops_cleanup_service.go"
|
||||||
|
- "!**/internal/service/ops_metrics_collector.go"
|
||||||
|
- "!**/internal/service/ops_scheduled_report_service.go"
|
||||||
|
- "!**/internal/service/wire.go"
|
||||||
deny:
|
deny:
|
||||||
- pkg: github.com/Wei-Shaw/sub2api/internal/repository
|
- pkg: github.com/Wei-Shaw/sub2api/internal/repository
|
||||||
desc: "service must not import repository"
|
desc: "service must not import repository"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.1.46
|
0.1.61
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -44,7 +45,25 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initLogger configures the default slog handler based on gin.Mode().
|
||||||
|
// In non-release mode, Debug level logs are enabled.
|
||||||
|
func initLogger() {
|
||||||
|
var level slog.Level
|
||||||
|
if gin.Mode() == gin.ReleaseMode {
|
||||||
|
level = slog.LevelInfo
|
||||||
|
} else {
|
||||||
|
level = slog.LevelDebug
|
||||||
|
}
|
||||||
|
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: level,
|
||||||
|
})
|
||||||
|
slog.SetDefault(slog.New(handler))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Initialize slog logger based on gin mode
|
||||||
|
initLogger()
|
||||||
|
|
||||||
// Parse command line flags
|
// Parse command line flags
|
||||||
setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
|
setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
|
||||||
showVersion := flag.Bool("version", false, "Show version information")
|
showVersion := flag.Bool("version", false, "Show version information")
|
||||||
|
|||||||
@@ -62,8 +62,16 @@ func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
|||||||
func provideCleanup(
|
func provideCleanup(
|
||||||
entClient *ent.Client,
|
entClient *ent.Client,
|
||||||
rdb *redis.Client,
|
rdb *redis.Client,
|
||||||
|
opsMetricsCollector *service.OpsMetricsCollector,
|
||||||
|
opsAggregation *service.OpsAggregationService,
|
||||||
|
opsAlertEvaluator *service.OpsAlertEvaluatorService,
|
||||||
|
opsCleanup *service.OpsCleanupService,
|
||||||
|
opsScheduledReport *service.OpsScheduledReportService,
|
||||||
|
schedulerSnapshot *service.SchedulerSnapshotService,
|
||||||
tokenRefresh *service.TokenRefreshService,
|
tokenRefresh *service.TokenRefreshService,
|
||||||
accountExpiry *service.AccountExpiryService,
|
accountExpiry *service.AccountExpiryService,
|
||||||
|
subscriptionExpiry *service.SubscriptionExpiryService,
|
||||||
|
usageCleanup *service.UsageCleanupService,
|
||||||
pricing *service.PricingService,
|
pricing *service.PricingService,
|
||||||
emailQueue *service.EmailQueueService,
|
emailQueue *service.EmailQueueService,
|
||||||
billingCache *service.BillingCacheService,
|
billingCache *service.BillingCacheService,
|
||||||
@@ -81,6 +89,48 @@ func provideCleanup(
|
|||||||
name string
|
name string
|
||||||
fn func() error
|
fn func() error
|
||||||
}{
|
}{
|
||||||
|
{"OpsScheduledReportService", func() error {
|
||||||
|
if opsScheduledReport != nil {
|
||||||
|
opsScheduledReport.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsCleanupService", func() error {
|
||||||
|
if opsCleanup != nil {
|
||||||
|
opsCleanup.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsAlertEvaluatorService", func() error {
|
||||||
|
if opsAlertEvaluator != nil {
|
||||||
|
opsAlertEvaluator.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsAggregationService", func() error {
|
||||||
|
if opsAggregation != nil {
|
||||||
|
opsAggregation.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsMetricsCollector", func() error {
|
||||||
|
if opsMetricsCollector != nil {
|
||||||
|
opsMetricsCollector.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"SchedulerSnapshotService", func() error {
|
||||||
|
if schedulerSnapshot != nil {
|
||||||
|
schedulerSnapshot.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"UsageCleanupService", func() error {
|
||||||
|
if usageCleanup != nil {
|
||||||
|
usageCleanup.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
{"TokenRefreshService", func() error {
|
{"TokenRefreshService", func() error {
|
||||||
tokenRefresh.Stop()
|
tokenRefresh.Stop()
|
||||||
return nil
|
return nil
|
||||||
@@ -89,6 +139,10 @@ func provideCleanup(
|
|||||||
accountExpiry.Stop()
|
accountExpiry.Stop()
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
|
{"SubscriptionExpiryService", func() error {
|
||||||
|
subscriptionExpiry.Stop()
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
{"PricingService", func() error {
|
{"PricingService", func() error {
|
||||||
pricing.Stop()
|
pricing.Stop()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -55,31 +55,51 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
billingCache := repository.NewBillingCache(redisClient)
|
billingCache := repository.NewBillingCache(redisClient)
|
||||||
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
|
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
|
||||||
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
|
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
|
||||||
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client)
|
|
||||||
authService := service.NewAuthService(userRepository, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService)
|
|
||||||
userService := service.NewUserService(userRepository)
|
|
||||||
authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService, promoService)
|
|
||||||
userHandler := handler.NewUserHandler(userService)
|
|
||||||
apiKeyRepository := repository.NewAPIKeyRepository(client)
|
apiKeyRepository := repository.NewAPIKeyRepository(client)
|
||||||
groupRepository := repository.NewGroupRepository(client, db)
|
groupRepository := repository.NewGroupRepository(client, db)
|
||||||
apiKeyCache := repository.NewAPIKeyCache(redisClient)
|
apiKeyCache := repository.NewAPIKeyCache(redisClient)
|
||||||
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, apiKeyCache, configConfig)
|
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, apiKeyCache, configConfig)
|
||||||
|
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
|
||||||
|
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||||
|
authService := service.NewAuthService(userRepository, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService)
|
||||||
|
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator)
|
||||||
|
secretEncryptor, err := repository.NewAESEncryptor(configConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
totpCache := repository.NewTotpCache(redisClient)
|
||||||
|
totpService := service.NewTotpService(userRepository, secretEncryptor, totpCache, settingService, emailService, emailQueueService)
|
||||||
|
authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService, promoService, totpService)
|
||||||
|
userHandler := handler.NewUserHandler(userService)
|
||||||
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
||||||
usageLogRepository := repository.NewUsageLogRepository(client, db)
|
usageLogRepository := repository.NewUsageLogRepository(client, db)
|
||||||
usageService := service.NewUsageService(usageLogRepository, userRepository, client)
|
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
|
||||||
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
|
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
|
||||||
redeemCodeRepository := repository.NewRedeemCodeRepository(client)
|
redeemCodeRepository := repository.NewRedeemCodeRepository(client)
|
||||||
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService)
|
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService)
|
||||||
redeemCache := repository.NewRedeemCache(redisClient)
|
redeemCache := repository.NewRedeemCache(redisClient)
|
||||||
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client)
|
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||||
redeemHandler := handler.NewRedeemHandler(redeemService)
|
redeemHandler := handler.NewRedeemHandler(redeemService)
|
||||||
subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService)
|
subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService)
|
||||||
dashboardService := service.NewDashboardService(usageLogRepository)
|
announcementRepository := repository.NewAnnouncementRepository(client)
|
||||||
dashboardHandler := admin.NewDashboardHandler(dashboardService)
|
announcementReadRepository := repository.NewAnnouncementReadRepository(client)
|
||||||
accountRepository := repository.NewAccountRepository(client, db)
|
announcementService := service.NewAnnouncementService(announcementRepository, announcementReadRepository, userRepository, userSubscriptionRepository)
|
||||||
|
announcementHandler := handler.NewAnnouncementHandler(announcementService)
|
||||||
|
dashboardAggregationRepository := repository.NewDashboardAggregationRepository(db)
|
||||||
|
dashboardStatsCache := repository.NewDashboardCache(redisClient, configConfig)
|
||||||
|
dashboardService := service.NewDashboardService(usageLogRepository, dashboardAggregationRepository, dashboardStatsCache, configConfig)
|
||||||
|
timingWheelService, err := service.ProvideTimingWheelService()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig)
|
||||||
|
dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService)
|
||||||
|
schedulerCache := repository.NewSchedulerCache(redisClient)
|
||||||
|
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
|
||||||
proxyRepository := repository.NewProxyRepository(client, db)
|
proxyRepository := repository.NewProxyRepository(client, db)
|
||||||
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
|
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
|
||||||
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, billingCacheService, proxyExitInfoProber)
|
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
|
||||||
|
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator)
|
||||||
adminUserHandler := admin.NewUserHandler(adminService)
|
adminUserHandler := admin.NewUserHandler(adminService)
|
||||||
groupHandler := admin.NewGroupHandler(adminService)
|
groupHandler := admin.NewGroupHandler(adminService)
|
||||||
claudeOAuthClient := repository.NewClaudeOAuthClient()
|
claudeOAuthClient := repository.NewClaudeOAuthClient()
|
||||||
@@ -92,22 +112,27 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
|
antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
|
||||||
geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository)
|
geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository)
|
||||||
tempUnschedCache := repository.NewTempUnschedCache(redisClient)
|
tempUnschedCache := repository.NewTempUnschedCache(redisClient)
|
||||||
rateLimitService := service.NewRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache)
|
timeoutCounterCache := repository.NewTimeoutCounterCache(redisClient)
|
||||||
claudeUsageFetcher := repository.NewClaudeUsageFetcher()
|
geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
|
||||||
|
compositeTokenCacheInvalidator := service.NewCompositeTokenCacheInvalidator(geminiTokenCache)
|
||||||
|
rateLimitService := service.ProvideRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache, timeoutCounterCache, settingService, compositeTokenCacheInvalidator)
|
||||||
|
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
||||||
|
claudeUsageFetcher := repository.NewClaudeUsageFetcher(httpUpstream)
|
||||||
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
|
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
|
||||||
usageCache := service.NewUsageCache()
|
usageCache := service.NewUsageCache()
|
||||||
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache)
|
identityCache := repository.NewIdentityCache(redisClient)
|
||||||
geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
|
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache)
|
||||||
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService)
|
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService)
|
||||||
gatewayCache := repository.NewGatewayCache(redisClient)
|
gatewayCache := repository.NewGatewayCache(redisClient)
|
||||||
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
|
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
|
||||||
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
|
||||||
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
|
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
|
||||||
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
|
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
|
||||||
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
|
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
|
||||||
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
|
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
|
||||||
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
|
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
|
||||||
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService)
|
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
|
||||||
|
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, compositeTokenCacheInvalidator)
|
||||||
|
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
|
||||||
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
||||||
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
||||||
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
||||||
@@ -115,44 +140,58 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
proxyHandler := admin.NewProxyHandler(adminService)
|
proxyHandler := admin.NewProxyHandler(adminService)
|
||||||
adminRedeemHandler := admin.NewRedeemHandler(adminService)
|
adminRedeemHandler := admin.NewRedeemHandler(adminService)
|
||||||
promoHandler := admin.NewPromoHandler(promoService)
|
promoHandler := admin.NewPromoHandler(promoService)
|
||||||
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService)
|
opsRepository := repository.NewOpsRepository(db)
|
||||||
updateCache := repository.NewUpdateCache(redisClient)
|
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
|
||||||
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
|
schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig)
|
||||||
serviceBuildInfo := provideServiceBuildInfo(buildInfo)
|
|
||||||
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo)
|
|
||||||
systemHandler := handler.ProvideSystemHandler(updateService)
|
|
||||||
adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
|
|
||||||
adminUsageHandler := admin.NewUsageHandler(usageService, apiKeyService, adminService)
|
|
||||||
userAttributeDefinitionRepository := repository.NewUserAttributeDefinitionRepository(client)
|
|
||||||
userAttributeValueRepository := repository.NewUserAttributeValueRepository(client)
|
|
||||||
userAttributeService := service.NewUserAttributeService(userAttributeDefinitionRepository, userAttributeValueRepository)
|
|
||||||
userAttributeHandler := admin.NewUserAttributeHandler(userAttributeService)
|
|
||||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler)
|
|
||||||
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
|
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
|
||||||
pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient)
|
pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
billingService := service.NewBillingService(configConfig, pricingService)
|
billingService := service.NewBillingService(configConfig, pricingService)
|
||||||
identityCache := repository.NewIdentityCache(redisClient)
|
|
||||||
identityService := service.NewIdentityService(identityCache)
|
identityService := service.NewIdentityService(identityCache)
|
||||||
timingWheelService := service.ProvideTimingWheelService()
|
|
||||||
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
||||||
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService)
|
claudeTokenProvider := service.NewClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService)
|
||||||
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache)
|
||||||
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, configConfig)
|
openAITokenProvider := service.NewOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService)
|
||||||
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService)
|
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)
|
||||||
|
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService)
|
||||||
|
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
|
||||||
|
opsHandler := admin.NewOpsHandler(opsService)
|
||||||
|
updateCache := repository.NewUpdateCache(redisClient)
|
||||||
|
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
|
||||||
|
serviceBuildInfo := provideServiceBuildInfo(buildInfo)
|
||||||
|
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo)
|
||||||
|
systemHandler := handler.ProvideSystemHandler(updateService)
|
||||||
|
adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
|
||||||
|
usageCleanupRepository := repository.NewUsageCleanupRepository(client, db)
|
||||||
|
usageCleanupService := service.ProvideUsageCleanupService(usageCleanupRepository, timingWheelService, dashboardAggregationService, configConfig)
|
||||||
|
adminUsageHandler := admin.NewUsageHandler(usageService, apiKeyService, adminService, usageCleanupService)
|
||||||
|
userAttributeDefinitionRepository := repository.NewUserAttributeDefinitionRepository(client)
|
||||||
|
userAttributeValueRepository := repository.NewUserAttributeValueRepository(client)
|
||||||
|
userAttributeService := service.NewUserAttributeService(userAttributeDefinitionRepository, userAttributeValueRepository)
|
||||||
|
userAttributeHandler := admin.NewUserAttributeHandler(userAttributeService)
|
||||||
|
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler)
|
||||||
|
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, configConfig)
|
||||||
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, configConfig)
|
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, configConfig)
|
||||||
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
|
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
|
||||||
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler)
|
totpHandler := handler.NewTotpHandler(totpService)
|
||||||
|
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler)
|
||||||
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
||||||
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
||||||
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
||||||
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, settingService, redisClient)
|
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, opsService, settingService, redisClient)
|
||||||
httpServer := server.ProvideHTTPServer(configConfig, engine)
|
httpServer := server.ProvideHTTPServer(configConfig, engine)
|
||||||
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, configConfig)
|
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
|
||||||
|
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
|
||||||
|
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig)
|
||||||
|
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig)
|
||||||
|
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
|
||||||
|
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig)
|
||||||
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
||||||
v := provideCleanup(client, redisClient, tokenRefreshService, accountExpiryService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService)
|
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
|
||||||
|
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService)
|
||||||
application := &Application{
|
application := &Application{
|
||||||
Server: httpServer,
|
Server: httpServer,
|
||||||
Cleanup: v,
|
Cleanup: v,
|
||||||
@@ -177,8 +216,16 @@ func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
|||||||
func provideCleanup(
|
func provideCleanup(
|
||||||
entClient *ent.Client,
|
entClient *ent.Client,
|
||||||
rdb *redis.Client,
|
rdb *redis.Client,
|
||||||
|
opsMetricsCollector *service.OpsMetricsCollector,
|
||||||
|
opsAggregation *service.OpsAggregationService,
|
||||||
|
opsAlertEvaluator *service.OpsAlertEvaluatorService,
|
||||||
|
opsCleanup *service.OpsCleanupService,
|
||||||
|
opsScheduledReport *service.OpsScheduledReportService,
|
||||||
|
schedulerSnapshot *service.SchedulerSnapshotService,
|
||||||
tokenRefresh *service.TokenRefreshService,
|
tokenRefresh *service.TokenRefreshService,
|
||||||
accountExpiry *service.AccountExpiryService,
|
accountExpiry *service.AccountExpiryService,
|
||||||
|
subscriptionExpiry *service.SubscriptionExpiryService,
|
||||||
|
usageCleanup *service.UsageCleanupService,
|
||||||
pricing *service.PricingService,
|
pricing *service.PricingService,
|
||||||
emailQueue *service.EmailQueueService,
|
emailQueue *service.EmailQueueService,
|
||||||
billingCache *service.BillingCacheService,
|
billingCache *service.BillingCacheService,
|
||||||
@@ -195,6 +242,48 @@ func provideCleanup(
|
|||||||
name string
|
name string
|
||||||
fn func() error
|
fn func() error
|
||||||
}{
|
}{
|
||||||
|
{"OpsScheduledReportService", func() error {
|
||||||
|
if opsScheduledReport != nil {
|
||||||
|
opsScheduledReport.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsCleanupService", func() error {
|
||||||
|
if opsCleanup != nil {
|
||||||
|
opsCleanup.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsAlertEvaluatorService", func() error {
|
||||||
|
if opsAlertEvaluator != nil {
|
||||||
|
opsAlertEvaluator.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsAggregationService", func() error {
|
||||||
|
if opsAggregation != nil {
|
||||||
|
opsAggregation.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"OpsMetricsCollector", func() error {
|
||||||
|
if opsMetricsCollector != nil {
|
||||||
|
opsMetricsCollector.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"SchedulerSnapshotService", func() error {
|
||||||
|
if schedulerSnapshot != nil {
|
||||||
|
schedulerSnapshot.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
{"UsageCleanupService", func() error {
|
||||||
|
if usageCleanup != nil {
|
||||||
|
usageCleanup.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
{"TokenRefreshService", func() error {
|
{"TokenRefreshService", func() error {
|
||||||
tokenRefresh.Stop()
|
tokenRefresh.Stop()
|
||||||
return nil
|
return nil
|
||||||
@@ -203,6 +292,10 @@ func provideCleanup(
|
|||||||
accountExpiry.Stop()
|
accountExpiry.Stop()
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
|
{"SubscriptionExpiryService", func() error {
|
||||||
|
subscriptionExpiry.Stop()
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
{"PricingService", func() error {
|
{"PricingService", func() error {
|
||||||
pricing.Stop()
|
pricing.Stop()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ type Account struct {
|
|||||||
Concurrency int `json:"concurrency,omitempty"`
|
Concurrency int `json:"concurrency,omitempty"`
|
||||||
// Priority holds the value of the "priority" field.
|
// Priority holds the value of the "priority" field.
|
||||||
Priority int `json:"priority,omitempty"`
|
Priority int `json:"priority,omitempty"`
|
||||||
|
// RateMultiplier holds the value of the "rate_multiplier" field.
|
||||||
|
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
|
||||||
// Status holds the value of the "status" field.
|
// Status holds the value of the "status" field.
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
// ErrorMessage holds the value of the "error_message" field.
|
// ErrorMessage holds the value of the "error_message" field.
|
||||||
@@ -135,6 +137,8 @@ func (*Account) scanValues(columns []string) ([]any, error) {
|
|||||||
values[i] = new([]byte)
|
values[i] = new([]byte)
|
||||||
case account.FieldAutoPauseOnExpired, account.FieldSchedulable:
|
case account.FieldAutoPauseOnExpired, account.FieldSchedulable:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
|
case account.FieldRateMultiplier:
|
||||||
|
values[i] = new(sql.NullFloat64)
|
||||||
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
|
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus:
|
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus:
|
||||||
@@ -241,6 +245,12 @@ func (_m *Account) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.Priority = int(value.Int64)
|
_m.Priority = int(value.Int64)
|
||||||
}
|
}
|
||||||
|
case account.FieldRateMultiplier:
|
||||||
|
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field rate_multiplier", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.RateMultiplier = value.Float64
|
||||||
|
}
|
||||||
case account.FieldStatus:
|
case account.FieldStatus:
|
||||||
if value, ok := values[i].(*sql.NullString); !ok {
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field status", values[i])
|
return fmt.Errorf("unexpected type %T for field status", values[i])
|
||||||
@@ -420,6 +430,9 @@ func (_m *Account) String() string {
|
|||||||
builder.WriteString("priority=")
|
builder.WriteString("priority=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
|
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("rate_multiplier=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
|
||||||
|
builder.WriteString(", ")
|
||||||
builder.WriteString("status=")
|
builder.WriteString("status=")
|
||||||
builder.WriteString(_m.Status)
|
builder.WriteString(_m.Status)
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const (
|
|||||||
FieldConcurrency = "concurrency"
|
FieldConcurrency = "concurrency"
|
||||||
// FieldPriority holds the string denoting the priority field in the database.
|
// FieldPriority holds the string denoting the priority field in the database.
|
||||||
FieldPriority = "priority"
|
FieldPriority = "priority"
|
||||||
|
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
||||||
|
FieldRateMultiplier = "rate_multiplier"
|
||||||
// FieldStatus holds the string denoting the status field in the database.
|
// FieldStatus holds the string denoting the status field in the database.
|
||||||
FieldStatus = "status"
|
FieldStatus = "status"
|
||||||
// FieldErrorMessage holds the string denoting the error_message field in the database.
|
// FieldErrorMessage holds the string denoting the error_message field in the database.
|
||||||
@@ -116,6 +118,7 @@ var Columns = []string{
|
|||||||
FieldProxyID,
|
FieldProxyID,
|
||||||
FieldConcurrency,
|
FieldConcurrency,
|
||||||
FieldPriority,
|
FieldPriority,
|
||||||
|
FieldRateMultiplier,
|
||||||
FieldStatus,
|
FieldStatus,
|
||||||
FieldErrorMessage,
|
FieldErrorMessage,
|
||||||
FieldLastUsedAt,
|
FieldLastUsedAt,
|
||||||
@@ -174,6 +177,8 @@ var (
|
|||||||
DefaultConcurrency int
|
DefaultConcurrency int
|
||||||
// DefaultPriority holds the default value on creation for the "priority" field.
|
// DefaultPriority holds the default value on creation for the "priority" field.
|
||||||
DefaultPriority int
|
DefaultPriority int
|
||||||
|
// DefaultRateMultiplier holds the default value on creation for the "rate_multiplier" field.
|
||||||
|
DefaultRateMultiplier float64
|
||||||
// DefaultStatus holds the default value on creation for the "status" field.
|
// DefaultStatus holds the default value on creation for the "status" field.
|
||||||
DefaultStatus string
|
DefaultStatus string
|
||||||
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
@@ -244,6 +249,11 @@ func ByPriority(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldPriority, opts...).ToFunc()
|
return sql.OrderByField(FieldPriority, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByRateMultiplier orders the results by the rate_multiplier field.
|
||||||
|
func ByRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByStatus orders the results by the status field.
|
// ByStatus orders the results by the status field.
|
||||||
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ func Priority(v int) predicate.Account {
|
|||||||
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RateMultiplier applies equality check predicate on the "rate_multiplier" field. It's identical to RateMultiplierEQ.
|
||||||
|
func RateMultiplier(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldEQ(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
|
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
|
||||||
func Status(v string) predicate.Account {
|
func Status(v string) predicate.Account {
|
||||||
return predicate.Account(sql.FieldEQ(FieldStatus, v))
|
return predicate.Account(sql.FieldEQ(FieldStatus, v))
|
||||||
@@ -675,6 +680,46 @@ func PriorityLTE(v int) predicate.Account {
|
|||||||
return predicate.Account(sql.FieldLTE(FieldPriority, v))
|
return predicate.Account(sql.FieldLTE(FieldPriority, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RateMultiplierEQ applies the EQ predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierEQ(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldEQ(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierNEQ applies the NEQ predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierNEQ(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldNEQ(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierIn applies the In predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierIn(vs ...float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldIn(FieldRateMultiplier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierNotIn applies the NotIn predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierNotIn(vs ...float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldNotIn(FieldRateMultiplier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierGT applies the GT predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierGT(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldGT(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierGTE applies the GTE predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierGTE(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldGTE(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierLT applies the LT predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierLT(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldLT(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateMultiplierLTE applies the LTE predicate on the "rate_multiplier" field.
|
||||||
|
func RateMultiplierLTE(v float64) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldLTE(FieldRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
// StatusEQ applies the EQ predicate on the "status" field.
|
// StatusEQ applies the EQ predicate on the "status" field.
|
||||||
func StatusEQ(v string) predicate.Account {
|
func StatusEQ(v string) predicate.Account {
|
||||||
return predicate.Account(sql.FieldEQ(FieldStatus, v))
|
return predicate.Account(sql.FieldEQ(FieldStatus, v))
|
||||||
|
|||||||
@@ -153,6 +153,20 @@ func (_c *AccountCreate) SetNillablePriority(v *int) *AccountCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (_c *AccountCreate) SetRateMultiplier(v float64) *AccountCreate {
|
||||||
|
_c.mutation.SetRateMultiplier(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_c *AccountCreate) SetNillableRateMultiplier(v *float64) *AccountCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (_c *AccountCreate) SetStatus(v string) *AccountCreate {
|
func (_c *AccountCreate) SetStatus(v string) *AccountCreate {
|
||||||
_c.mutation.SetStatus(v)
|
_c.mutation.SetStatus(v)
|
||||||
@@ -429,6 +443,10 @@ func (_c *AccountCreate) defaults() error {
|
|||||||
v := account.DefaultPriority
|
v := account.DefaultPriority
|
||||||
_c.mutation.SetPriority(v)
|
_c.mutation.SetPriority(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.RateMultiplier(); !ok {
|
||||||
|
v := account.DefaultRateMultiplier
|
||||||
|
_c.mutation.SetRateMultiplier(v)
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.Status(); !ok {
|
if _, ok := _c.mutation.Status(); !ok {
|
||||||
v := account.DefaultStatus
|
v := account.DefaultStatus
|
||||||
_c.mutation.SetStatus(v)
|
_c.mutation.SetStatus(v)
|
||||||
@@ -488,6 +506,9 @@ func (_c *AccountCreate) check() error {
|
|||||||
if _, ok := _c.mutation.Priority(); !ok {
|
if _, ok := _c.mutation.Priority(); !ok {
|
||||||
return &ValidationError{Name: "priority", err: errors.New(`ent: missing required field "Account.priority"`)}
|
return &ValidationError{Name: "priority", err: errors.New(`ent: missing required field "Account.priority"`)}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.RateMultiplier(); !ok {
|
||||||
|
return &ValidationError{Name: "rate_multiplier", err: errors.New(`ent: missing required field "Account.rate_multiplier"`)}
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.Status(); !ok {
|
if _, ok := _c.mutation.Status(); !ok {
|
||||||
return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Account.status"`)}
|
return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Account.status"`)}
|
||||||
}
|
}
|
||||||
@@ -578,6 +599,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||||
_node.Priority = value
|
_node.Priority = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.RateMultiplier(); ok {
|
||||||
|
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
|
_node.RateMultiplier = value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.Status(); ok {
|
if value, ok := _c.mutation.Status(); ok {
|
||||||
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
||||||
_node.Status = value
|
_node.Status = value
|
||||||
@@ -893,6 +918,24 @@ func (u *AccountUpsert) AddPriority(v int) *AccountUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsert) SetRateMultiplier(v float64) *AccountUpsert {
|
||||||
|
u.Set(account.FieldRateMultiplier, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsert) UpdateRateMultiplier() *AccountUpsert {
|
||||||
|
u.SetExcluded(account.FieldRateMultiplier)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRateMultiplier adds v to the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsert) AddRateMultiplier(v float64) *AccountUpsert {
|
||||||
|
u.Add(account.FieldRateMultiplier, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (u *AccountUpsert) SetStatus(v string) *AccountUpsert {
|
func (u *AccountUpsert) SetStatus(v string) *AccountUpsert {
|
||||||
u.Set(account.FieldStatus, v)
|
u.Set(account.FieldStatus, v)
|
||||||
@@ -1325,6 +1368,27 @@ func (u *AccountUpsertOne) UpdatePriority() *AccountUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsertOne) SetRateMultiplier(v float64) *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.SetRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRateMultiplier adds v to the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsertOne) AddRateMultiplier(v float64) *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.AddRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsertOne) UpdateRateMultiplier() *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.UpdateRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (u *AccountUpsertOne) SetStatus(v string) *AccountUpsertOne {
|
func (u *AccountUpsertOne) SetStatus(v string) *AccountUpsertOne {
|
||||||
return u.Update(func(s *AccountUpsert) {
|
return u.Update(func(s *AccountUpsert) {
|
||||||
@@ -1956,6 +2020,27 @@ func (u *AccountUpsertBulk) UpdatePriority() *AccountUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsertBulk) SetRateMultiplier(v float64) *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.SetRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRateMultiplier adds v to the "rate_multiplier" field.
|
||||||
|
func (u *AccountUpsertBulk) AddRateMultiplier(v float64) *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.AddRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRateMultiplier sets the "rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsertBulk) UpdateRateMultiplier() *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.UpdateRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (u *AccountUpsertBulk) SetStatus(v string) *AccountUpsertBulk {
|
func (u *AccountUpsertBulk) SetStatus(v string) *AccountUpsertBulk {
|
||||||
return u.Update(func(s *AccountUpsert) {
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
|||||||
@@ -193,6 +193,27 @@ func (_u *AccountUpdate) AddPriority(v int) *AccountUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (_u *AccountUpdate) SetRateMultiplier(v float64) *AccountUpdate {
|
||||||
|
_u.mutation.ResetRateMultiplier()
|
||||||
|
_u.mutation.SetRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_u *AccountUpdate) SetNillableRateMultiplier(v *float64) *AccountUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRateMultiplier adds value to the "rate_multiplier" field.
|
||||||
|
func (_u *AccountUpdate) AddRateMultiplier(v float64) *AccountUpdate {
|
||||||
|
_u.mutation.AddRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (_u *AccountUpdate) SetStatus(v string) *AccountUpdate {
|
func (_u *AccountUpdate) SetStatus(v string) *AccountUpdate {
|
||||||
_u.mutation.SetStatus(v)
|
_u.mutation.SetStatus(v)
|
||||||
@@ -629,6 +650,12 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.AddedPriority(); ok {
|
if value, ok := _u.mutation.AddedPriority(); ok {
|
||||||
_spec.AddField(account.FieldPriority, field.TypeInt, value)
|
_spec.AddField(account.FieldPriority, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.RateMultiplier(); ok {
|
||||||
|
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
||||||
|
_spec.AddField(account.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Status(); ok {
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
||||||
}
|
}
|
||||||
@@ -1005,6 +1032,27 @@ func (_u *AccountUpdateOne) AddPriority(v int) *AccountUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRateMultiplier sets the "rate_multiplier" field.
|
||||||
|
func (_u *AccountUpdateOne) SetRateMultiplier(v float64) *AccountUpdateOne {
|
||||||
|
_u.mutation.ResetRateMultiplier()
|
||||||
|
_u.mutation.SetRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRateMultiplier sets the "rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_u *AccountUpdateOne) SetNillableRateMultiplier(v *float64) *AccountUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRateMultiplier adds value to the "rate_multiplier" field.
|
||||||
|
func (_u *AccountUpdateOne) AddRateMultiplier(v float64) *AccountUpdateOne {
|
||||||
|
_u.mutation.AddRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetStatus sets the "status" field.
|
// SetStatus sets the "status" field.
|
||||||
func (_u *AccountUpdateOne) SetStatus(v string) *AccountUpdateOne {
|
func (_u *AccountUpdateOne) SetStatus(v string) *AccountUpdateOne {
|
||||||
_u.mutation.SetStatus(v)
|
_u.mutation.SetStatus(v)
|
||||||
@@ -1471,6 +1519,12 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
|
|||||||
if value, ok := _u.mutation.AddedPriority(); ok {
|
if value, ok := _u.mutation.AddedPriority(); ok {
|
||||||
_spec.AddField(account.FieldPriority, field.TypeInt, value)
|
_spec.AddField(account.FieldPriority, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.RateMultiplier(); ok {
|
||||||
|
_spec.SetField(account.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
||||||
|
_spec.AddField(account.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Status(); ok {
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
_spec.SetField(account.FieldStatus, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
|||||||
249
backend/ent/announcement.go
Normal file
249
backend/ent/announcement.go
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Announcement is the model entity for the Announcement schema.
|
||||||
|
type Announcement struct {
|
||||||
|
config `json:"-"`
|
||||||
|
// ID of the ent.
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
// 公告标题
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
// 公告内容(支持 Markdown)
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
// 状态: draft, active, archived
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
// 展示条件(JSON 规则)
|
||||||
|
Targeting domain.AnnouncementTargeting `json:"targeting,omitempty"`
|
||||||
|
// 开始展示时间(为空表示立即生效)
|
||||||
|
StartsAt *time.Time `json:"starts_at,omitempty"`
|
||||||
|
// 结束展示时间(为空表示永久生效)
|
||||||
|
EndsAt *time.Time `json:"ends_at,omitempty"`
|
||||||
|
// 创建人用户ID(管理员)
|
||||||
|
CreatedBy *int64 `json:"created_by,omitempty"`
|
||||||
|
// 更新人用户ID(管理员)
|
||||||
|
UpdatedBy *int64 `json:"updated_by,omitempty"`
|
||||||
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
// UpdatedAt holds the value of the "updated_at" field.
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
|
// The values are being populated by the AnnouncementQuery when eager-loading is set.
|
||||||
|
Edges AnnouncementEdges `json:"edges"`
|
||||||
|
selectValues sql.SelectValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementEdges holds the relations/edges for other nodes in the graph.
|
||||||
|
type AnnouncementEdges struct {
|
||||||
|
// Reads holds the value of the reads edge.
|
||||||
|
Reads []*AnnouncementRead `json:"reads,omitempty"`
|
||||||
|
// loadedTypes holds the information for reporting if a
|
||||||
|
// type was loaded (or requested) in eager-loading or not.
|
||||||
|
loadedTypes [1]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadsOrErr returns the Reads value or an error if the edge
|
||||||
|
// was not loaded in eager-loading.
|
||||||
|
func (e AnnouncementEdges) ReadsOrErr() ([]*AnnouncementRead, error) {
|
||||||
|
if e.loadedTypes[0] {
|
||||||
|
return e.Reads, nil
|
||||||
|
}
|
||||||
|
return nil, &NotLoadedError{edge: "reads"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
|
func (*Announcement) scanValues(columns []string) ([]any, error) {
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case announcement.FieldTargeting:
|
||||||
|
values[i] = new([]byte)
|
||||||
|
case announcement.FieldID, announcement.FieldCreatedBy, announcement.FieldUpdatedBy:
|
||||||
|
values[i] = new(sql.NullInt64)
|
||||||
|
case announcement.FieldTitle, announcement.FieldContent, announcement.FieldStatus:
|
||||||
|
values[i] = new(sql.NullString)
|
||||||
|
case announcement.FieldStartsAt, announcement.FieldEndsAt, announcement.FieldCreatedAt, announcement.FieldUpdatedAt:
|
||||||
|
values[i] = new(sql.NullTime)
|
||||||
|
default:
|
||||||
|
values[i] = new(sql.UnknownType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||||
|
// to the Announcement fields.
|
||||||
|
func (_m *Announcement) assignValues(columns []string, values []any) error {
|
||||||
|
if m, n := len(values), len(columns); m < n {
|
||||||
|
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||||
|
}
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case announcement.FieldID:
|
||||||
|
value, ok := values[i].(*sql.NullInt64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field id", value)
|
||||||
|
}
|
||||||
|
_m.ID = int64(value.Int64)
|
||||||
|
case announcement.FieldTitle:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field title", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Title = value.String
|
||||||
|
}
|
||||||
|
case announcement.FieldContent:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field content", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Content = value.String
|
||||||
|
}
|
||||||
|
case announcement.FieldStatus:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field status", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Status = value.String
|
||||||
|
}
|
||||||
|
case announcement.FieldTargeting:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field targeting", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.Targeting); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field targeting: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case announcement.FieldStartsAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field starts_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.StartsAt = new(time.Time)
|
||||||
|
*_m.StartsAt = value.Time
|
||||||
|
}
|
||||||
|
case announcement.FieldEndsAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field ends_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.EndsAt = new(time.Time)
|
||||||
|
*_m.EndsAt = value.Time
|
||||||
|
}
|
||||||
|
case announcement.FieldCreatedBy:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field created_by", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CreatedBy = new(int64)
|
||||||
|
*_m.CreatedBy = value.Int64
|
||||||
|
}
|
||||||
|
case announcement.FieldUpdatedBy:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field updated_by", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.UpdatedBy = new(int64)
|
||||||
|
*_m.UpdatedBy = value.Int64
|
||||||
|
}
|
||||||
|
case announcement.FieldCreatedAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CreatedAt = value.Time
|
||||||
|
}
|
||||||
|
case announcement.FieldUpdatedAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.UpdatedAt = value.Time
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the Announcement.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *Announcement) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryReads queries the "reads" edge of the Announcement entity.
|
||||||
|
func (_m *Announcement) QueryReads() *AnnouncementReadQuery {
|
||||||
|
return NewAnnouncementClient(_m.config).QueryReads(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns a builder for updating this Announcement.
|
||||||
|
// Note that you need to call Announcement.Unwrap() before calling this method if this Announcement
|
||||||
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
|
func (_m *Announcement) Update() *AnnouncementUpdateOne {
|
||||||
|
return NewAnnouncementClient(_m.config).UpdateOne(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the Announcement entity that was returned from a transaction after it was closed,
|
||||||
|
// so that all future queries will be executed through the driver which created the transaction.
|
||||||
|
func (_m *Announcement) Unwrap() *Announcement {
|
||||||
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
|
if !ok {
|
||||||
|
panic("ent: Announcement is not a transactional entity")
|
||||||
|
}
|
||||||
|
_m.config.driver = _tx.drv
|
||||||
|
return _m
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer.
|
||||||
|
func (_m *Announcement) String() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString("Announcement(")
|
||||||
|
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||||
|
builder.WriteString("title=")
|
||||||
|
builder.WriteString(_m.Title)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("content=")
|
||||||
|
builder.WriteString(_m.Content)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("status=")
|
||||||
|
builder.WriteString(_m.Status)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("targeting=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Targeting))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.StartsAt; v != nil {
|
||||||
|
builder.WriteString("starts_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.EndsAt; v != nil {
|
||||||
|
builder.WriteString("ends_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.CreatedBy; v != nil {
|
||||||
|
builder.WriteString("created_by=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.UpdatedBy; v != nil {
|
||||||
|
builder.WriteString("updated_by=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("created_at=")
|
||||||
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("updated_at=")
|
||||||
|
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||||
|
builder.WriteByte(')')
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announcements is a parsable slice of Announcement.
|
||||||
|
type Announcements []*Announcement
|
||||||
164
backend/ent/announcement/announcement.go
Normal file
164
backend/ent/announcement/announcement.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package announcement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Label holds the string label denoting the announcement type in the database.
|
||||||
|
Label = "announcement"
|
||||||
|
// FieldID holds the string denoting the id field in the database.
|
||||||
|
FieldID = "id"
|
||||||
|
// FieldTitle holds the string denoting the title field in the database.
|
||||||
|
FieldTitle = "title"
|
||||||
|
// FieldContent holds the string denoting the content field in the database.
|
||||||
|
FieldContent = "content"
|
||||||
|
// FieldStatus holds the string denoting the status field in the database.
|
||||||
|
FieldStatus = "status"
|
||||||
|
// FieldTargeting holds the string denoting the targeting field in the database.
|
||||||
|
FieldTargeting = "targeting"
|
||||||
|
// FieldStartsAt holds the string denoting the starts_at field in the database.
|
||||||
|
FieldStartsAt = "starts_at"
|
||||||
|
// FieldEndsAt holds the string denoting the ends_at field in the database.
|
||||||
|
FieldEndsAt = "ends_at"
|
||||||
|
// FieldCreatedBy holds the string denoting the created_by field in the database.
|
||||||
|
FieldCreatedBy = "created_by"
|
||||||
|
// FieldUpdatedBy holds the string denoting the updated_by field in the database.
|
||||||
|
FieldUpdatedBy = "updated_by"
|
||||||
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
|
FieldCreatedAt = "created_at"
|
||||||
|
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||||
|
FieldUpdatedAt = "updated_at"
|
||||||
|
// EdgeReads holds the string denoting the reads edge name in mutations.
|
||||||
|
EdgeReads = "reads"
|
||||||
|
// Table holds the table name of the announcement in the database.
|
||||||
|
Table = "announcements"
|
||||||
|
// ReadsTable is the table that holds the reads relation/edge.
|
||||||
|
ReadsTable = "announcement_reads"
|
||||||
|
// ReadsInverseTable is the table name for the AnnouncementRead entity.
|
||||||
|
// It exists in this package in order to avoid circular dependency with the "announcementread" package.
|
||||||
|
ReadsInverseTable = "announcement_reads"
|
||||||
|
// ReadsColumn is the table column denoting the reads relation/edge.
|
||||||
|
ReadsColumn = "announcement_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Columns holds all SQL columns for announcement fields.
|
||||||
|
var Columns = []string{
|
||||||
|
FieldID,
|
||||||
|
FieldTitle,
|
||||||
|
FieldContent,
|
||||||
|
FieldStatus,
|
||||||
|
FieldTargeting,
|
||||||
|
FieldStartsAt,
|
||||||
|
FieldEndsAt,
|
||||||
|
FieldCreatedBy,
|
||||||
|
FieldUpdatedBy,
|
||||||
|
FieldCreatedAt,
|
||||||
|
FieldUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||||
|
func ValidColumn(column string) bool {
|
||||||
|
for i := range Columns {
|
||||||
|
if column == Columns[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TitleValidator is a validator for the "title" field. It is called by the builders before save.
|
||||||
|
TitleValidator func(string) error
|
||||||
|
// ContentValidator is a validator for the "content" field. It is called by the builders before save.
|
||||||
|
ContentValidator func(string) error
|
||||||
|
// DefaultStatus holds the default value on creation for the "status" field.
|
||||||
|
DefaultStatus string
|
||||||
|
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
|
StatusValidator func(string) error
|
||||||
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
|
DefaultCreatedAt func() time.Time
|
||||||
|
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||||
|
DefaultUpdatedAt func() time.Time
|
||||||
|
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||||
|
UpdateDefaultUpdatedAt func() time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the Announcement queries.
|
||||||
|
type OrderOption func(*sql.Selector)
|
||||||
|
|
||||||
|
// ByID orders the results by the id field.
|
||||||
|
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByTitle orders the results by the title field.
|
||||||
|
func ByTitle(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldTitle, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByContent orders the results by the content field.
|
||||||
|
func ByContent(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldContent, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByStatus orders the results by the status field.
|
||||||
|
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByStartsAt orders the results by the starts_at field.
|
||||||
|
func ByStartsAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldStartsAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByEndsAt orders the results by the ends_at field.
|
||||||
|
func ByEndsAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldEndsAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedBy orders the results by the created_by field.
|
||||||
|
func ByCreatedBy(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedBy, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUpdatedBy orders the results by the updated_by field.
|
||||||
|
func ByUpdatedBy(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUpdatedBy, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUpdatedAt orders the results by the updated_at field.
|
||||||
|
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByReadsCount orders the results by reads count.
|
||||||
|
func ByReadsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborsCount(s, newReadsStep(), opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByReads orders the results by reads terms.
|
||||||
|
func ByReads(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newReadsStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func newReadsStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(ReadsInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, ReadsTable, ReadsColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
624
backend/ent/announcement/where.go
Normal file
624
backend/ent/announcement/where.go
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package announcement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID filters vertices based on their ID field.
|
||||||
|
func ID(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
|
func IDEQ(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
|
func IDNEQ(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDIn applies the In predicate on the ID field.
|
||||||
|
func IDIn(ids ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
|
func IDNotIn(ids ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGT applies the GT predicate on the ID field.
|
||||||
|
func IDGT(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
|
func IDGTE(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLT applies the LT predicate on the ID field.
|
||||||
|
func IDLT(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
|
func IDLTE(id int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title applies equality check predicate on the "title" field. It's identical to TitleEQ.
|
||||||
|
func Title(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content applies equality check predicate on the "content" field. It's identical to ContentEQ.
|
||||||
|
func Content(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
|
||||||
|
func Status(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAt applies equality check predicate on the "starts_at" field. It's identical to StartsAtEQ.
|
||||||
|
func StartsAt(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAt applies equality check predicate on the "ends_at" field. It's identical to EndsAtEQ.
|
||||||
|
func EndsAt(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedBy applies equality check predicate on the "created_by" field. It's identical to CreatedByEQ.
|
||||||
|
func CreatedBy(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedBy applies equality check predicate on the "updated_by" field. It's identical to UpdatedByEQ.
|
||||||
|
func UpdatedBy(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||||
|
func CreatedAt(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
|
||||||
|
func UpdatedAt(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleEQ applies the EQ predicate on the "title" field.
|
||||||
|
func TitleEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleNEQ applies the NEQ predicate on the "title" field.
|
||||||
|
func TitleNEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleIn applies the In predicate on the "title" field.
|
||||||
|
func TitleIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldTitle, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleNotIn applies the NotIn predicate on the "title" field.
|
||||||
|
func TitleNotIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldTitle, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleGT applies the GT predicate on the "title" field.
|
||||||
|
func TitleGT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleGTE applies the GTE predicate on the "title" field.
|
||||||
|
func TitleGTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleLT applies the LT predicate on the "title" field.
|
||||||
|
func TitleLT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleLTE applies the LTE predicate on the "title" field.
|
||||||
|
func TitleLTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleContains applies the Contains predicate on the "title" field.
|
||||||
|
func TitleContains(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContains(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleHasPrefix applies the HasPrefix predicate on the "title" field.
|
||||||
|
func TitleHasPrefix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasPrefix(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleHasSuffix applies the HasSuffix predicate on the "title" field.
|
||||||
|
func TitleHasSuffix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasSuffix(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleEqualFold applies the EqualFold predicate on the "title" field.
|
||||||
|
func TitleEqualFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEqualFold(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleContainsFold applies the ContainsFold predicate on the "title" field.
|
||||||
|
func TitleContainsFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContainsFold(FieldTitle, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentEQ applies the EQ predicate on the "content" field.
|
||||||
|
func ContentEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentNEQ applies the NEQ predicate on the "content" field.
|
||||||
|
func ContentNEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentIn applies the In predicate on the "content" field.
|
||||||
|
func ContentIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldContent, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentNotIn applies the NotIn predicate on the "content" field.
|
||||||
|
func ContentNotIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldContent, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentGT applies the GT predicate on the "content" field.
|
||||||
|
func ContentGT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentGTE applies the GTE predicate on the "content" field.
|
||||||
|
func ContentGTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentLT applies the LT predicate on the "content" field.
|
||||||
|
func ContentLT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentLTE applies the LTE predicate on the "content" field.
|
||||||
|
func ContentLTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentContains applies the Contains predicate on the "content" field.
|
||||||
|
func ContentContains(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContains(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentHasPrefix applies the HasPrefix predicate on the "content" field.
|
||||||
|
func ContentHasPrefix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasPrefix(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentHasSuffix applies the HasSuffix predicate on the "content" field.
|
||||||
|
func ContentHasSuffix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasSuffix(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentEqualFold applies the EqualFold predicate on the "content" field.
|
||||||
|
func ContentEqualFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEqualFold(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentContainsFold applies the ContainsFold predicate on the "content" field.
|
||||||
|
func ContentContainsFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContainsFold(FieldContent, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusEQ applies the EQ predicate on the "status" field.
|
||||||
|
func StatusEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusNEQ applies the NEQ predicate on the "status" field.
|
||||||
|
func StatusNEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusIn applies the In predicate on the "status" field.
|
||||||
|
func StatusIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldStatus, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusNotIn applies the NotIn predicate on the "status" field.
|
||||||
|
func StatusNotIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldStatus, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusGT applies the GT predicate on the "status" field.
|
||||||
|
func StatusGT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusGTE applies the GTE predicate on the "status" field.
|
||||||
|
func StatusGTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusLT applies the LT predicate on the "status" field.
|
||||||
|
func StatusLT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusLTE applies the LTE predicate on the "status" field.
|
||||||
|
func StatusLTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusContains applies the Contains predicate on the "status" field.
|
||||||
|
func StatusContains(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContains(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
|
||||||
|
func StatusHasPrefix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasPrefix(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
|
||||||
|
func StatusHasSuffix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasSuffix(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusEqualFold applies the EqualFold predicate on the "status" field.
|
||||||
|
func StatusEqualFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEqualFold(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusContainsFold applies the ContainsFold predicate on the "status" field.
|
||||||
|
func StatusContainsFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContainsFold(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetingIsNil applies the IsNil predicate on the "targeting" field.
|
||||||
|
func TargetingIsNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIsNull(FieldTargeting))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetingNotNil applies the NotNil predicate on the "targeting" field.
|
||||||
|
func TargetingNotNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotNull(FieldTargeting))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtEQ applies the EQ predicate on the "starts_at" field.
|
||||||
|
func StartsAtEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtNEQ applies the NEQ predicate on the "starts_at" field.
|
||||||
|
func StartsAtNEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtIn applies the In predicate on the "starts_at" field.
|
||||||
|
func StartsAtIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldStartsAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtNotIn applies the NotIn predicate on the "starts_at" field.
|
||||||
|
func StartsAtNotIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldStartsAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtGT applies the GT predicate on the "starts_at" field.
|
||||||
|
func StartsAtGT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtGTE applies the GTE predicate on the "starts_at" field.
|
||||||
|
func StartsAtGTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtLT applies the LT predicate on the "starts_at" field.
|
||||||
|
func StartsAtLT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtLTE applies the LTE predicate on the "starts_at" field.
|
||||||
|
func StartsAtLTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldStartsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtIsNil applies the IsNil predicate on the "starts_at" field.
|
||||||
|
func StartsAtIsNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIsNull(FieldStartsAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtNotNil applies the NotNil predicate on the "starts_at" field.
|
||||||
|
func StartsAtNotNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotNull(FieldStartsAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtEQ applies the EQ predicate on the "ends_at" field.
|
||||||
|
func EndsAtEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtNEQ applies the NEQ predicate on the "ends_at" field.
|
||||||
|
func EndsAtNEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtIn applies the In predicate on the "ends_at" field.
|
||||||
|
func EndsAtIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldEndsAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtNotIn applies the NotIn predicate on the "ends_at" field.
|
||||||
|
func EndsAtNotIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldEndsAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtGT applies the GT predicate on the "ends_at" field.
|
||||||
|
func EndsAtGT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtGTE applies the GTE predicate on the "ends_at" field.
|
||||||
|
func EndsAtGTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtLT applies the LT predicate on the "ends_at" field.
|
||||||
|
func EndsAtLT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtLTE applies the LTE predicate on the "ends_at" field.
|
||||||
|
func EndsAtLTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldEndsAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtIsNil applies the IsNil predicate on the "ends_at" field.
|
||||||
|
func EndsAtIsNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIsNull(FieldEndsAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndsAtNotNil applies the NotNil predicate on the "ends_at" field.
|
||||||
|
func EndsAtNotNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotNull(FieldEndsAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByEQ applies the EQ predicate on the "created_by" field.
|
||||||
|
func CreatedByEQ(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByNEQ applies the NEQ predicate on the "created_by" field.
|
||||||
|
func CreatedByNEQ(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByIn applies the In predicate on the "created_by" field.
|
||||||
|
func CreatedByIn(vs ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldCreatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByNotIn applies the NotIn predicate on the "created_by" field.
|
||||||
|
func CreatedByNotIn(vs ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldCreatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByGT applies the GT predicate on the "created_by" field.
|
||||||
|
func CreatedByGT(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByGTE applies the GTE predicate on the "created_by" field.
|
||||||
|
func CreatedByGTE(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByLT applies the LT predicate on the "created_by" field.
|
||||||
|
func CreatedByLT(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByLTE applies the LTE predicate on the "created_by" field.
|
||||||
|
func CreatedByLTE(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByIsNil applies the IsNil predicate on the "created_by" field.
|
||||||
|
func CreatedByIsNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIsNull(FieldCreatedBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByNotNil applies the NotNil predicate on the "created_by" field.
|
||||||
|
func CreatedByNotNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotNull(FieldCreatedBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByEQ applies the EQ predicate on the "updated_by" field.
|
||||||
|
func UpdatedByEQ(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByNEQ applies the NEQ predicate on the "updated_by" field.
|
||||||
|
func UpdatedByNEQ(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByIn applies the In predicate on the "updated_by" field.
|
||||||
|
func UpdatedByIn(vs ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldUpdatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByNotIn applies the NotIn predicate on the "updated_by" field.
|
||||||
|
func UpdatedByNotIn(vs ...int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldUpdatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByGT applies the GT predicate on the "updated_by" field.
|
||||||
|
func UpdatedByGT(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByGTE applies the GTE predicate on the "updated_by" field.
|
||||||
|
func UpdatedByGTE(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByLT applies the LT predicate on the "updated_by" field.
|
||||||
|
func UpdatedByLT(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByLTE applies the LTE predicate on the "updated_by" field.
|
||||||
|
func UpdatedByLTE(v int64) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldUpdatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByIsNil applies the IsNil predicate on the "updated_by" field.
|
||||||
|
func UpdatedByIsNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIsNull(FieldUpdatedBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedByNotNil applies the NotNil predicate on the "updated_by" field.
|
||||||
|
func UpdatedByNotNil() predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotNull(FieldUpdatedBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtNEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
|
func CreatedAtIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
|
func CreatedAtNotIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
|
func CreatedAtGT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtGTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
|
func CreatedAtLT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtLTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNEQ(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNotIn(vs ...time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLT(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLTE(v time.Time) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasReads applies the HasEdge predicate on the "reads" edge.
|
||||||
|
func HasReads() predicate.Announcement {
|
||||||
|
return predicate.Announcement(func(s *sql.Selector) {
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, ReadsTable, ReadsColumn),
|
||||||
|
)
|
||||||
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasReadsWith applies the HasEdge predicate on the "reads" edge with a given conditions (other predicates).
|
||||||
|
func HasReadsWith(preds ...predicate.AnnouncementRead) predicate.Announcement {
|
||||||
|
return predicate.Announcement(func(s *sql.Selector) {
|
||||||
|
step := newReadsStep()
|
||||||
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
|
for _, p := range preds {
|
||||||
|
p(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// And groups predicates with the AND operator between them.
|
||||||
|
func And(predicates ...predicate.Announcement) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.AndPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or groups predicates with the OR operator between them.
|
||||||
|
func Or(predicates ...predicate.Announcement) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.OrPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not applies the not operator on the given predicate.
|
||||||
|
func Not(p predicate.Announcement) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.NotPredicates(p))
|
||||||
|
}
|
||||||
1159
backend/ent/announcement_create.go
Normal file
1159
backend/ent/announcement_create.go
Normal file
File diff suppressed because it is too large
Load Diff
88
backend/ent/announcement_delete.go
Normal file
88
backend/ent/announcement_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/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementDelete is the builder for deleting a Announcement entity.
|
||||||
|
type AnnouncementDelete struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementDelete builder.
|
||||||
|
func (_d *AnnouncementDelete) Where(ps ...predicate.Announcement) *AnnouncementDelete {
|
||||||
|
_d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
|
func (_d *AnnouncementDelete) Exec(ctx context.Context) (int, error) {
|
||||||
|
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *AnnouncementDelete) ExecX(ctx context.Context) int {
|
||||||
|
n, err := _d.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_d *AnnouncementDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
|
_spec := sqlgraph.NewDeleteSpec(announcement.Table, sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64))
|
||||||
|
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||||
|
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
_d.mutation.done = true
|
||||||
|
return affected, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementDeleteOne is the builder for deleting a single Announcement entity.
|
||||||
|
type AnnouncementDeleteOne struct {
|
||||||
|
_d *AnnouncementDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementDelete builder.
|
||||||
|
func (_d *AnnouncementDeleteOne) Where(ps ...predicate.Announcement) *AnnouncementDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query.
|
||||||
|
func (_d *AnnouncementDeleteOne) Exec(ctx context.Context) error {
|
||||||
|
n, err := _d._d.Exec(ctx)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case n == 0:
|
||||||
|
return &NotFoundError{announcement.Label}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *AnnouncementDeleteOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
643
backend/ent/announcement_query.go
Normal file
643
backend/ent/announcement_query.go
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementQuery is the builder for querying Announcement entities.
|
||||||
|
type AnnouncementQuery struct {
|
||||||
|
config
|
||||||
|
ctx *QueryContext
|
||||||
|
order []announcement.OrderOption
|
||||||
|
inters []Interceptor
|
||||||
|
predicates []predicate.Announcement
|
||||||
|
withReads *AnnouncementReadQuery
|
||||||
|
modifiers []func(*sql.Selector)
|
||||||
|
// intermediate query (i.e. traversal path).
|
||||||
|
sql *sql.Selector
|
||||||
|
path func(context.Context) (*sql.Selector, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where adds a new predicate for the AnnouncementQuery builder.
|
||||||
|
func (_q *AnnouncementQuery) Where(ps ...predicate.Announcement) *AnnouncementQuery {
|
||||||
|
_q.predicates = append(_q.predicates, ps...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the number of records to be returned by this query.
|
||||||
|
func (_q *AnnouncementQuery) Limit(limit int) *AnnouncementQuery {
|
||||||
|
_q.ctx.Limit = &limit
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset to start from.
|
||||||
|
func (_q *AnnouncementQuery) Offset(offset int) *AnnouncementQuery {
|
||||||
|
_q.ctx.Offset = &offset
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique configures the query builder to filter duplicate records on query.
|
||||||
|
// By default, unique is set to true, and can be disabled using this method.
|
||||||
|
func (_q *AnnouncementQuery) Unique(unique bool) *AnnouncementQuery {
|
||||||
|
_q.ctx.Unique = &unique
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order specifies how the records should be ordered.
|
||||||
|
func (_q *AnnouncementQuery) Order(o ...announcement.OrderOption) *AnnouncementQuery {
|
||||||
|
_q.order = append(_q.order, o...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryReads chains the current query on the "reads" edge.
|
||||||
|
func (_q *AnnouncementQuery) QueryReads() *AnnouncementReadQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: _q.config}).Query()
|
||||||
|
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector := _q.sqlQuery(ctx)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcement.Table, announcement.FieldID, selector),
|
||||||
|
sqlgraph.To(announcementread.Table, announcementread.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, announcement.ReadsTable, announcement.ReadsColumn),
|
||||||
|
)
|
||||||
|
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||||
|
return fromU, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first Announcement entity from the query.
|
||||||
|
// Returns a *NotFoundError when no Announcement was found.
|
||||||
|
func (_q *AnnouncementQuery) First(ctx context.Context) (*Announcement, error) {
|
||||||
|
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nil, &NotFoundError{announcement.Label}
|
||||||
|
}
|
||||||
|
return nodes[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstX is like First, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) FirstX(ctx context.Context) *Announcement {
|
||||||
|
node, err := _q.First(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstID returns the first Announcement ID from the query.
|
||||||
|
// Returns a *NotFoundError when no Announcement ID was found.
|
||||||
|
func (_q *AnnouncementQuery) FirstID(ctx context.Context) (id int64, err error) {
|
||||||
|
var ids []int64
|
||||||
|
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
err = &NotFoundError{announcement.Label}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ids[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) FirstIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.FirstID(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only returns a single Announcement entity found by the query, ensuring it only returns one.
|
||||||
|
// Returns a *NotSingularError when more than one Announcement entity is found.
|
||||||
|
// Returns a *NotFoundError when no Announcement entities are found.
|
||||||
|
func (_q *AnnouncementQuery) Only(ctx context.Context) (*Announcement, error) {
|
||||||
|
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch len(nodes) {
|
||||||
|
case 1:
|
||||||
|
return nodes[0], nil
|
||||||
|
case 0:
|
||||||
|
return nil, &NotFoundError{announcement.Label}
|
||||||
|
default:
|
||||||
|
return nil, &NotSingularError{announcement.Label}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyX is like Only, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) OnlyX(ctx context.Context) *Announcement {
|
||||||
|
node, err := _q.Only(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyID is like Only, but returns the only Announcement ID in the query.
|
||||||
|
// Returns a *NotSingularError when more than one Announcement ID is found.
|
||||||
|
// Returns a *NotFoundError when no entities are found.
|
||||||
|
func (_q *AnnouncementQuery) OnlyID(ctx context.Context) (id int64, err error) {
|
||||||
|
var ids []int64
|
||||||
|
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(ids) {
|
||||||
|
case 1:
|
||||||
|
id = ids[0]
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{announcement.Label}
|
||||||
|
default:
|
||||||
|
err = &NotSingularError{announcement.Label}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) OnlyIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.OnlyID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// All executes the query and returns a list of Announcements.
|
||||||
|
func (_q *AnnouncementQuery) All(ctx context.Context) ([]*Announcement, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qr := querierAll[[]*Announcement, *AnnouncementQuery]()
|
||||||
|
return withInterceptors[[]*Announcement](ctx, _q, qr, _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllX is like All, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) AllX(ctx context.Context) []*Announcement {
|
||||||
|
nodes, err := _q.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs executes the query and returns a list of Announcement IDs.
|
||||||
|
func (_q *AnnouncementQuery) IDs(ctx context.Context) (ids []int64, err error) {
|
||||||
|
if _q.ctx.Unique == nil && _q.path != nil {
|
||||||
|
_q.Unique(true)
|
||||||
|
}
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
|
||||||
|
if err = _q.Select(announcement.FieldID).Scan(ctx, &ids); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDsX is like IDs, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) IDsX(ctx context.Context) []int64 {
|
||||||
|
ids, err := _q.IDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the count of the given query.
|
||||||
|
func (_q *AnnouncementQuery) Count(ctx context.Context) (int, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return withInterceptors[int](ctx, _q, querierCount[*AnnouncementQuery](), _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountX is like Count, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) CountX(ctx context.Context) int {
|
||||||
|
count, err := _q.Count(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist returns true if the query has elements in the graph.
|
||||||
|
func (_q *AnnouncementQuery) Exist(ctx context.Context) (bool, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||||
|
switch _, err := _q.FirstID(ctx); {
|
||||||
|
case IsNotFound(err):
|
||||||
|
return false, nil
|
||||||
|
case err != nil:
|
||||||
|
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||||
|
default:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistX is like Exist, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementQuery) ExistX(ctx context.Context) bool {
|
||||||
|
exist, err := _q.Exist(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the AnnouncementQuery builder, including all associated steps. It can be
|
||||||
|
// used to prepare common query builders and use them differently after the clone is made.
|
||||||
|
func (_q *AnnouncementQuery) Clone() *AnnouncementQuery {
|
||||||
|
if _q == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &AnnouncementQuery{
|
||||||
|
config: _q.config,
|
||||||
|
ctx: _q.ctx.Clone(),
|
||||||
|
order: append([]announcement.OrderOption{}, _q.order...),
|
||||||
|
inters: append([]Interceptor{}, _q.inters...),
|
||||||
|
predicates: append([]predicate.Announcement{}, _q.predicates...),
|
||||||
|
withReads: _q.withReads.Clone(),
|
||||||
|
// clone intermediate query.
|
||||||
|
sql: _q.sql.Clone(),
|
||||||
|
path: _q.path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReads tells the query-builder to eager-load the nodes that are connected to
|
||||||
|
// the "reads" edge. The optional arguments are used to configure the query builder of the edge.
|
||||||
|
func (_q *AnnouncementQuery) WithReads(opts ...func(*AnnouncementReadQuery)) *AnnouncementQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: _q.config}).Query()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(query)
|
||||||
|
}
|
||||||
|
_q.withReads = query
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupBy is used to group vertices by one or more fields/columns.
|
||||||
|
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// var v []struct {
|
||||||
|
// Title string `json:"title,omitempty"`
|
||||||
|
// Count int `json:"count,omitempty"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client.Announcement.Query().
|
||||||
|
// GroupBy(announcement.FieldTitle).
|
||||||
|
// Aggregate(ent.Count()).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *AnnouncementQuery) GroupBy(field string, fields ...string) *AnnouncementGroupBy {
|
||||||
|
_q.ctx.Fields = append([]string{field}, fields...)
|
||||||
|
grbuild := &AnnouncementGroupBy{build: _q}
|
||||||
|
grbuild.flds = &_q.ctx.Fields
|
||||||
|
grbuild.label = announcement.Label
|
||||||
|
grbuild.scan = grbuild.Scan
|
||||||
|
return grbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select allows the selection one or more fields/columns for the given query,
|
||||||
|
// instead of selecting all fields in the entity.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// var v []struct {
|
||||||
|
// Title string `json:"title,omitempty"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client.Announcement.Query().
|
||||||
|
// Select(announcement.FieldTitle).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *AnnouncementQuery) Select(fields ...string) *AnnouncementSelect {
|
||||||
|
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||||
|
sbuild := &AnnouncementSelect{AnnouncementQuery: _q}
|
||||||
|
sbuild.label = announcement.Label
|
||||||
|
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||||
|
return sbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate returns a AnnouncementSelect configured with the given aggregations.
|
||||||
|
func (_q *AnnouncementQuery) Aggregate(fns ...AggregateFunc) *AnnouncementSelect {
|
||||||
|
return _q.Select().Aggregate(fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) prepareQuery(ctx context.Context) error {
|
||||||
|
for _, inter := range _q.inters {
|
||||||
|
if inter == nil {
|
||||||
|
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||||
|
}
|
||||||
|
if trv, ok := inter.(Traverser); ok {
|
||||||
|
if err := trv.Traverse(ctx, _q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range _q.ctx.Fields {
|
||||||
|
if !announcement.ValidColumn(f) {
|
||||||
|
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _q.path != nil {
|
||||||
|
prev, err := _q.path(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_q.sql = prev
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Announcement, error) {
|
||||||
|
var (
|
||||||
|
nodes = []*Announcement{}
|
||||||
|
_spec = _q.querySpec()
|
||||||
|
loadedTypes = [1]bool{
|
||||||
|
_q.withReads != nil,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||||
|
return (*Announcement).scanValues(nil, columns)
|
||||||
|
}
|
||||||
|
_spec.Assign = func(columns []string, values []any) error {
|
||||||
|
node := &Announcement{config: _q.config}
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
node.Edges.loadedTypes = loadedTypes
|
||||||
|
return node.assignValues(columns, values)
|
||||||
|
}
|
||||||
|
if len(_q.modifiers) > 0 {
|
||||||
|
_spec.Modifiers = _q.modifiers
|
||||||
|
}
|
||||||
|
for i := range hooks {
|
||||||
|
hooks[i](ctx, _spec)
|
||||||
|
}
|
||||||
|
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
if query := _q.withReads; query != nil {
|
||||||
|
if err := _q.loadReads(ctx, query, nodes,
|
||||||
|
func(n *Announcement) { n.Edges.Reads = []*AnnouncementRead{} },
|
||||||
|
func(n *Announcement, e *AnnouncementRead) { n.Edges.Reads = append(n.Edges.Reads, e) }); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) loadReads(ctx context.Context, query *AnnouncementReadQuery, nodes []*Announcement, init func(*Announcement), assign func(*Announcement, *AnnouncementRead)) error {
|
||||||
|
fks := make([]driver.Value, 0, len(nodes))
|
||||||
|
nodeids := make(map[int64]*Announcement)
|
||||||
|
for i := range nodes {
|
||||||
|
fks = append(fks, nodes[i].ID)
|
||||||
|
nodeids[nodes[i].ID] = nodes[i]
|
||||||
|
if init != nil {
|
||||||
|
init(nodes[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(query.ctx.Fields) > 0 {
|
||||||
|
query.ctx.AppendFieldOnce(announcementread.FieldAnnouncementID)
|
||||||
|
}
|
||||||
|
query.Where(predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
s.Where(sql.InValues(s.C(announcement.ReadsColumn), fks...))
|
||||||
|
}))
|
||||||
|
neighbors, err := query.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, n := range neighbors {
|
||||||
|
fk := n.AnnouncementID
|
||||||
|
node, ok := nodeids[fk]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(`unexpected referenced foreign-key "announcement_id" returned %v for node %v`, fk, n.ID)
|
||||||
|
}
|
||||||
|
assign(node, n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) sqlCount(ctx context.Context) (int, error) {
|
||||||
|
_spec := _q.querySpec()
|
||||||
|
if len(_q.modifiers) > 0 {
|
||||||
|
_spec.Modifiers = _q.modifiers
|
||||||
|
}
|
||||||
|
_spec.Node.Columns = _q.ctx.Fields
|
||||||
|
if len(_q.ctx.Fields) > 0 {
|
||||||
|
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||||
|
}
|
||||||
|
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) querySpec() *sqlgraph.QuerySpec {
|
||||||
|
_spec := sqlgraph.NewQuerySpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64))
|
||||||
|
_spec.From = _q.sql
|
||||||
|
if unique := _q.ctx.Unique; unique != nil {
|
||||||
|
_spec.Unique = *unique
|
||||||
|
} else if _q.path != nil {
|
||||||
|
_spec.Unique = true
|
||||||
|
}
|
||||||
|
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||||
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, announcement.FieldID)
|
||||||
|
for i := range fields {
|
||||||
|
if fields[i] != announcement.FieldID {
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ps := _q.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limit := _q.ctx.Limit; limit != nil {
|
||||||
|
_spec.Limit = *limit
|
||||||
|
}
|
||||||
|
if offset := _q.ctx.Offset; offset != nil {
|
||||||
|
_spec.Offset = *offset
|
||||||
|
}
|
||||||
|
if ps := _q.order; len(ps) > 0 {
|
||||||
|
_spec.Order = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _spec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||||
|
builder := sql.Dialect(_q.driver.Dialect())
|
||||||
|
t1 := builder.Table(announcement.Table)
|
||||||
|
columns := _q.ctx.Fields
|
||||||
|
if len(columns) == 0 {
|
||||||
|
columns = announcement.Columns
|
||||||
|
}
|
||||||
|
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||||
|
if _q.sql != nil {
|
||||||
|
selector = _q.sql
|
||||||
|
selector.Select(selector.Columns(columns...)...)
|
||||||
|
}
|
||||||
|
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||||
|
selector.Distinct()
|
||||||
|
}
|
||||||
|
for _, m := range _q.modifiers {
|
||||||
|
m(selector)
|
||||||
|
}
|
||||||
|
for _, p := range _q.predicates {
|
||||||
|
p(selector)
|
||||||
|
}
|
||||||
|
for _, p := range _q.order {
|
||||||
|
p(selector)
|
||||||
|
}
|
||||||
|
if offset := _q.ctx.Offset; offset != nil {
|
||||||
|
// limit is mandatory for offset clause. We start
|
||||||
|
// with default value, and override it below if needed.
|
||||||
|
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||||
|
}
|
||||||
|
if limit := _q.ctx.Limit; limit != nil {
|
||||||
|
selector.Limit(*limit)
|
||||||
|
}
|
||||||
|
return selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
|
||||||
|
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
|
||||||
|
// either committed or rolled-back.
|
||||||
|
func (_q *AnnouncementQuery) ForUpdate(opts ...sql.LockOption) *AnnouncementQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForUpdate(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
|
||||||
|
// on any rows that are read. Other sessions can read the rows, but cannot modify them
|
||||||
|
// until your transaction commits.
|
||||||
|
func (_q *AnnouncementQuery) ForShare(opts ...sql.LockOption) *AnnouncementQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForShare(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementGroupBy is the group-by builder for Announcement entities.
|
||||||
|
type AnnouncementGroupBy struct {
|
||||||
|
selector
|
||||||
|
build *AnnouncementQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the group-by query.
|
||||||
|
func (_g *AnnouncementGroupBy) Aggregate(fns ...AggregateFunc) *AnnouncementGroupBy {
|
||||||
|
_g.fns = append(_g.fns, fns...)
|
||||||
|
return _g
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_g *AnnouncementGroupBy) Scan(ctx context.Context, v any) error {
|
||||||
|
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
|
||||||
|
if err := _g.build.prepareQuery(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return scanWithInterceptors[*AnnouncementQuery, *AnnouncementGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_g *AnnouncementGroupBy) sqlScan(ctx context.Context, root *AnnouncementQuery, v any) error {
|
||||||
|
selector := root.sqlQuery(ctx).Select()
|
||||||
|
aggregation := make([]string, 0, len(_g.fns))
|
||||||
|
for _, fn := range _g.fns {
|
||||||
|
aggregation = append(aggregation, fn(selector))
|
||||||
|
}
|
||||||
|
if len(selector.SelectedColumns()) == 0 {
|
||||||
|
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||||
|
for _, f := range *_g.flds {
|
||||||
|
columns = append(columns, selector.C(f))
|
||||||
|
}
|
||||||
|
columns = append(columns, aggregation...)
|
||||||
|
selector.Select(columns...)
|
||||||
|
}
|
||||||
|
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows := &sql.Rows{}
|
||||||
|
query, args := selector.Query()
|
||||||
|
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return sql.ScanSlice(rows, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementSelect is the builder for selecting fields of Announcement entities.
|
||||||
|
type AnnouncementSelect struct {
|
||||||
|
*AnnouncementQuery
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the selector query.
|
||||||
|
func (_s *AnnouncementSelect) Aggregate(fns ...AggregateFunc) *AnnouncementSelect {
|
||||||
|
_s.fns = append(_s.fns, fns...)
|
||||||
|
return _s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_s *AnnouncementSelect) Scan(ctx context.Context, v any) error {
|
||||||
|
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
|
||||||
|
if err := _s.prepareQuery(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return scanWithInterceptors[*AnnouncementQuery, *AnnouncementSelect](ctx, _s.AnnouncementQuery, _s, _s.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_s *AnnouncementSelect) sqlScan(ctx context.Context, root *AnnouncementQuery, v any) error {
|
||||||
|
selector := root.sqlQuery(ctx)
|
||||||
|
aggregation := make([]string, 0, len(_s.fns))
|
||||||
|
for _, fn := range _s.fns {
|
||||||
|
aggregation = append(aggregation, fn(selector))
|
||||||
|
}
|
||||||
|
switch n := len(*_s.selector.flds); {
|
||||||
|
case n == 0 && len(aggregation) > 0:
|
||||||
|
selector.Select(aggregation...)
|
||||||
|
case n != 0 && len(aggregation) > 0:
|
||||||
|
selector.AppendSelect(aggregation...)
|
||||||
|
}
|
||||||
|
rows := &sql.Rows{}
|
||||||
|
query, args := selector.Query()
|
||||||
|
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return sql.ScanSlice(rows, v)
|
||||||
|
}
|
||||||
824
backend/ent/announcement_update.go
Normal file
824
backend/ent/announcement_update.go
Normal file
@@ -0,0 +1,824 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementUpdate is the builder for updating Announcement entities.
|
||||||
|
type AnnouncementUpdate struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementUpdate builder.
|
||||||
|
func (_u *AnnouncementUpdate) Where(ps ...predicate.Announcement) *AnnouncementUpdate {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTitle sets the "title" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetTitle(v string) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetTitle(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTitle sets the "title" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableTitle(v *string) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTitle(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContent sets the "content" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetContent(v string) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetContent(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableContent sets the "content" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableContent(v *string) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetContent(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus sets the "status" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetStatus(v string) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetStatus(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableStatus(v *string) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStatus(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTargeting sets the "targeting" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetTargeting(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTargeting sets the "targeting" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableTargeting(v *domain.AnnouncementTargeting) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTargeting(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTargeting clears the value of the "targeting" field.
|
||||||
|
func (_u *AnnouncementUpdate) ClearTargeting() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearTargeting()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartsAt sets the "starts_at" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetStartsAt(v time.Time) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetStartsAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStartsAt sets the "starts_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableStartsAt(v *time.Time) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStartsAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStartsAt clears the value of the "starts_at" field.
|
||||||
|
func (_u *AnnouncementUpdate) ClearStartsAt() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearStartsAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEndsAt sets the "ends_at" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetEndsAt(v time.Time) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetEndsAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEndsAt sets the "ends_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableEndsAt(v *time.Time) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEndsAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearEndsAt clears the value of the "ends_at" field.
|
||||||
|
func (_u *AnnouncementUpdate) ClearEndsAt() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearEndsAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedBy sets the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetCreatedBy(v int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.ResetCreatedBy()
|
||||||
|
_u.mutation.SetCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableCreatedBy(v *int64) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCreatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCreatedBy adds value to the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) AddCreatedBy(v int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.AddCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCreatedBy clears the value of the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) ClearCreatedBy() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearCreatedBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedBy sets the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetUpdatedBy(v int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.ResetUpdatedBy()
|
||||||
|
_u.mutation.SetUpdatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUpdatedBy sets the "updated_by" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableUpdatedBy(v *int64) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUpdatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUpdatedBy adds value to the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) AddUpdatedBy(v int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.AddUpdatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpdatedBy clears the value of the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdate) ClearUpdatedBy() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearUpdatedBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetUpdatedAt(v time.Time) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReadIDs adds the "reads" edge to the AnnouncementRead entity by IDs.
|
||||||
|
func (_u *AnnouncementUpdate) AddReadIDs(ids ...int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.AddReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReads adds the "reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *AnnouncementUpdate) AddReads(v ...*AnnouncementRead) *AnnouncementUpdate {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.AddReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the AnnouncementMutation object of the builder.
|
||||||
|
func (_u *AnnouncementUpdate) Mutation() *AnnouncementMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearReads clears all "reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *AnnouncementUpdate) ClearReads() *AnnouncementUpdate {
|
||||||
|
_u.mutation.ClearReads()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReadIDs removes the "reads" edge to AnnouncementRead entities by IDs.
|
||||||
|
func (_u *AnnouncementUpdate) RemoveReadIDs(ids ...int64) *AnnouncementUpdate {
|
||||||
|
_u.mutation.RemoveReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReads removes "reads" edges to AnnouncementRead entities.
|
||||||
|
func (_u *AnnouncementUpdate) RemoveReads(v ...*AnnouncementRead) *AnnouncementUpdate {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.RemoveReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
|
func (_u *AnnouncementUpdate) Save(ctx context.Context) (int, error) {
|
||||||
|
_u.defaults()
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementUpdate) SaveX(ctx context.Context) int {
|
||||||
|
affected, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_u *AnnouncementUpdate) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementUpdate) ExecX(ctx context.Context) {
|
||||||
|
if err := _u.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults sets the default values of the builder before save.
|
||||||
|
func (_u *AnnouncementUpdate) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := announcement.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *AnnouncementUpdate) check() error {
|
||||||
|
if v, ok := _u.mutation.Title(); ok {
|
||||||
|
if err := announcement.TitleValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Announcement.title": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.Content(); ok {
|
||||||
|
if err := announcement.ContentValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "content", err: fmt.Errorf(`ent: validator failed for field "Announcement.content": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.Status(); ok {
|
||||||
|
if err := announcement.StatusValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *AnnouncementUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64))
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Title(); ok {
|
||||||
|
_spec.SetField(announcement.FieldTitle, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Content(); ok {
|
||||||
|
_spec.SetField(announcement.FieldContent, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
|
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Targeting(); ok {
|
||||||
|
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TargetingCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldTargeting, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.StartsAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldStartsAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.StartsAtCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldStartsAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.EndsAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldEndsAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.EndsAtCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldEndsAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||||
|
_spec.SetField(announcement.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||||
|
_spec.AddField(announcement.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CreatedByCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldCreatedBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpdatedBy(); ok {
|
||||||
|
_spec.SetField(announcement.FieldUpdatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedUpdatedBy(); ok {
|
||||||
|
_spec.AddField(announcement.FieldUpdatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.UpdatedByCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldUpdatedBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.RemovedReadsIDs(); len(nodes) > 0 && !_u.mutation.ReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.ReadsIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{announcement.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementUpdateOne is the builder for updating a single Announcement entity.
|
||||||
|
type AnnouncementUpdateOne struct {
|
||||||
|
config
|
||||||
|
fields []string
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTitle sets the "title" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetTitle(v string) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetTitle(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTitle sets the "title" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableTitle(v *string) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTitle(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContent sets the "content" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetContent(v string) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetContent(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableContent sets the "content" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableContent(v *string) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetContent(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus sets the "status" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetStatus(v string) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetStatus(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableStatus(v *string) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStatus(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTargeting sets the "targeting" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetTargeting(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTargeting sets the "targeting" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableTargeting(v *domain.AnnouncementTargeting) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTargeting(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTargeting clears the value of the "targeting" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearTargeting() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearTargeting()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartsAt sets the "starts_at" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetStartsAt(v time.Time) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetStartsAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStartsAt sets the "starts_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableStartsAt(v *time.Time) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStartsAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStartsAt clears the value of the "starts_at" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearStartsAt() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearStartsAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEndsAt sets the "ends_at" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetEndsAt(v time.Time) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetEndsAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEndsAt sets the "ends_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableEndsAt(v *time.Time) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEndsAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearEndsAt clears the value of the "ends_at" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearEndsAt() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearEndsAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedBy sets the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetCreatedBy(v int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ResetCreatedBy()
|
||||||
|
_u.mutation.SetCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableCreatedBy(v *int64) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCreatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCreatedBy adds value to the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) AddCreatedBy(v int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.AddCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCreatedBy clears the value of the "created_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearCreatedBy() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearCreatedBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedBy sets the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetUpdatedBy(v int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ResetUpdatedBy()
|
||||||
|
_u.mutation.SetUpdatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUpdatedBy sets the "updated_by" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableUpdatedBy(v *int64) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUpdatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUpdatedBy adds value to the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) AddUpdatedBy(v int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.AddUpdatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpdatedBy clears the value of the "updated_by" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearUpdatedBy() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearUpdatedBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetUpdatedAt(v time.Time) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReadIDs adds the "reads" edge to the AnnouncementRead entity by IDs.
|
||||||
|
func (_u *AnnouncementUpdateOne) AddReadIDs(ids ...int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.AddReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReads adds the "reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *AnnouncementUpdateOne) AddReads(v ...*AnnouncementRead) *AnnouncementUpdateOne {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.AddReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the AnnouncementMutation object of the builder.
|
||||||
|
func (_u *AnnouncementUpdateOne) Mutation() *AnnouncementMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearReads clears all "reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *AnnouncementUpdateOne) ClearReads() *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.ClearReads()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReadIDs removes the "reads" edge to AnnouncementRead entities by IDs.
|
||||||
|
func (_u *AnnouncementUpdateOne) RemoveReadIDs(ids ...int64) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.RemoveReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReads removes "reads" edges to AnnouncementRead entities.
|
||||||
|
func (_u *AnnouncementUpdateOne) RemoveReads(v ...*AnnouncementRead) *AnnouncementUpdateOne {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.RemoveReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementUpdate builder.
|
||||||
|
func (_u *AnnouncementUpdateOne) Where(ps ...predicate.Announcement) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||||
|
// The default is selecting all fields defined in the entity schema.
|
||||||
|
func (_u *AnnouncementUpdateOne) Select(field string, fields ...string) *AnnouncementUpdateOne {
|
||||||
|
_u.fields = append([]string{field}, fields...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the updated Announcement entity.
|
||||||
|
func (_u *AnnouncementUpdateOne) Save(ctx context.Context) (*Announcement, error) {
|
||||||
|
_u.defaults()
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementUpdateOne) SaveX(ctx context.Context) *Announcement {
|
||||||
|
node, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query on the entity.
|
||||||
|
func (_u *AnnouncementUpdateOne) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementUpdateOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _u.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults sets the default values of the builder before save.
|
||||||
|
func (_u *AnnouncementUpdateOne) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := announcement.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *AnnouncementUpdateOne) check() error {
|
||||||
|
if v, ok := _u.mutation.Title(); ok {
|
||||||
|
if err := announcement.TitleValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "title", err: fmt.Errorf(`ent: validator failed for field "Announcement.title": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.Content(); ok {
|
||||||
|
if err := announcement.ContentValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "content", err: fmt.Errorf(`ent: validator failed for field "Announcement.content": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.Status(); ok {
|
||||||
|
if err := announcement.StatusValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *AnnouncementUpdateOne) sqlSave(ctx context.Context) (_node *Announcement, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(announcement.Table, announcement.Columns, sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
|
if !ok {
|
||||||
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Announcement.id" for update`)}
|
||||||
|
}
|
||||||
|
_spec.Node.ID.Value = id
|
||||||
|
if fields := _u.fields; len(fields) > 0 {
|
||||||
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, announcement.FieldID)
|
||||||
|
for _, f := range fields {
|
||||||
|
if !announcement.ValidColumn(f) {
|
||||||
|
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
if f != announcement.FieldID {
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Title(); ok {
|
||||||
|
_spec.SetField(announcement.FieldTitle, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Content(); ok {
|
||||||
|
_spec.SetField(announcement.FieldContent, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
|
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Targeting(); ok {
|
||||||
|
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TargetingCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldTargeting, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.StartsAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldStartsAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.StartsAtCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldStartsAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.EndsAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldEndsAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.EndsAtCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldEndsAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||||
|
_spec.SetField(announcement.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||||
|
_spec.AddField(announcement.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CreatedByCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldCreatedBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpdatedBy(); ok {
|
||||||
|
_spec.SetField(announcement.FieldUpdatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedUpdatedBy(); ok {
|
||||||
|
_spec.AddField(announcement.FieldUpdatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.UpdatedByCleared() {
|
||||||
|
_spec.ClearField(announcement.FieldUpdatedBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||||
|
_spec.SetField(announcement.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.RemovedReadsIDs(); len(nodes) > 0 && !_u.mutation.ReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.ReadsIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: announcement.ReadsTable,
|
||||||
|
Columns: []string{announcement.ReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
_node = &Announcement{config: _u.config}
|
||||||
|
_spec.Assign = _node.assignValues
|
||||||
|
_spec.ScanValues = _node.scanValues
|
||||||
|
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{announcement.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
185
backend/ent/announcementread.go
Normal file
185
backend/ent/announcementread.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementRead is the model entity for the AnnouncementRead schema.
|
||||||
|
type AnnouncementRead struct {
|
||||||
|
config `json:"-"`
|
||||||
|
// ID of the ent.
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
// AnnouncementID holds the value of the "announcement_id" field.
|
||||||
|
AnnouncementID int64 `json:"announcement_id,omitempty"`
|
||||||
|
// UserID holds the value of the "user_id" field.
|
||||||
|
UserID int64 `json:"user_id,omitempty"`
|
||||||
|
// 用户首次已读时间
|
||||||
|
ReadAt time.Time `json:"read_at,omitempty"`
|
||||||
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
|
// The values are being populated by the AnnouncementReadQuery when eager-loading is set.
|
||||||
|
Edges AnnouncementReadEdges `json:"edges"`
|
||||||
|
selectValues sql.SelectValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadEdges holds the relations/edges for other nodes in the graph.
|
||||||
|
type AnnouncementReadEdges struct {
|
||||||
|
// Announcement holds the value of the announcement edge.
|
||||||
|
Announcement *Announcement `json:"announcement,omitempty"`
|
||||||
|
// User holds the value of the user edge.
|
||||||
|
User *User `json:"user,omitempty"`
|
||||||
|
// loadedTypes holds the information for reporting if a
|
||||||
|
// type was loaded (or requested) in eager-loading or not.
|
||||||
|
loadedTypes [2]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementOrErr returns the Announcement value or an error if the edge
|
||||||
|
// was not loaded in eager-loading, or loaded but was not found.
|
||||||
|
func (e AnnouncementReadEdges) AnnouncementOrErr() (*Announcement, error) {
|
||||||
|
if e.Announcement != nil {
|
||||||
|
return e.Announcement, nil
|
||||||
|
} else if e.loadedTypes[0] {
|
||||||
|
return nil, &NotFoundError{label: announcement.Label}
|
||||||
|
}
|
||||||
|
return nil, &NotLoadedError{edge: "announcement"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserOrErr returns the User value or an error if the edge
|
||||||
|
// was not loaded in eager-loading, or loaded but was not found.
|
||||||
|
func (e AnnouncementReadEdges) UserOrErr() (*User, error) {
|
||||||
|
if e.User != nil {
|
||||||
|
return e.User, nil
|
||||||
|
} else if e.loadedTypes[1] {
|
||||||
|
return nil, &NotFoundError{label: user.Label}
|
||||||
|
}
|
||||||
|
return nil, &NotLoadedError{edge: "user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
|
func (*AnnouncementRead) scanValues(columns []string) ([]any, error) {
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case announcementread.FieldID, announcementread.FieldAnnouncementID, announcementread.FieldUserID:
|
||||||
|
values[i] = new(sql.NullInt64)
|
||||||
|
case announcementread.FieldReadAt, announcementread.FieldCreatedAt:
|
||||||
|
values[i] = new(sql.NullTime)
|
||||||
|
default:
|
||||||
|
values[i] = new(sql.UnknownType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||||
|
// to the AnnouncementRead fields.
|
||||||
|
func (_m *AnnouncementRead) assignValues(columns []string, values []any) error {
|
||||||
|
if m, n := len(values), len(columns); m < n {
|
||||||
|
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||||
|
}
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case announcementread.FieldID:
|
||||||
|
value, ok := values[i].(*sql.NullInt64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field id", value)
|
||||||
|
}
|
||||||
|
_m.ID = int64(value.Int64)
|
||||||
|
case announcementread.FieldAnnouncementID:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field announcement_id", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.AnnouncementID = value.Int64
|
||||||
|
}
|
||||||
|
case announcementread.FieldUserID:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field user_id", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.UserID = value.Int64
|
||||||
|
}
|
||||||
|
case announcementread.FieldReadAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field read_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.ReadAt = value.Time
|
||||||
|
}
|
||||||
|
case announcementread.FieldCreatedAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CreatedAt = value.Time
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the AnnouncementRead.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *AnnouncementRead) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncement queries the "announcement" edge of the AnnouncementRead entity.
|
||||||
|
func (_m *AnnouncementRead) QueryAnnouncement() *AnnouncementQuery {
|
||||||
|
return NewAnnouncementReadClient(_m.config).QueryAnnouncement(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryUser queries the "user" edge of the AnnouncementRead entity.
|
||||||
|
func (_m *AnnouncementRead) QueryUser() *UserQuery {
|
||||||
|
return NewAnnouncementReadClient(_m.config).QueryUser(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns a builder for updating this AnnouncementRead.
|
||||||
|
// Note that you need to call AnnouncementRead.Unwrap() before calling this method if this AnnouncementRead
|
||||||
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
|
func (_m *AnnouncementRead) Update() *AnnouncementReadUpdateOne {
|
||||||
|
return NewAnnouncementReadClient(_m.config).UpdateOne(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the AnnouncementRead entity that was returned from a transaction after it was closed,
|
||||||
|
// so that all future queries will be executed through the driver which created the transaction.
|
||||||
|
func (_m *AnnouncementRead) Unwrap() *AnnouncementRead {
|
||||||
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
|
if !ok {
|
||||||
|
panic("ent: AnnouncementRead is not a transactional entity")
|
||||||
|
}
|
||||||
|
_m.config.driver = _tx.drv
|
||||||
|
return _m
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer.
|
||||||
|
func (_m *AnnouncementRead) String() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString("AnnouncementRead(")
|
||||||
|
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||||
|
builder.WriteString("announcement_id=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.AnnouncementID))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("user_id=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("read_at=")
|
||||||
|
builder.WriteString(_m.ReadAt.Format(time.ANSIC))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("created_at=")
|
||||||
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
|
builder.WriteByte(')')
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReads is a parsable slice of AnnouncementRead.
|
||||||
|
type AnnouncementReads []*AnnouncementRead
|
||||||
127
backend/ent/announcementread/announcementread.go
Normal file
127
backend/ent/announcementread/announcementread.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package announcementread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Label holds the string label denoting the announcementread type in the database.
|
||||||
|
Label = "announcement_read"
|
||||||
|
// FieldID holds the string denoting the id field in the database.
|
||||||
|
FieldID = "id"
|
||||||
|
// FieldAnnouncementID holds the string denoting the announcement_id field in the database.
|
||||||
|
FieldAnnouncementID = "announcement_id"
|
||||||
|
// FieldUserID holds the string denoting the user_id field in the database.
|
||||||
|
FieldUserID = "user_id"
|
||||||
|
// FieldReadAt holds the string denoting the read_at field in the database.
|
||||||
|
FieldReadAt = "read_at"
|
||||||
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
|
FieldCreatedAt = "created_at"
|
||||||
|
// EdgeAnnouncement holds the string denoting the announcement edge name in mutations.
|
||||||
|
EdgeAnnouncement = "announcement"
|
||||||
|
// EdgeUser holds the string denoting the user edge name in mutations.
|
||||||
|
EdgeUser = "user"
|
||||||
|
// Table holds the table name of the announcementread in the database.
|
||||||
|
Table = "announcement_reads"
|
||||||
|
// AnnouncementTable is the table that holds the announcement relation/edge.
|
||||||
|
AnnouncementTable = "announcement_reads"
|
||||||
|
// AnnouncementInverseTable is the table name for the Announcement entity.
|
||||||
|
// It exists in this package in order to avoid circular dependency with the "announcement" package.
|
||||||
|
AnnouncementInverseTable = "announcements"
|
||||||
|
// AnnouncementColumn is the table column denoting the announcement relation/edge.
|
||||||
|
AnnouncementColumn = "announcement_id"
|
||||||
|
// UserTable is the table that holds the user relation/edge.
|
||||||
|
UserTable = "announcement_reads"
|
||||||
|
// UserInverseTable is the table name for the User entity.
|
||||||
|
// It exists in this package in order to avoid circular dependency with the "user" package.
|
||||||
|
UserInverseTable = "users"
|
||||||
|
// UserColumn is the table column denoting the user relation/edge.
|
||||||
|
UserColumn = "user_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Columns holds all SQL columns for announcementread fields.
|
||||||
|
var Columns = []string{
|
||||||
|
FieldID,
|
||||||
|
FieldAnnouncementID,
|
||||||
|
FieldUserID,
|
||||||
|
FieldReadAt,
|
||||||
|
FieldCreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||||
|
func ValidColumn(column string) bool {
|
||||||
|
for i := range Columns {
|
||||||
|
if column == Columns[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultReadAt holds the default value on creation for the "read_at" field.
|
||||||
|
DefaultReadAt func() time.Time
|
||||||
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
|
DefaultCreatedAt func() time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the AnnouncementRead queries.
|
||||||
|
type OrderOption func(*sql.Selector)
|
||||||
|
|
||||||
|
// ByID orders the results by the id field.
|
||||||
|
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByAnnouncementID orders the results by the announcement_id field.
|
||||||
|
func ByAnnouncementID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldAnnouncementID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUserID orders the results by the user_id field.
|
||||||
|
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUserID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByReadAt orders the results by the read_at field.
|
||||||
|
func ByReadAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldReadAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByAnnouncementField orders the results by announcement field.
|
||||||
|
func ByAnnouncementField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newAnnouncementStep(), sql.OrderByField(field, opts...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUserField orders the results by user field.
|
||||||
|
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func newAnnouncementStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(AnnouncementInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, AnnouncementTable, AnnouncementColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func newUserStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(UserInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
257
backend/ent/announcementread/where.go
Normal file
257
backend/ent/announcementread/where.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package announcementread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID filters vertices based on their ID field.
|
||||||
|
func ID(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
|
func IDEQ(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
|
func IDNEQ(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDIn applies the In predicate on the ID field.
|
||||||
|
func IDIn(ids ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
|
func IDNotIn(ids ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNotIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGT applies the GT predicate on the ID field.
|
||||||
|
func IDGT(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
|
func IDGTE(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLT applies the LT predicate on the ID field.
|
||||||
|
func IDLT(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
|
func IDLTE(id int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementID applies equality check predicate on the "announcement_id" field. It's identical to AnnouncementIDEQ.
|
||||||
|
func AnnouncementID(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldAnnouncementID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
|
||||||
|
func UserID(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldUserID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAt applies equality check predicate on the "read_at" field. It's identical to ReadAtEQ.
|
||||||
|
func ReadAt(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||||
|
func CreatedAt(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementIDEQ applies the EQ predicate on the "announcement_id" field.
|
||||||
|
func AnnouncementIDEQ(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldAnnouncementID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementIDNEQ applies the NEQ predicate on the "announcement_id" field.
|
||||||
|
func AnnouncementIDNEQ(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNEQ(FieldAnnouncementID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementIDIn applies the In predicate on the "announcement_id" field.
|
||||||
|
func AnnouncementIDIn(vs ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldIn(FieldAnnouncementID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementIDNotIn applies the NotIn predicate on the "announcement_id" field.
|
||||||
|
func AnnouncementIDNotIn(vs ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNotIn(FieldAnnouncementID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDEQ applies the EQ predicate on the "user_id" field.
|
||||||
|
func UserIDEQ(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldUserID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
|
||||||
|
func UserIDNEQ(v int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNEQ(FieldUserID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDIn applies the In predicate on the "user_id" field.
|
||||||
|
func UserIDIn(vs ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldIn(FieldUserID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
|
||||||
|
func UserIDNotIn(vs ...int64) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNotIn(FieldUserID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtEQ applies the EQ predicate on the "read_at" field.
|
||||||
|
func ReadAtEQ(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtNEQ applies the NEQ predicate on the "read_at" field.
|
||||||
|
func ReadAtNEQ(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNEQ(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtIn applies the In predicate on the "read_at" field.
|
||||||
|
func ReadAtIn(vs ...time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldIn(FieldReadAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtNotIn applies the NotIn predicate on the "read_at" field.
|
||||||
|
func ReadAtNotIn(vs ...time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNotIn(FieldReadAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtGT applies the GT predicate on the "read_at" field.
|
||||||
|
func ReadAtGT(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGT(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtGTE applies the GTE predicate on the "read_at" field.
|
||||||
|
func ReadAtGTE(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGTE(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtLT applies the LT predicate on the "read_at" field.
|
||||||
|
func ReadAtLT(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLT(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAtLTE applies the LTE predicate on the "read_at" field.
|
||||||
|
func ReadAtLTE(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLTE(FieldReadAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtEQ(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtNEQ(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
|
func CreatedAtIn(vs ...time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
|
func CreatedAtNotIn(vs ...time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
|
func CreatedAtGT(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtGTE(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
|
func CreatedAtLT(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtLTE(v time.Time) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAnnouncement applies the HasEdge predicate on the "announcement" edge.
|
||||||
|
func HasAnnouncement() predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, AnnouncementTable, AnnouncementColumn),
|
||||||
|
)
|
||||||
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAnnouncementWith applies the HasEdge predicate on the "announcement" edge with a given conditions (other predicates).
|
||||||
|
func HasAnnouncementWith(preds ...predicate.Announcement) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
step := newAnnouncementStep()
|
||||||
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
|
for _, p := range preds {
|
||||||
|
p(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasUser applies the HasEdge predicate on the "user" edge.
|
||||||
|
func HasUser() predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
|
||||||
|
)
|
||||||
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
|
||||||
|
func HasUserWith(preds ...predicate.User) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
step := newUserStep()
|
||||||
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
|
for _, p := range preds {
|
||||||
|
p(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// And groups predicates with the AND operator between them.
|
||||||
|
func And(predicates ...predicate.AnnouncementRead) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.AndPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or groups predicates with the OR operator between them.
|
||||||
|
func Or(predicates ...predicate.AnnouncementRead) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.OrPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not applies the not operator on the given predicate.
|
||||||
|
func Not(p predicate.AnnouncementRead) predicate.AnnouncementRead {
|
||||||
|
return predicate.AnnouncementRead(sql.NotPredicates(p))
|
||||||
|
}
|
||||||
660
backend/ent/announcementread_create.go
Normal file
660
backend/ent/announcementread_create.go
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementReadCreate is the builder for creating a AnnouncementRead entity.
|
||||||
|
type AnnouncementReadCreate struct {
|
||||||
|
config
|
||||||
|
mutation *AnnouncementReadMutation
|
||||||
|
hooks []Hook
|
||||||
|
conflict []sql.ConflictOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (_c *AnnouncementReadCreate) SetAnnouncementID(v int64) *AnnouncementReadCreate {
|
||||||
|
_c.mutation.SetAnnouncementID(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_c *AnnouncementReadCreate) SetUserID(v int64) *AnnouncementReadCreate {
|
||||||
|
_c.mutation.SetUserID(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (_c *AnnouncementReadCreate) SetReadAt(v time.Time) *AnnouncementReadCreate {
|
||||||
|
_c.mutation.SetReadAt(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
|
||||||
|
func (_c *AnnouncementReadCreate) SetNillableReadAt(v *time.Time) *AnnouncementReadCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetReadAt(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedAt sets the "created_at" field.
|
||||||
|
func (_c *AnnouncementReadCreate) SetCreatedAt(v time.Time) *AnnouncementReadCreate {
|
||||||
|
_c.mutation.SetCreatedAt(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||||
|
func (_c *AnnouncementReadCreate) SetNillableCreatedAt(v *time.Time) *AnnouncementReadCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetCreatedAt(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
|
||||||
|
func (_c *AnnouncementReadCreate) SetAnnouncement(v *Announcement) *AnnouncementReadCreate {
|
||||||
|
return _c.SetAnnouncementID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUser sets the "user" edge to the User entity.
|
||||||
|
func (_c *AnnouncementReadCreate) SetUser(v *User) *AnnouncementReadCreate {
|
||||||
|
return _c.SetUserID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the AnnouncementReadMutation object of the builder.
|
||||||
|
func (_c *AnnouncementReadCreate) Mutation() *AnnouncementReadMutation {
|
||||||
|
return _c.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save creates the AnnouncementRead in the database.
|
||||||
|
func (_c *AnnouncementReadCreate) Save(ctx context.Context) (*AnnouncementRead, error) {
|
||||||
|
_c.defaults()
|
||||||
|
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX calls Save and panics if Save returns an error.
|
||||||
|
func (_c *AnnouncementReadCreate) SaveX(ctx context.Context) *AnnouncementRead {
|
||||||
|
v, err := _c.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_c *AnnouncementReadCreate) Exec(ctx context.Context) error {
|
||||||
|
_, err := _c.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_c *AnnouncementReadCreate) ExecX(ctx context.Context) {
|
||||||
|
if err := _c.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults sets the default values of the builder before save.
|
||||||
|
func (_c *AnnouncementReadCreate) defaults() {
|
||||||
|
if _, ok := _c.mutation.ReadAt(); !ok {
|
||||||
|
v := announcementread.DefaultReadAt()
|
||||||
|
_c.mutation.SetReadAt(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
|
v := announcementread.DefaultCreatedAt()
|
||||||
|
_c.mutation.SetCreatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_c *AnnouncementReadCreate) check() error {
|
||||||
|
if _, ok := _c.mutation.AnnouncementID(); !ok {
|
||||||
|
return &ValidationError{Name: "announcement_id", err: errors.New(`ent: missing required field "AnnouncementRead.announcement_id"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.UserID(); !ok {
|
||||||
|
return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "AnnouncementRead.user_id"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.ReadAt(); !ok {
|
||||||
|
return &ValidationError{Name: "read_at", err: errors.New(`ent: missing required field "AnnouncementRead.read_at"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
|
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "AnnouncementRead.created_at"`)}
|
||||||
|
}
|
||||||
|
if len(_c.mutation.AnnouncementIDs()) == 0 {
|
||||||
|
return &ValidationError{Name: "announcement", err: errors.New(`ent: missing required edge "AnnouncementRead.announcement"`)}
|
||||||
|
}
|
||||||
|
if len(_c.mutation.UserIDs()) == 0 {
|
||||||
|
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "AnnouncementRead.user"`)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *AnnouncementReadCreate) sqlSave(ctx context.Context) (*AnnouncementRead, error) {
|
||||||
|
if err := _c.check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_node, _spec := _c.createSpec()
|
||||||
|
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
|
||||||
|
if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id := _spec.ID.Value.(int64)
|
||||||
|
_node.ID = int64(id)
|
||||||
|
_c.mutation.id = &_node.ID
|
||||||
|
_c.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *AnnouncementReadCreate) createSpec() (*AnnouncementRead, *sqlgraph.CreateSpec) {
|
||||||
|
var (
|
||||||
|
_node = &AnnouncementRead{config: _c.config}
|
||||||
|
_spec = sqlgraph.NewCreateSpec(announcementread.Table, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
|
||||||
|
)
|
||||||
|
_spec.OnConflict = _c.conflict
|
||||||
|
if value, ok := _c.mutation.ReadAt(); ok {
|
||||||
|
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
|
||||||
|
_node.ReadAt = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.CreatedAt(); ok {
|
||||||
|
_spec.SetField(announcementread.FieldCreatedAt, field.TypeTime, value)
|
||||||
|
_node.CreatedAt = value
|
||||||
|
}
|
||||||
|
if nodes := _c.mutation.AnnouncementIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.AnnouncementTable,
|
||||||
|
Columns: []string{announcementread.AnnouncementColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_node.AnnouncementID = nodes[0]
|
||||||
|
_spec.Edges = append(_spec.Edges, edge)
|
||||||
|
}
|
||||||
|
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.UserTable,
|
||||||
|
Columns: []string{announcementread.UserColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_node.UserID = nodes[0]
|
||||||
|
_spec.Edges = append(_spec.Edges, edge)
|
||||||
|
}
|
||||||
|
return _node, _spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
|
||||||
|
// of the `INSERT` statement. For example:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// SetAnnouncementID(v).
|
||||||
|
// OnConflict(
|
||||||
|
// // Update the row with the new values
|
||||||
|
// // the was proposed for insertion.
|
||||||
|
// sql.ResolveWithNewValues(),
|
||||||
|
// ).
|
||||||
|
// // Override some of the fields with custom
|
||||||
|
// // update values.
|
||||||
|
// Update(func(u *ent.AnnouncementReadUpsert) {
|
||||||
|
// SetAnnouncementID(v+v).
|
||||||
|
// }).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (_c *AnnouncementReadCreate) OnConflict(opts ...sql.ConflictOption) *AnnouncementReadUpsertOne {
|
||||||
|
_c.conflict = opts
|
||||||
|
return &AnnouncementReadUpsertOne{
|
||||||
|
create: _c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||||
|
// as conflict target. Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(sql.ConflictColumns(columns...)).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (_c *AnnouncementReadCreate) OnConflictColumns(columns ...string) *AnnouncementReadUpsertOne {
|
||||||
|
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||||
|
return &AnnouncementReadUpsertOne{
|
||||||
|
create: _c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// AnnouncementReadUpsertOne is the builder for "upsert"-ing
|
||||||
|
// one AnnouncementRead node.
|
||||||
|
AnnouncementReadUpsertOne struct {
|
||||||
|
create *AnnouncementReadCreate
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadUpsert is the "OnConflict" setter.
|
||||||
|
AnnouncementReadUpsert struct {
|
||||||
|
*sql.UpdateSet
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (u *AnnouncementReadUpsert) SetAnnouncementID(v int64) *AnnouncementReadUpsert {
|
||||||
|
u.Set(announcementread.FieldAnnouncementID, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsert) UpdateAnnouncementID() *AnnouncementReadUpsert {
|
||||||
|
u.SetExcluded(announcementread.FieldAnnouncementID)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (u *AnnouncementReadUpsert) SetUserID(v int64) *AnnouncementReadUpsert {
|
||||||
|
u.Set(announcementread.FieldUserID, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserID sets the "user_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsert) UpdateUserID() *AnnouncementReadUpsert {
|
||||||
|
u.SetExcluded(announcementread.FieldUserID)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (u *AnnouncementReadUpsert) SetReadAt(v time.Time) *AnnouncementReadUpsert {
|
||||||
|
u.Set(announcementread.FieldReadAt, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsert) UpdateReadAt() *AnnouncementReadUpsert {
|
||||||
|
u.SetExcluded(announcementread.FieldReadAt)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||||
|
// Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(
|
||||||
|
// sql.ResolveWithNewValues(),
|
||||||
|
// ).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (u *AnnouncementReadUpsertOne) UpdateNewValues() *AnnouncementReadUpsertOne {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
|
||||||
|
if _, exists := u.create.mutation.CreatedAt(); exists {
|
||||||
|
s.SetIgnore(announcementread.FieldCreatedAt)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore sets each column to itself in case of conflict.
|
||||||
|
// Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(sql.ResolveWithIgnore()).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (u *AnnouncementReadUpsertOne) Ignore() *AnnouncementReadUpsertOne {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoNothing configures the conflict_action to `DO NOTHING`.
|
||||||
|
// Supported only by SQLite and PostgreSQL.
|
||||||
|
func (u *AnnouncementReadUpsertOne) DoNothing() *AnnouncementReadUpsertOne {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update allows overriding fields `UPDATE` values. See the AnnouncementReadCreate.OnConflict
|
||||||
|
// documentation for more info.
|
||||||
|
func (u *AnnouncementReadUpsertOne) Update(set func(*AnnouncementReadUpsert)) *AnnouncementReadUpsertOne {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||||
|
set(&AnnouncementReadUpsert{UpdateSet: update})
|
||||||
|
}))
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (u *AnnouncementReadUpsertOne) SetAnnouncementID(v int64) *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetAnnouncementID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertOne) UpdateAnnouncementID() *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateAnnouncementID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (u *AnnouncementReadUpsertOne) SetUserID(v int64) *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetUserID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserID sets the "user_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertOne) UpdateUserID() *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateUserID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (u *AnnouncementReadUpsertOne) SetReadAt(v time.Time) *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetReadAt(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertOne) UpdateReadAt() *AnnouncementReadUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateReadAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (u *AnnouncementReadUpsertOne) Exec(ctx context.Context) error {
|
||||||
|
if len(u.create.conflict) == 0 {
|
||||||
|
return errors.New("ent: missing options for AnnouncementReadCreate.OnConflict")
|
||||||
|
}
|
||||||
|
return u.create.Exec(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (u *AnnouncementReadUpsertOne) ExecX(ctx context.Context) {
|
||||||
|
if err := u.create.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the UPSERT query and returns the inserted/updated ID.
|
||||||
|
func (u *AnnouncementReadUpsertOne) ID(ctx context.Context) (id int64, err error) {
|
||||||
|
node, err := u.create.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
return node.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDX is like ID, but panics if an error occurs.
|
||||||
|
func (u *AnnouncementReadUpsertOne) IDX(ctx context.Context) int64 {
|
||||||
|
id, err := u.ID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadCreateBulk is the builder for creating many AnnouncementRead entities in bulk.
|
||||||
|
type AnnouncementReadCreateBulk struct {
|
||||||
|
config
|
||||||
|
err error
|
||||||
|
builders []*AnnouncementReadCreate
|
||||||
|
conflict []sql.ConflictOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save creates the AnnouncementRead entities in the database.
|
||||||
|
func (_c *AnnouncementReadCreateBulk) Save(ctx context.Context) ([]*AnnouncementRead, error) {
|
||||||
|
if _c.err != nil {
|
||||||
|
return nil, _c.err
|
||||||
|
}
|
||||||
|
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||||
|
nodes := make([]*AnnouncementRead, len(_c.builders))
|
||||||
|
mutators := make([]Mutator, len(_c.builders))
|
||||||
|
for i := range _c.builders {
|
||||||
|
func(i int, root context.Context) {
|
||||||
|
builder := _c.builders[i]
|
||||||
|
builder.defaults()
|
||||||
|
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||||
|
mutation, ok := m.(*AnnouncementReadMutation)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||||
|
}
|
||||||
|
if err := builder.check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builder.mutation = mutation
|
||||||
|
var err error
|
||||||
|
nodes[i], specs[i] = builder.createSpec()
|
||||||
|
if i < len(mutators)-1 {
|
||||||
|
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
|
||||||
|
} else {
|
||||||
|
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||||
|
spec.OnConflict = _c.conflict
|
||||||
|
// Invoke the actual operation on the latest mutation in the chain.
|
||||||
|
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
|
||||||
|
if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mutation.id = &nodes[i].ID
|
||||||
|
if specs[i].ID.Value != nil {
|
||||||
|
id := specs[i].ID.Value.(int64)
|
||||||
|
nodes[i].ID = int64(id)
|
||||||
|
}
|
||||||
|
mutation.done = true
|
||||||
|
return nodes[i], nil
|
||||||
|
})
|
||||||
|
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||||
|
mut = builder.hooks[i](mut)
|
||||||
|
}
|
||||||
|
mutators[i] = mut
|
||||||
|
}(i, ctx)
|
||||||
|
}
|
||||||
|
if len(mutators) > 0 {
|
||||||
|
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_c *AnnouncementReadCreateBulk) SaveX(ctx context.Context) []*AnnouncementRead {
|
||||||
|
v, err := _c.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_c *AnnouncementReadCreateBulk) Exec(ctx context.Context) error {
|
||||||
|
_, err := _c.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_c *AnnouncementReadCreateBulk) ExecX(ctx context.Context) {
|
||||||
|
if err := _c.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
|
||||||
|
// of the `INSERT` statement. For example:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.CreateBulk(builders...).
|
||||||
|
// OnConflict(
|
||||||
|
// // Update the row with the new values
|
||||||
|
// // the was proposed for insertion.
|
||||||
|
// sql.ResolveWithNewValues(),
|
||||||
|
// ).
|
||||||
|
// // Override some of the fields with custom
|
||||||
|
// // update values.
|
||||||
|
// Update(func(u *ent.AnnouncementReadUpsert) {
|
||||||
|
// SetAnnouncementID(v+v).
|
||||||
|
// }).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (_c *AnnouncementReadCreateBulk) OnConflict(opts ...sql.ConflictOption) *AnnouncementReadUpsertBulk {
|
||||||
|
_c.conflict = opts
|
||||||
|
return &AnnouncementReadUpsertBulk{
|
||||||
|
create: _c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||||
|
// as conflict target. Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(sql.ConflictColumns(columns...)).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (_c *AnnouncementReadCreateBulk) OnConflictColumns(columns ...string) *AnnouncementReadUpsertBulk {
|
||||||
|
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||||
|
return &AnnouncementReadUpsertBulk{
|
||||||
|
create: _c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadUpsertBulk is the builder for "upsert"-ing
|
||||||
|
// a bulk of AnnouncementRead nodes.
|
||||||
|
type AnnouncementReadUpsertBulk struct {
|
||||||
|
create *AnnouncementReadCreateBulk
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNewValues updates the mutable fields using the new values that
|
||||||
|
// were set on create. Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(
|
||||||
|
// sql.ResolveWithNewValues(),
|
||||||
|
// ).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (u *AnnouncementReadUpsertBulk) UpdateNewValues() *AnnouncementReadUpsertBulk {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
|
||||||
|
for _, b := range u.create.builders {
|
||||||
|
if _, exists := b.mutation.CreatedAt(); exists {
|
||||||
|
s.SetIgnore(announcementread.FieldCreatedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore sets each column to itself in case of conflict.
|
||||||
|
// Using this option is equivalent to using:
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Create().
|
||||||
|
// OnConflict(sql.ResolveWithIgnore()).
|
||||||
|
// Exec(ctx)
|
||||||
|
func (u *AnnouncementReadUpsertBulk) Ignore() *AnnouncementReadUpsertBulk {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoNothing configures the conflict_action to `DO NOTHING`.
|
||||||
|
// Supported only by SQLite and PostgreSQL.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) DoNothing() *AnnouncementReadUpsertBulk {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update allows overriding fields `UPDATE` values. See the AnnouncementReadCreateBulk.OnConflict
|
||||||
|
// documentation for more info.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) Update(set func(*AnnouncementReadUpsert)) *AnnouncementReadUpsertBulk {
|
||||||
|
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||||
|
set(&AnnouncementReadUpsert{UpdateSet: update})
|
||||||
|
}))
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) SetAnnouncementID(v int64) *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetAnnouncementID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAnnouncementID sets the "announcement_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) UpdateAnnouncementID() *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateAnnouncementID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) SetUserID(v int64) *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetUserID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserID sets the "user_id" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) UpdateUserID() *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateUserID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) SetReadAt(v time.Time) *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.SetReadAt(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReadAt sets the "read_at" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) UpdateReadAt() *AnnouncementReadUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementReadUpsert) {
|
||||||
|
s.UpdateReadAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) Exec(ctx context.Context) error {
|
||||||
|
if u.create.err != nil {
|
||||||
|
return u.create.err
|
||||||
|
}
|
||||||
|
for i, b := range u.create.builders {
|
||||||
|
if len(b.conflict) != 0 {
|
||||||
|
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the AnnouncementReadCreateBulk instead", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(u.create.conflict) == 0 {
|
||||||
|
return errors.New("ent: missing options for AnnouncementReadCreateBulk.OnConflict")
|
||||||
|
}
|
||||||
|
return u.create.Exec(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (u *AnnouncementReadUpsertBulk) ExecX(ctx context.Context) {
|
||||||
|
if err := u.create.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
88
backend/ent/announcementread_delete.go
Normal file
88
backend/ent/announcementread_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/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementReadDelete is the builder for deleting a AnnouncementRead entity.
|
||||||
|
type AnnouncementReadDelete struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementReadMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementReadDelete builder.
|
||||||
|
func (_d *AnnouncementReadDelete) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadDelete {
|
||||||
|
_d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
|
func (_d *AnnouncementReadDelete) Exec(ctx context.Context) (int, error) {
|
||||||
|
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *AnnouncementReadDelete) ExecX(ctx context.Context) int {
|
||||||
|
n, err := _d.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_d *AnnouncementReadDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
|
_spec := sqlgraph.NewDeleteSpec(announcementread.Table, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
|
||||||
|
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||||
|
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
_d.mutation.done = true
|
||||||
|
return affected, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadDeleteOne is the builder for deleting a single AnnouncementRead entity.
|
||||||
|
type AnnouncementReadDeleteOne struct {
|
||||||
|
_d *AnnouncementReadDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementReadDelete builder.
|
||||||
|
func (_d *AnnouncementReadDeleteOne) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query.
|
||||||
|
func (_d *AnnouncementReadDeleteOne) Exec(ctx context.Context) error {
|
||||||
|
n, err := _d._d.Exec(ctx)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case n == 0:
|
||||||
|
return &NotFoundError{announcementread.Label}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *AnnouncementReadDeleteOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
718
backend/ent/announcementread_query.go
Normal file
718
backend/ent/announcementread_query.go
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementReadQuery is the builder for querying AnnouncementRead entities.
|
||||||
|
type AnnouncementReadQuery struct {
|
||||||
|
config
|
||||||
|
ctx *QueryContext
|
||||||
|
order []announcementread.OrderOption
|
||||||
|
inters []Interceptor
|
||||||
|
predicates []predicate.AnnouncementRead
|
||||||
|
withAnnouncement *AnnouncementQuery
|
||||||
|
withUser *UserQuery
|
||||||
|
modifiers []func(*sql.Selector)
|
||||||
|
// intermediate query (i.e. traversal path).
|
||||||
|
sql *sql.Selector
|
||||||
|
path func(context.Context) (*sql.Selector, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where adds a new predicate for the AnnouncementReadQuery builder.
|
||||||
|
func (_q *AnnouncementReadQuery) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadQuery {
|
||||||
|
_q.predicates = append(_q.predicates, ps...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the number of records to be returned by this query.
|
||||||
|
func (_q *AnnouncementReadQuery) Limit(limit int) *AnnouncementReadQuery {
|
||||||
|
_q.ctx.Limit = &limit
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset to start from.
|
||||||
|
func (_q *AnnouncementReadQuery) Offset(offset int) *AnnouncementReadQuery {
|
||||||
|
_q.ctx.Offset = &offset
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique configures the query builder to filter duplicate records on query.
|
||||||
|
// By default, unique is set to true, and can be disabled using this method.
|
||||||
|
func (_q *AnnouncementReadQuery) Unique(unique bool) *AnnouncementReadQuery {
|
||||||
|
_q.ctx.Unique = &unique
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order specifies how the records should be ordered.
|
||||||
|
func (_q *AnnouncementReadQuery) Order(o ...announcementread.OrderOption) *AnnouncementReadQuery {
|
||||||
|
_q.order = append(_q.order, o...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncement chains the current query on the "announcement" edge.
|
||||||
|
func (_q *AnnouncementReadQuery) QueryAnnouncement() *AnnouncementQuery {
|
||||||
|
query := (&AnnouncementClient{config: _q.config}).Query()
|
||||||
|
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector := _q.sqlQuery(ctx)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcementread.Table, announcementread.FieldID, selector),
|
||||||
|
sqlgraph.To(announcement.Table, announcement.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.AnnouncementTable, announcementread.AnnouncementColumn),
|
||||||
|
)
|
||||||
|
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||||
|
return fromU, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryUser chains the current query on the "user" edge.
|
||||||
|
func (_q *AnnouncementReadQuery) QueryUser() *UserQuery {
|
||||||
|
query := (&UserClient{config: _q.config}).Query()
|
||||||
|
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector := _q.sqlQuery(ctx)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcementread.Table, announcementread.FieldID, selector),
|
||||||
|
sqlgraph.To(user.Table, user.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.UserTable, announcementread.UserColumn),
|
||||||
|
)
|
||||||
|
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||||
|
return fromU, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first AnnouncementRead entity from the query.
|
||||||
|
// Returns a *NotFoundError when no AnnouncementRead was found.
|
||||||
|
func (_q *AnnouncementReadQuery) First(ctx context.Context) (*AnnouncementRead, error) {
|
||||||
|
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nil, &NotFoundError{announcementread.Label}
|
||||||
|
}
|
||||||
|
return nodes[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstX is like First, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) FirstX(ctx context.Context) *AnnouncementRead {
|
||||||
|
node, err := _q.First(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstID returns the first AnnouncementRead ID from the query.
|
||||||
|
// Returns a *NotFoundError when no AnnouncementRead ID was found.
|
||||||
|
func (_q *AnnouncementReadQuery) FirstID(ctx context.Context) (id int64, err error) {
|
||||||
|
var ids []int64
|
||||||
|
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
err = &NotFoundError{announcementread.Label}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ids[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) FirstIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.FirstID(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only returns a single AnnouncementRead entity found by the query, ensuring it only returns one.
|
||||||
|
// Returns a *NotSingularError when more than one AnnouncementRead entity is found.
|
||||||
|
// Returns a *NotFoundError when no AnnouncementRead entities are found.
|
||||||
|
func (_q *AnnouncementReadQuery) Only(ctx context.Context) (*AnnouncementRead, error) {
|
||||||
|
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch len(nodes) {
|
||||||
|
case 1:
|
||||||
|
return nodes[0], nil
|
||||||
|
case 0:
|
||||||
|
return nil, &NotFoundError{announcementread.Label}
|
||||||
|
default:
|
||||||
|
return nil, &NotSingularError{announcementread.Label}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyX is like Only, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) OnlyX(ctx context.Context) *AnnouncementRead {
|
||||||
|
node, err := _q.Only(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyID is like Only, but returns the only AnnouncementRead ID in the query.
|
||||||
|
// Returns a *NotSingularError when more than one AnnouncementRead ID is found.
|
||||||
|
// Returns a *NotFoundError when no entities are found.
|
||||||
|
func (_q *AnnouncementReadQuery) OnlyID(ctx context.Context) (id int64, err error) {
|
||||||
|
var ids []int64
|
||||||
|
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(ids) {
|
||||||
|
case 1:
|
||||||
|
id = ids[0]
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{announcementread.Label}
|
||||||
|
default:
|
||||||
|
err = &NotSingularError{announcementread.Label}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) OnlyIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.OnlyID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// All executes the query and returns a list of AnnouncementReads.
|
||||||
|
func (_q *AnnouncementReadQuery) All(ctx context.Context) ([]*AnnouncementRead, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qr := querierAll[[]*AnnouncementRead, *AnnouncementReadQuery]()
|
||||||
|
return withInterceptors[[]*AnnouncementRead](ctx, _q, qr, _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllX is like All, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) AllX(ctx context.Context) []*AnnouncementRead {
|
||||||
|
nodes, err := _q.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs executes the query and returns a list of AnnouncementRead IDs.
|
||||||
|
func (_q *AnnouncementReadQuery) IDs(ctx context.Context) (ids []int64, err error) {
|
||||||
|
if _q.ctx.Unique == nil && _q.path != nil {
|
||||||
|
_q.Unique(true)
|
||||||
|
}
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
|
||||||
|
if err = _q.Select(announcementread.FieldID).Scan(ctx, &ids); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDsX is like IDs, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) IDsX(ctx context.Context) []int64 {
|
||||||
|
ids, err := _q.IDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the count of the given query.
|
||||||
|
func (_q *AnnouncementReadQuery) Count(ctx context.Context) (int, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return withInterceptors[int](ctx, _q, querierCount[*AnnouncementReadQuery](), _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountX is like Count, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) CountX(ctx context.Context) int {
|
||||||
|
count, err := _q.Count(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist returns true if the query has elements in the graph.
|
||||||
|
func (_q *AnnouncementReadQuery) Exist(ctx context.Context) (bool, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||||
|
switch _, err := _q.FirstID(ctx); {
|
||||||
|
case IsNotFound(err):
|
||||||
|
return false, nil
|
||||||
|
case err != nil:
|
||||||
|
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||||
|
default:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistX is like Exist, but panics if an error occurs.
|
||||||
|
func (_q *AnnouncementReadQuery) ExistX(ctx context.Context) bool {
|
||||||
|
exist, err := _q.Exist(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the AnnouncementReadQuery builder, including all associated steps. It can be
|
||||||
|
// used to prepare common query builders and use them differently after the clone is made.
|
||||||
|
func (_q *AnnouncementReadQuery) Clone() *AnnouncementReadQuery {
|
||||||
|
if _q == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &AnnouncementReadQuery{
|
||||||
|
config: _q.config,
|
||||||
|
ctx: _q.ctx.Clone(),
|
||||||
|
order: append([]announcementread.OrderOption{}, _q.order...),
|
||||||
|
inters: append([]Interceptor{}, _q.inters...),
|
||||||
|
predicates: append([]predicate.AnnouncementRead{}, _q.predicates...),
|
||||||
|
withAnnouncement: _q.withAnnouncement.Clone(),
|
||||||
|
withUser: _q.withUser.Clone(),
|
||||||
|
// clone intermediate query.
|
||||||
|
sql: _q.sql.Clone(),
|
||||||
|
path: _q.path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAnnouncement tells the query-builder to eager-load the nodes that are connected to
|
||||||
|
// the "announcement" edge. The optional arguments are used to configure the query builder of the edge.
|
||||||
|
func (_q *AnnouncementReadQuery) WithAnnouncement(opts ...func(*AnnouncementQuery)) *AnnouncementReadQuery {
|
||||||
|
query := (&AnnouncementClient{config: _q.config}).Query()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(query)
|
||||||
|
}
|
||||||
|
_q.withAnnouncement = query
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUser tells the query-builder to eager-load the nodes that are connected to
|
||||||
|
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
|
||||||
|
func (_q *AnnouncementReadQuery) WithUser(opts ...func(*UserQuery)) *AnnouncementReadQuery {
|
||||||
|
query := (&UserClient{config: _q.config}).Query()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(query)
|
||||||
|
}
|
||||||
|
_q.withUser = query
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupBy is used to group vertices by one or more fields/columns.
|
||||||
|
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// var v []struct {
|
||||||
|
// AnnouncementID int64 `json:"announcement_id,omitempty"`
|
||||||
|
// Count int `json:"count,omitempty"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Query().
|
||||||
|
// GroupBy(announcementread.FieldAnnouncementID).
|
||||||
|
// Aggregate(ent.Count()).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *AnnouncementReadQuery) GroupBy(field string, fields ...string) *AnnouncementReadGroupBy {
|
||||||
|
_q.ctx.Fields = append([]string{field}, fields...)
|
||||||
|
grbuild := &AnnouncementReadGroupBy{build: _q}
|
||||||
|
grbuild.flds = &_q.ctx.Fields
|
||||||
|
grbuild.label = announcementread.Label
|
||||||
|
grbuild.scan = grbuild.Scan
|
||||||
|
return grbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select allows the selection one or more fields/columns for the given query,
|
||||||
|
// instead of selecting all fields in the entity.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// var v []struct {
|
||||||
|
// AnnouncementID int64 `json:"announcement_id,omitempty"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client.AnnouncementRead.Query().
|
||||||
|
// Select(announcementread.FieldAnnouncementID).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *AnnouncementReadQuery) Select(fields ...string) *AnnouncementReadSelect {
|
||||||
|
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||||
|
sbuild := &AnnouncementReadSelect{AnnouncementReadQuery: _q}
|
||||||
|
sbuild.label = announcementread.Label
|
||||||
|
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||||
|
return sbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate returns a AnnouncementReadSelect configured with the given aggregations.
|
||||||
|
func (_q *AnnouncementReadQuery) Aggregate(fns ...AggregateFunc) *AnnouncementReadSelect {
|
||||||
|
return _q.Select().Aggregate(fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) prepareQuery(ctx context.Context) error {
|
||||||
|
for _, inter := range _q.inters {
|
||||||
|
if inter == nil {
|
||||||
|
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||||
|
}
|
||||||
|
if trv, ok := inter.(Traverser); ok {
|
||||||
|
if err := trv.Traverse(ctx, _q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range _q.ctx.Fields {
|
||||||
|
if !announcementread.ValidColumn(f) {
|
||||||
|
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _q.path != nil {
|
||||||
|
prev, err := _q.path(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_q.sql = prev
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AnnouncementRead, error) {
|
||||||
|
var (
|
||||||
|
nodes = []*AnnouncementRead{}
|
||||||
|
_spec = _q.querySpec()
|
||||||
|
loadedTypes = [2]bool{
|
||||||
|
_q.withAnnouncement != nil,
|
||||||
|
_q.withUser != nil,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||||
|
return (*AnnouncementRead).scanValues(nil, columns)
|
||||||
|
}
|
||||||
|
_spec.Assign = func(columns []string, values []any) error {
|
||||||
|
node := &AnnouncementRead{config: _q.config}
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
node.Edges.loadedTypes = loadedTypes
|
||||||
|
return node.assignValues(columns, values)
|
||||||
|
}
|
||||||
|
if len(_q.modifiers) > 0 {
|
||||||
|
_spec.Modifiers = _q.modifiers
|
||||||
|
}
|
||||||
|
for i := range hooks {
|
||||||
|
hooks[i](ctx, _spec)
|
||||||
|
}
|
||||||
|
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
if query := _q.withAnnouncement; query != nil {
|
||||||
|
if err := _q.loadAnnouncement(ctx, query, nodes, nil,
|
||||||
|
func(n *AnnouncementRead, e *Announcement) { n.Edges.Announcement = e }); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if query := _q.withUser; query != nil {
|
||||||
|
if err := _q.loadUser(ctx, query, nodes, nil,
|
||||||
|
func(n *AnnouncementRead, e *User) { n.Edges.User = e }); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) loadAnnouncement(ctx context.Context, query *AnnouncementQuery, nodes []*AnnouncementRead, init func(*AnnouncementRead), assign func(*AnnouncementRead, *Announcement)) error {
|
||||||
|
ids := make([]int64, 0, len(nodes))
|
||||||
|
nodeids := make(map[int64][]*AnnouncementRead)
|
||||||
|
for i := range nodes {
|
||||||
|
fk := nodes[i].AnnouncementID
|
||||||
|
if _, ok := nodeids[fk]; !ok {
|
||||||
|
ids = append(ids, fk)
|
||||||
|
}
|
||||||
|
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query.Where(announcement.IDIn(ids...))
|
||||||
|
neighbors, err := query.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, n := range neighbors {
|
||||||
|
nodes, ok := nodeids[n.ID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(`unexpected foreign-key "announcement_id" returned %v`, n.ID)
|
||||||
|
}
|
||||||
|
for i := range nodes {
|
||||||
|
assign(nodes[i], n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (_q *AnnouncementReadQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*AnnouncementRead, init func(*AnnouncementRead), assign func(*AnnouncementRead, *User)) error {
|
||||||
|
ids := make([]int64, 0, len(nodes))
|
||||||
|
nodeids := make(map[int64][]*AnnouncementRead)
|
||||||
|
for i := range nodes {
|
||||||
|
fk := nodes[i].UserID
|
||||||
|
if _, ok := nodeids[fk]; !ok {
|
||||||
|
ids = append(ids, fk)
|
||||||
|
}
|
||||||
|
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query.Where(user.IDIn(ids...))
|
||||||
|
neighbors, err := query.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, n := range neighbors {
|
||||||
|
nodes, ok := nodeids[n.ID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID)
|
||||||
|
}
|
||||||
|
for i := range nodes {
|
||||||
|
assign(nodes[i], n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) sqlCount(ctx context.Context) (int, error) {
|
||||||
|
_spec := _q.querySpec()
|
||||||
|
if len(_q.modifiers) > 0 {
|
||||||
|
_spec.Modifiers = _q.modifiers
|
||||||
|
}
|
||||||
|
_spec.Node.Columns = _q.ctx.Fields
|
||||||
|
if len(_q.ctx.Fields) > 0 {
|
||||||
|
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||||
|
}
|
||||||
|
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) querySpec() *sqlgraph.QuerySpec {
|
||||||
|
_spec := sqlgraph.NewQuerySpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
|
||||||
|
_spec.From = _q.sql
|
||||||
|
if unique := _q.ctx.Unique; unique != nil {
|
||||||
|
_spec.Unique = *unique
|
||||||
|
} else if _q.path != nil {
|
||||||
|
_spec.Unique = true
|
||||||
|
}
|
||||||
|
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||||
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, announcementread.FieldID)
|
||||||
|
for i := range fields {
|
||||||
|
if fields[i] != announcementread.FieldID {
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _q.withAnnouncement != nil {
|
||||||
|
_spec.Node.AddColumnOnce(announcementread.FieldAnnouncementID)
|
||||||
|
}
|
||||||
|
if _q.withUser != nil {
|
||||||
|
_spec.Node.AddColumnOnce(announcementread.FieldUserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ps := _q.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limit := _q.ctx.Limit; limit != nil {
|
||||||
|
_spec.Limit = *limit
|
||||||
|
}
|
||||||
|
if offset := _q.ctx.Offset; offset != nil {
|
||||||
|
_spec.Offset = *offset
|
||||||
|
}
|
||||||
|
if ps := _q.order; len(ps) > 0 {
|
||||||
|
_spec.Order = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _spec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *AnnouncementReadQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||||
|
builder := sql.Dialect(_q.driver.Dialect())
|
||||||
|
t1 := builder.Table(announcementread.Table)
|
||||||
|
columns := _q.ctx.Fields
|
||||||
|
if len(columns) == 0 {
|
||||||
|
columns = announcementread.Columns
|
||||||
|
}
|
||||||
|
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||||
|
if _q.sql != nil {
|
||||||
|
selector = _q.sql
|
||||||
|
selector.Select(selector.Columns(columns...)...)
|
||||||
|
}
|
||||||
|
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||||
|
selector.Distinct()
|
||||||
|
}
|
||||||
|
for _, m := range _q.modifiers {
|
||||||
|
m(selector)
|
||||||
|
}
|
||||||
|
for _, p := range _q.predicates {
|
||||||
|
p(selector)
|
||||||
|
}
|
||||||
|
for _, p := range _q.order {
|
||||||
|
p(selector)
|
||||||
|
}
|
||||||
|
if offset := _q.ctx.Offset; offset != nil {
|
||||||
|
// limit is mandatory for offset clause. We start
|
||||||
|
// with default value, and override it below if needed.
|
||||||
|
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||||
|
}
|
||||||
|
if limit := _q.ctx.Limit; limit != nil {
|
||||||
|
selector.Limit(*limit)
|
||||||
|
}
|
||||||
|
return selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
|
||||||
|
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
|
||||||
|
// either committed or rolled-back.
|
||||||
|
func (_q *AnnouncementReadQuery) ForUpdate(opts ...sql.LockOption) *AnnouncementReadQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForUpdate(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
|
||||||
|
// on any rows that are read. Other sessions can read the rows, but cannot modify them
|
||||||
|
// until your transaction commits.
|
||||||
|
func (_q *AnnouncementReadQuery) ForShare(opts ...sql.LockOption) *AnnouncementReadQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForShare(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadGroupBy is the group-by builder for AnnouncementRead entities.
|
||||||
|
type AnnouncementReadGroupBy struct {
|
||||||
|
selector
|
||||||
|
build *AnnouncementReadQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the group-by query.
|
||||||
|
func (_g *AnnouncementReadGroupBy) Aggregate(fns ...AggregateFunc) *AnnouncementReadGroupBy {
|
||||||
|
_g.fns = append(_g.fns, fns...)
|
||||||
|
return _g
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_g *AnnouncementReadGroupBy) Scan(ctx context.Context, v any) error {
|
||||||
|
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
|
||||||
|
if err := _g.build.prepareQuery(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return scanWithInterceptors[*AnnouncementReadQuery, *AnnouncementReadGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_g *AnnouncementReadGroupBy) sqlScan(ctx context.Context, root *AnnouncementReadQuery, v any) error {
|
||||||
|
selector := root.sqlQuery(ctx).Select()
|
||||||
|
aggregation := make([]string, 0, len(_g.fns))
|
||||||
|
for _, fn := range _g.fns {
|
||||||
|
aggregation = append(aggregation, fn(selector))
|
||||||
|
}
|
||||||
|
if len(selector.SelectedColumns()) == 0 {
|
||||||
|
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||||
|
for _, f := range *_g.flds {
|
||||||
|
columns = append(columns, selector.C(f))
|
||||||
|
}
|
||||||
|
columns = append(columns, aggregation...)
|
||||||
|
selector.Select(columns...)
|
||||||
|
}
|
||||||
|
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows := &sql.Rows{}
|
||||||
|
query, args := selector.Query()
|
||||||
|
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return sql.ScanSlice(rows, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadSelect is the builder for selecting fields of AnnouncementRead entities.
|
||||||
|
type AnnouncementReadSelect struct {
|
||||||
|
*AnnouncementReadQuery
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the selector query.
|
||||||
|
func (_s *AnnouncementReadSelect) Aggregate(fns ...AggregateFunc) *AnnouncementReadSelect {
|
||||||
|
_s.fns = append(_s.fns, fns...)
|
||||||
|
return _s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_s *AnnouncementReadSelect) Scan(ctx context.Context, v any) error {
|
||||||
|
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
|
||||||
|
if err := _s.prepareQuery(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return scanWithInterceptors[*AnnouncementReadQuery, *AnnouncementReadSelect](ctx, _s.AnnouncementReadQuery, _s, _s.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_s *AnnouncementReadSelect) sqlScan(ctx context.Context, root *AnnouncementReadQuery, v any) error {
|
||||||
|
selector := root.sqlQuery(ctx)
|
||||||
|
aggregation := make([]string, 0, len(_s.fns))
|
||||||
|
for _, fn := range _s.fns {
|
||||||
|
aggregation = append(aggregation, fn(selector))
|
||||||
|
}
|
||||||
|
switch n := len(*_s.selector.flds); {
|
||||||
|
case n == 0 && len(aggregation) > 0:
|
||||||
|
selector.Select(aggregation...)
|
||||||
|
case n != 0 && len(aggregation) > 0:
|
||||||
|
selector.AppendSelect(aggregation...)
|
||||||
|
}
|
||||||
|
rows := &sql.Rows{}
|
||||||
|
query, args := selector.Query()
|
||||||
|
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return sql.ScanSlice(rows, v)
|
||||||
|
}
|
||||||
456
backend/ent/announcementread_update.go
Normal file
456
backend/ent/announcementread_update.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementReadUpdate is the builder for updating AnnouncementRead entities.
|
||||||
|
type AnnouncementReadUpdate struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementReadMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementReadUpdate builder.
|
||||||
|
func (_u *AnnouncementReadUpdate) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetAnnouncementID(v int64) *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.SetAnnouncementID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAnnouncementID sets the "announcement_id" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetNillableAnnouncementID(v *int64) *AnnouncementReadUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAnnouncementID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetUserID(v int64) *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.SetUserID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUserID sets the "user_id" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetNillableUserID(v *int64) *AnnouncementReadUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUserID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetReadAt(v time.Time) *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.SetReadAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetNillableReadAt(v *time.Time) *AnnouncementReadUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetReadAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetAnnouncement(v *Announcement) *AnnouncementReadUpdate {
|
||||||
|
return _u.SetAnnouncementID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUser sets the "user" edge to the User entity.
|
||||||
|
func (_u *AnnouncementReadUpdate) SetUser(v *User) *AnnouncementReadUpdate {
|
||||||
|
return _u.SetUserID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the AnnouncementReadMutation object of the builder.
|
||||||
|
func (_u *AnnouncementReadUpdate) Mutation() *AnnouncementReadMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAnnouncement clears the "announcement" edge to the Announcement entity.
|
||||||
|
func (_u *AnnouncementReadUpdate) ClearAnnouncement() *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.ClearAnnouncement()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUser clears the "user" edge to the User entity.
|
||||||
|
func (_u *AnnouncementReadUpdate) ClearUser() *AnnouncementReadUpdate {
|
||||||
|
_u.mutation.ClearUser()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
|
func (_u *AnnouncementReadUpdate) Save(ctx context.Context) (int, error) {
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementReadUpdate) SaveX(ctx context.Context) int {
|
||||||
|
affected, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_u *AnnouncementReadUpdate) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementReadUpdate) ExecX(ctx context.Context) {
|
||||||
|
if err := _u.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *AnnouncementReadUpdate) check() error {
|
||||||
|
if _u.mutation.AnnouncementCleared() && len(_u.mutation.AnnouncementIDs()) > 0 {
|
||||||
|
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.announcement"`)
|
||||||
|
}
|
||||||
|
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
|
||||||
|
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.user"`)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *AnnouncementReadUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ReadAt(); ok {
|
||||||
|
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.AnnouncementCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.AnnouncementTable,
|
||||||
|
Columns: []string{announcementread.AnnouncementColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.AnnouncementIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.AnnouncementTable,
|
||||||
|
Columns: []string{announcementread.AnnouncementColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
if _u.mutation.UserCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.UserTable,
|
||||||
|
Columns: []string{announcementread.UserColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.UserTable,
|
||||||
|
Columns: []string{announcementread.UserColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{announcementread.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadUpdateOne is the builder for updating a single AnnouncementRead entity.
|
||||||
|
type AnnouncementReadUpdateOne struct {
|
||||||
|
config
|
||||||
|
fields []string
|
||||||
|
hooks []Hook
|
||||||
|
mutation *AnnouncementReadMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncementID sets the "announcement_id" field.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetAnnouncementID(v int64) *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.SetAnnouncementID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAnnouncementID sets the "announcement_id" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetNillableAnnouncementID(v *int64) *AnnouncementReadUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAnnouncementID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetUserID(v int64) *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.SetUserID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUserID sets the "user_id" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetNillableUserID(v *int64) *AnnouncementReadUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUserID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadAt sets the "read_at" field.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetReadAt(v time.Time) *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.SetReadAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableReadAt sets the "read_at" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetNillableReadAt(v *time.Time) *AnnouncementReadUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetReadAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAnnouncement sets the "announcement" edge to the Announcement entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetAnnouncement(v *Announcement) *AnnouncementReadUpdateOne {
|
||||||
|
return _u.SetAnnouncementID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUser sets the "user" edge to the User entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SetUser(v *User) *AnnouncementReadUpdateOne {
|
||||||
|
return _u.SetUserID(v.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the AnnouncementReadMutation object of the builder.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) Mutation() *AnnouncementReadMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAnnouncement clears the "announcement" edge to the Announcement entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) ClearAnnouncement() *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.ClearAnnouncement()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUser clears the "user" edge to the User entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) ClearUser() *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.ClearUser()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the AnnouncementReadUpdate builder.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) Where(ps ...predicate.AnnouncementRead) *AnnouncementReadUpdateOne {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||||
|
// The default is selecting all fields defined in the entity schema.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) Select(field string, fields ...string) *AnnouncementReadUpdateOne {
|
||||||
|
_u.fields = append([]string{field}, fields...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the updated AnnouncementRead entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) Save(ctx context.Context) (*AnnouncementRead, error) {
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) SaveX(ctx context.Context) *AnnouncementRead {
|
||||||
|
node, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query on the entity.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _u.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *AnnouncementReadUpdateOne) check() error {
|
||||||
|
if _u.mutation.AnnouncementCleared() && len(_u.mutation.AnnouncementIDs()) > 0 {
|
||||||
|
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.announcement"`)
|
||||||
|
}
|
||||||
|
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
|
||||||
|
return errors.New(`ent: clearing a required unique edge "AnnouncementRead.user"`)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *AnnouncementReadUpdateOne) sqlSave(ctx context.Context) (_node *AnnouncementRead, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(announcementread.Table, announcementread.Columns, sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
|
if !ok {
|
||||||
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AnnouncementRead.id" for update`)}
|
||||||
|
}
|
||||||
|
_spec.Node.ID.Value = id
|
||||||
|
if fields := _u.fields; len(fields) > 0 {
|
||||||
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, announcementread.FieldID)
|
||||||
|
for _, f := range fields {
|
||||||
|
if !announcementread.ValidColumn(f) {
|
||||||
|
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
if f != announcementread.FieldID {
|
||||||
|
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
|
for i := range ps {
|
||||||
|
ps[i](selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ReadAt(); ok {
|
||||||
|
_spec.SetField(announcementread.FieldReadAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.AnnouncementCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.AnnouncementTable,
|
||||||
|
Columns: []string{announcementread.AnnouncementColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.AnnouncementIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.AnnouncementTable,
|
||||||
|
Columns: []string{announcementread.AnnouncementColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcement.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
if _u.mutation.UserCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.UserTable,
|
||||||
|
Columns: []string{announcementread.UserColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.M2O,
|
||||||
|
Inverse: true,
|
||||||
|
Table: announcementread.UserTable,
|
||||||
|
Columns: []string{announcementread.UserColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
|
_node = &AnnouncementRead{config: _u.config}
|
||||||
|
_spec.Assign = _node.assignValues
|
||||||
|
_spec.ScanValues = _node.scanValues
|
||||||
|
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{announcementread.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/account"
|
"github.com/Wei-Shaw/sub2api/ent/account"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||||
@@ -24,6 +26,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/user"
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||||
@@ -45,6 +48,10 @@ type Client struct {
|
|||||||
Account *AccountClient
|
Account *AccountClient
|
||||||
// AccountGroup is the client for interacting with the AccountGroup builders.
|
// AccountGroup is the client for interacting with the AccountGroup builders.
|
||||||
AccountGroup *AccountGroupClient
|
AccountGroup *AccountGroupClient
|
||||||
|
// Announcement is the client for interacting with the Announcement builders.
|
||||||
|
Announcement *AnnouncementClient
|
||||||
|
// AnnouncementRead is the client for interacting with the AnnouncementRead builders.
|
||||||
|
AnnouncementRead *AnnouncementReadClient
|
||||||
// Group is the client for interacting with the Group builders.
|
// Group is the client for interacting with the Group builders.
|
||||||
Group *GroupClient
|
Group *GroupClient
|
||||||
// PromoCode is the client for interacting with the PromoCode builders.
|
// PromoCode is the client for interacting with the PromoCode builders.
|
||||||
@@ -57,6 +64,8 @@ type Client struct {
|
|||||||
RedeemCode *RedeemCodeClient
|
RedeemCode *RedeemCodeClient
|
||||||
// Setting is the client for interacting with the Setting builders.
|
// Setting is the client for interacting with the Setting builders.
|
||||||
Setting *SettingClient
|
Setting *SettingClient
|
||||||
|
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||||
|
UsageCleanupTask *UsageCleanupTaskClient
|
||||||
// UsageLog is the client for interacting with the UsageLog builders.
|
// UsageLog is the client for interacting with the UsageLog builders.
|
||||||
UsageLog *UsageLogClient
|
UsageLog *UsageLogClient
|
||||||
// User is the client for interacting with the User builders.
|
// User is the client for interacting with the User builders.
|
||||||
@@ -83,12 +92,15 @@ func (c *Client) init() {
|
|||||||
c.APIKey = NewAPIKeyClient(c.config)
|
c.APIKey = NewAPIKeyClient(c.config)
|
||||||
c.Account = NewAccountClient(c.config)
|
c.Account = NewAccountClient(c.config)
|
||||||
c.AccountGroup = NewAccountGroupClient(c.config)
|
c.AccountGroup = NewAccountGroupClient(c.config)
|
||||||
|
c.Announcement = NewAnnouncementClient(c.config)
|
||||||
|
c.AnnouncementRead = NewAnnouncementReadClient(c.config)
|
||||||
c.Group = NewGroupClient(c.config)
|
c.Group = NewGroupClient(c.config)
|
||||||
c.PromoCode = NewPromoCodeClient(c.config)
|
c.PromoCode = NewPromoCodeClient(c.config)
|
||||||
c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
|
c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
|
||||||
c.Proxy = NewProxyClient(c.config)
|
c.Proxy = NewProxyClient(c.config)
|
||||||
c.RedeemCode = NewRedeemCodeClient(c.config)
|
c.RedeemCode = NewRedeemCodeClient(c.config)
|
||||||
c.Setting = NewSettingClient(c.config)
|
c.Setting = NewSettingClient(c.config)
|
||||||
|
c.UsageCleanupTask = NewUsageCleanupTaskClient(c.config)
|
||||||
c.UsageLog = NewUsageLogClient(c.config)
|
c.UsageLog = NewUsageLogClient(c.config)
|
||||||
c.User = NewUserClient(c.config)
|
c.User = NewUserClient(c.config)
|
||||||
c.UserAllowedGroup = NewUserAllowedGroupClient(c.config)
|
c.UserAllowedGroup = NewUserAllowedGroupClient(c.config)
|
||||||
@@ -190,12 +202,15 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
|||||||
APIKey: NewAPIKeyClient(cfg),
|
APIKey: NewAPIKeyClient(cfg),
|
||||||
Account: NewAccountClient(cfg),
|
Account: NewAccountClient(cfg),
|
||||||
AccountGroup: NewAccountGroupClient(cfg),
|
AccountGroup: NewAccountGroupClient(cfg),
|
||||||
|
Announcement: NewAnnouncementClient(cfg),
|
||||||
|
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||||
Group: NewGroupClient(cfg),
|
Group: NewGroupClient(cfg),
|
||||||
PromoCode: NewPromoCodeClient(cfg),
|
PromoCode: NewPromoCodeClient(cfg),
|
||||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||||
Proxy: NewProxyClient(cfg),
|
Proxy: NewProxyClient(cfg),
|
||||||
RedeemCode: NewRedeemCodeClient(cfg),
|
RedeemCode: NewRedeemCodeClient(cfg),
|
||||||
Setting: NewSettingClient(cfg),
|
Setting: NewSettingClient(cfg),
|
||||||
|
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||||
UsageLog: NewUsageLogClient(cfg),
|
UsageLog: NewUsageLogClient(cfg),
|
||||||
User: NewUserClient(cfg),
|
User: NewUserClient(cfg),
|
||||||
UserAllowedGroup: NewUserAllowedGroupClient(cfg),
|
UserAllowedGroup: NewUserAllowedGroupClient(cfg),
|
||||||
@@ -224,12 +239,15 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
|||||||
APIKey: NewAPIKeyClient(cfg),
|
APIKey: NewAPIKeyClient(cfg),
|
||||||
Account: NewAccountClient(cfg),
|
Account: NewAccountClient(cfg),
|
||||||
AccountGroup: NewAccountGroupClient(cfg),
|
AccountGroup: NewAccountGroupClient(cfg),
|
||||||
|
Announcement: NewAnnouncementClient(cfg),
|
||||||
|
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||||
Group: NewGroupClient(cfg),
|
Group: NewGroupClient(cfg),
|
||||||
PromoCode: NewPromoCodeClient(cfg),
|
PromoCode: NewPromoCodeClient(cfg),
|
||||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||||
Proxy: NewProxyClient(cfg),
|
Proxy: NewProxyClient(cfg),
|
||||||
RedeemCode: NewRedeemCodeClient(cfg),
|
RedeemCode: NewRedeemCodeClient(cfg),
|
||||||
Setting: NewSettingClient(cfg),
|
Setting: NewSettingClient(cfg),
|
||||||
|
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||||
UsageLog: NewUsageLogClient(cfg),
|
UsageLog: NewUsageLogClient(cfg),
|
||||||
User: NewUserClient(cfg),
|
User: NewUserClient(cfg),
|
||||||
UserAllowedGroup: NewUserAllowedGroupClient(cfg),
|
UserAllowedGroup: NewUserAllowedGroupClient(cfg),
|
||||||
@@ -265,8 +283,9 @@ func (c *Client) Close() error {
|
|||||||
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
|
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
|
||||||
func (c *Client) Use(hooks ...Hook) {
|
func (c *Client) Use(hooks ...Hook) {
|
||||||
for _, n := range []interface{ Use(...Hook) }{
|
for _, n := range []interface{ Use(...Hook) }{
|
||||||
c.APIKey, c.Account, c.AccountGroup, c.Group, c.PromoCode, c.PromoCodeUsage,
|
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||||
c.Proxy, c.RedeemCode, c.Setting, c.UsageLog, c.User, c.UserAllowedGroup,
|
c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.Setting,
|
||||||
|
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
||||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
||||||
} {
|
} {
|
||||||
n.Use(hooks...)
|
n.Use(hooks...)
|
||||||
@@ -277,8 +296,9 @@ func (c *Client) Use(hooks ...Hook) {
|
|||||||
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
|
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
|
||||||
func (c *Client) Intercept(interceptors ...Interceptor) {
|
func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||||
for _, n := range []interface{ Intercept(...Interceptor) }{
|
for _, n := range []interface{ Intercept(...Interceptor) }{
|
||||||
c.APIKey, c.Account, c.AccountGroup, c.Group, c.PromoCode, c.PromoCodeUsage,
|
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||||
c.Proxy, c.RedeemCode, c.Setting, c.UsageLog, c.User, c.UserAllowedGroup,
|
c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.Setting,
|
||||||
|
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
||||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
||||||
} {
|
} {
|
||||||
n.Intercept(interceptors...)
|
n.Intercept(interceptors...)
|
||||||
@@ -294,6 +314,10 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
|||||||
return c.Account.mutate(ctx, m)
|
return c.Account.mutate(ctx, m)
|
||||||
case *AccountGroupMutation:
|
case *AccountGroupMutation:
|
||||||
return c.AccountGroup.mutate(ctx, m)
|
return c.AccountGroup.mutate(ctx, m)
|
||||||
|
case *AnnouncementMutation:
|
||||||
|
return c.Announcement.mutate(ctx, m)
|
||||||
|
case *AnnouncementReadMutation:
|
||||||
|
return c.AnnouncementRead.mutate(ctx, m)
|
||||||
case *GroupMutation:
|
case *GroupMutation:
|
||||||
return c.Group.mutate(ctx, m)
|
return c.Group.mutate(ctx, m)
|
||||||
case *PromoCodeMutation:
|
case *PromoCodeMutation:
|
||||||
@@ -306,6 +330,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
|||||||
return c.RedeemCode.mutate(ctx, m)
|
return c.RedeemCode.mutate(ctx, m)
|
||||||
case *SettingMutation:
|
case *SettingMutation:
|
||||||
return c.Setting.mutate(ctx, m)
|
return c.Setting.mutate(ctx, m)
|
||||||
|
case *UsageCleanupTaskMutation:
|
||||||
|
return c.UsageCleanupTask.mutate(ctx, m)
|
||||||
case *UsageLogMutation:
|
case *UsageLogMutation:
|
||||||
return c.UsageLog.mutate(ctx, m)
|
return c.UsageLog.mutate(ctx, m)
|
||||||
case *UserMutation:
|
case *UserMutation:
|
||||||
@@ -821,6 +847,320 @@ func (c *AccountGroupClient) mutate(ctx context.Context, m *AccountGroupMutation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnouncementClient is a client for the Announcement schema.
|
||||||
|
type AnnouncementClient struct {
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnnouncementClient returns a client for the Announcement from the given config.
|
||||||
|
func NewAnnouncementClient(c config) *AnnouncementClient {
|
||||||
|
return &AnnouncementClient{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds a list of mutation hooks to the hooks stack.
|
||||||
|
// A call to `Use(f, g, h)` equals to `announcement.Hooks(f(g(h())))`.
|
||||||
|
func (c *AnnouncementClient) Use(hooks ...Hook) {
|
||||||
|
c.hooks.Announcement = append(c.hooks.Announcement, hooks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `announcement.Intercept(f(g(h())))`.
|
||||||
|
func (c *AnnouncementClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.Announcement = append(c.inters.Announcement, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a Announcement entity.
|
||||||
|
func (c *AnnouncementClient) Create() *AnnouncementCreate {
|
||||||
|
mutation := newAnnouncementMutation(c.config, OpCreate)
|
||||||
|
return &AnnouncementCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBulk returns a builder for creating a bulk of Announcement entities.
|
||||||
|
func (c *AnnouncementClient) CreateBulk(builders ...*AnnouncementCreate) *AnnouncementCreateBulk {
|
||||||
|
return &AnnouncementCreateBulk{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 *AnnouncementClient) MapCreateBulk(slice any, setFunc func(*AnnouncementCreate, int)) *AnnouncementCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &AnnouncementCreateBulk{err: fmt.Errorf("calling to AnnouncementClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*AnnouncementCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &AnnouncementCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns an update builder for Announcement.
|
||||||
|
func (c *AnnouncementClient) Update() *AnnouncementUpdate {
|
||||||
|
mutation := newAnnouncementMutation(c.config, OpUpdate)
|
||||||
|
return &AnnouncementUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOne returns an update builder for the given entity.
|
||||||
|
func (c *AnnouncementClient) UpdateOne(_m *Announcement) *AnnouncementUpdateOne {
|
||||||
|
mutation := newAnnouncementMutation(c.config, OpUpdateOne, withAnnouncement(_m))
|
||||||
|
return &AnnouncementUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOneID returns an update builder for the given id.
|
||||||
|
func (c *AnnouncementClient) UpdateOneID(id int64) *AnnouncementUpdateOne {
|
||||||
|
mutation := newAnnouncementMutation(c.config, OpUpdateOne, withAnnouncementID(id))
|
||||||
|
return &AnnouncementUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns a delete builder for Announcement.
|
||||||
|
func (c *AnnouncementClient) Delete() *AnnouncementDelete {
|
||||||
|
mutation := newAnnouncementMutation(c.config, OpDelete)
|
||||||
|
return &AnnouncementDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
|
func (c *AnnouncementClient) DeleteOne(_m *Announcement) *AnnouncementDeleteOne {
|
||||||
|
return c.DeleteOneID(_m.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
|
func (c *AnnouncementClient) DeleteOneID(id int64) *AnnouncementDeleteOne {
|
||||||
|
builder := c.Delete().Where(announcement.ID(id))
|
||||||
|
builder.mutation.id = &id
|
||||||
|
builder.mutation.op = OpDeleteOne
|
||||||
|
return &AnnouncementDeleteOne{builder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns a query builder for Announcement.
|
||||||
|
func (c *AnnouncementClient) Query() *AnnouncementQuery {
|
||||||
|
return &AnnouncementQuery{
|
||||||
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypeAnnouncement},
|
||||||
|
inters: c.Interceptors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a Announcement entity by its id.
|
||||||
|
func (c *AnnouncementClient) Get(ctx context.Context, id int64) (*Announcement, error) {
|
||||||
|
return c.Query().Where(announcement.ID(id)).Only(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetX is like Get, but panics if an error occurs.
|
||||||
|
func (c *AnnouncementClient) GetX(ctx context.Context, id int64) *Announcement {
|
||||||
|
obj, err := c.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryReads queries the reads edge of a Announcement.
|
||||||
|
func (c *AnnouncementClient) QueryReads(_m *Announcement) *AnnouncementReadQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: c.config}).Query()
|
||||||
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
|
id := _m.ID
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcement.Table, announcement.FieldID, id),
|
||||||
|
sqlgraph.To(announcementread.Table, announcementread.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, announcement.ReadsTable, announcement.ReadsColumn),
|
||||||
|
)
|
||||||
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
|
return fromV, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks returns the client hooks.
|
||||||
|
func (c *AnnouncementClient) Hooks() []Hook {
|
||||||
|
return c.hooks.Announcement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *AnnouncementClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.Announcement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AnnouncementClient) mutate(ctx context.Context, m *AnnouncementMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&AnnouncementCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&AnnouncementUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&AnnouncementUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&AnnouncementDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown Announcement mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadClient is a client for the AnnouncementRead schema.
|
||||||
|
type AnnouncementReadClient struct {
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnnouncementReadClient returns a client for the AnnouncementRead from the given config.
|
||||||
|
func NewAnnouncementReadClient(c config) *AnnouncementReadClient {
|
||||||
|
return &AnnouncementReadClient{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds a list of mutation hooks to the hooks stack.
|
||||||
|
// A call to `Use(f, g, h)` equals to `announcementread.Hooks(f(g(h())))`.
|
||||||
|
func (c *AnnouncementReadClient) Use(hooks ...Hook) {
|
||||||
|
c.hooks.AnnouncementRead = append(c.hooks.AnnouncementRead, hooks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `announcementread.Intercept(f(g(h())))`.
|
||||||
|
func (c *AnnouncementReadClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.AnnouncementRead = append(c.inters.AnnouncementRead, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a AnnouncementRead entity.
|
||||||
|
func (c *AnnouncementReadClient) Create() *AnnouncementReadCreate {
|
||||||
|
mutation := newAnnouncementReadMutation(c.config, OpCreate)
|
||||||
|
return &AnnouncementReadCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBulk returns a builder for creating a bulk of AnnouncementRead entities.
|
||||||
|
func (c *AnnouncementReadClient) CreateBulk(builders ...*AnnouncementReadCreate) *AnnouncementReadCreateBulk {
|
||||||
|
return &AnnouncementReadCreateBulk{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 *AnnouncementReadClient) MapCreateBulk(slice any, setFunc func(*AnnouncementReadCreate, int)) *AnnouncementReadCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &AnnouncementReadCreateBulk{err: fmt.Errorf("calling to AnnouncementReadClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*AnnouncementReadCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &AnnouncementReadCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns an update builder for AnnouncementRead.
|
||||||
|
func (c *AnnouncementReadClient) Update() *AnnouncementReadUpdate {
|
||||||
|
mutation := newAnnouncementReadMutation(c.config, OpUpdate)
|
||||||
|
return &AnnouncementReadUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOne returns an update builder for the given entity.
|
||||||
|
func (c *AnnouncementReadClient) UpdateOne(_m *AnnouncementRead) *AnnouncementReadUpdateOne {
|
||||||
|
mutation := newAnnouncementReadMutation(c.config, OpUpdateOne, withAnnouncementRead(_m))
|
||||||
|
return &AnnouncementReadUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOneID returns an update builder for the given id.
|
||||||
|
func (c *AnnouncementReadClient) UpdateOneID(id int64) *AnnouncementReadUpdateOne {
|
||||||
|
mutation := newAnnouncementReadMutation(c.config, OpUpdateOne, withAnnouncementReadID(id))
|
||||||
|
return &AnnouncementReadUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns a delete builder for AnnouncementRead.
|
||||||
|
func (c *AnnouncementReadClient) Delete() *AnnouncementReadDelete {
|
||||||
|
mutation := newAnnouncementReadMutation(c.config, OpDelete)
|
||||||
|
return &AnnouncementReadDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
|
func (c *AnnouncementReadClient) DeleteOne(_m *AnnouncementRead) *AnnouncementReadDeleteOne {
|
||||||
|
return c.DeleteOneID(_m.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
|
func (c *AnnouncementReadClient) DeleteOneID(id int64) *AnnouncementReadDeleteOne {
|
||||||
|
builder := c.Delete().Where(announcementread.ID(id))
|
||||||
|
builder.mutation.id = &id
|
||||||
|
builder.mutation.op = OpDeleteOne
|
||||||
|
return &AnnouncementReadDeleteOne{builder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns a query builder for AnnouncementRead.
|
||||||
|
func (c *AnnouncementReadClient) Query() *AnnouncementReadQuery {
|
||||||
|
return &AnnouncementReadQuery{
|
||||||
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypeAnnouncementRead},
|
||||||
|
inters: c.Interceptors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a AnnouncementRead entity by its id.
|
||||||
|
func (c *AnnouncementReadClient) Get(ctx context.Context, id int64) (*AnnouncementRead, error) {
|
||||||
|
return c.Query().Where(announcementread.ID(id)).Only(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetX is like Get, but panics if an error occurs.
|
||||||
|
func (c *AnnouncementReadClient) GetX(ctx context.Context, id int64) *AnnouncementRead {
|
||||||
|
obj, err := c.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncement queries the announcement edge of a AnnouncementRead.
|
||||||
|
func (c *AnnouncementReadClient) QueryAnnouncement(_m *AnnouncementRead) *AnnouncementQuery {
|
||||||
|
query := (&AnnouncementClient{config: c.config}).Query()
|
||||||
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
|
id := _m.ID
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcementread.Table, announcementread.FieldID, id),
|
||||||
|
sqlgraph.To(announcement.Table, announcement.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.AnnouncementTable, announcementread.AnnouncementColumn),
|
||||||
|
)
|
||||||
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
|
return fromV, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryUser queries the user edge of a AnnouncementRead.
|
||||||
|
func (c *AnnouncementReadClient) QueryUser(_m *AnnouncementRead) *UserQuery {
|
||||||
|
query := (&UserClient{config: c.config}).Query()
|
||||||
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
|
id := _m.ID
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(announcementread.Table, announcementread.FieldID, id),
|
||||||
|
sqlgraph.To(user.Table, user.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, true, announcementread.UserTable, announcementread.UserColumn),
|
||||||
|
)
|
||||||
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
|
return fromV, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks returns the client hooks.
|
||||||
|
func (c *AnnouncementReadClient) Hooks() []Hook {
|
||||||
|
return c.hooks.AnnouncementRead
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *AnnouncementReadClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.AnnouncementRead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AnnouncementReadClient) mutate(ctx context.Context, m *AnnouncementReadMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&AnnouncementReadCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&AnnouncementReadUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&AnnouncementReadUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&AnnouncementReadDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown AnnouncementRead mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GroupClient is a client for the Group schema.
|
// GroupClient is a client for the Group schema.
|
||||||
type GroupClient struct {
|
type GroupClient struct {
|
||||||
config
|
config
|
||||||
@@ -1847,6 +2187,139 @@ func (c *SettingClient) mutate(ctx context.Context, m *SettingMutation) (Value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTaskClient is a client for the UsageCleanupTask schema.
|
||||||
|
type UsageCleanupTaskClient struct {
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUsageCleanupTaskClient returns a client for the UsageCleanupTask from the given config.
|
||||||
|
func NewUsageCleanupTaskClient(c config) *UsageCleanupTaskClient {
|
||||||
|
return &UsageCleanupTaskClient{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds a list of mutation hooks to the hooks stack.
|
||||||
|
// A call to `Use(f, g, h)` equals to `usagecleanuptask.Hooks(f(g(h())))`.
|
||||||
|
func (c *UsageCleanupTaskClient) Use(hooks ...Hook) {
|
||||||
|
c.hooks.UsageCleanupTask = append(c.hooks.UsageCleanupTask, hooks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `usagecleanuptask.Intercept(f(g(h())))`.
|
||||||
|
func (c *UsageCleanupTaskClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.UsageCleanupTask = append(c.inters.UsageCleanupTask, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a UsageCleanupTask entity.
|
||||||
|
func (c *UsageCleanupTaskClient) Create() *UsageCleanupTaskCreate {
|
||||||
|
mutation := newUsageCleanupTaskMutation(c.config, OpCreate)
|
||||||
|
return &UsageCleanupTaskCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBulk returns a builder for creating a bulk of UsageCleanupTask entities.
|
||||||
|
func (c *UsageCleanupTaskClient) CreateBulk(builders ...*UsageCleanupTaskCreate) *UsageCleanupTaskCreateBulk {
|
||||||
|
return &UsageCleanupTaskCreateBulk{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 *UsageCleanupTaskClient) MapCreateBulk(slice any, setFunc func(*UsageCleanupTaskCreate, int)) *UsageCleanupTaskCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &UsageCleanupTaskCreateBulk{err: fmt.Errorf("calling to UsageCleanupTaskClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*UsageCleanupTaskCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &UsageCleanupTaskCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns an update builder for UsageCleanupTask.
|
||||||
|
func (c *UsageCleanupTaskClient) Update() *UsageCleanupTaskUpdate {
|
||||||
|
mutation := newUsageCleanupTaskMutation(c.config, OpUpdate)
|
||||||
|
return &UsageCleanupTaskUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOne returns an update builder for the given entity.
|
||||||
|
func (c *UsageCleanupTaskClient) UpdateOne(_m *UsageCleanupTask) *UsageCleanupTaskUpdateOne {
|
||||||
|
mutation := newUsageCleanupTaskMutation(c.config, OpUpdateOne, withUsageCleanupTask(_m))
|
||||||
|
return &UsageCleanupTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOneID returns an update builder for the given id.
|
||||||
|
func (c *UsageCleanupTaskClient) UpdateOneID(id int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
mutation := newUsageCleanupTaskMutation(c.config, OpUpdateOne, withUsageCleanupTaskID(id))
|
||||||
|
return &UsageCleanupTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns a delete builder for UsageCleanupTask.
|
||||||
|
func (c *UsageCleanupTaskClient) Delete() *UsageCleanupTaskDelete {
|
||||||
|
mutation := newUsageCleanupTaskMutation(c.config, OpDelete)
|
||||||
|
return &UsageCleanupTaskDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
|
func (c *UsageCleanupTaskClient) DeleteOne(_m *UsageCleanupTask) *UsageCleanupTaskDeleteOne {
|
||||||
|
return c.DeleteOneID(_m.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
|
func (c *UsageCleanupTaskClient) DeleteOneID(id int64) *UsageCleanupTaskDeleteOne {
|
||||||
|
builder := c.Delete().Where(usagecleanuptask.ID(id))
|
||||||
|
builder.mutation.id = &id
|
||||||
|
builder.mutation.op = OpDeleteOne
|
||||||
|
return &UsageCleanupTaskDeleteOne{builder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns a query builder for UsageCleanupTask.
|
||||||
|
func (c *UsageCleanupTaskClient) Query() *UsageCleanupTaskQuery {
|
||||||
|
return &UsageCleanupTaskQuery{
|
||||||
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypeUsageCleanupTask},
|
||||||
|
inters: c.Interceptors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a UsageCleanupTask entity by its id.
|
||||||
|
func (c *UsageCleanupTaskClient) Get(ctx context.Context, id int64) (*UsageCleanupTask, error) {
|
||||||
|
return c.Query().Where(usagecleanuptask.ID(id)).Only(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetX is like Get, but panics if an error occurs.
|
||||||
|
func (c *UsageCleanupTaskClient) GetX(ctx context.Context, id int64) *UsageCleanupTask {
|
||||||
|
obj, err := c.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks returns the client hooks.
|
||||||
|
func (c *UsageCleanupTaskClient) Hooks() []Hook {
|
||||||
|
return c.hooks.UsageCleanupTask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *UsageCleanupTaskClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.UsageCleanupTask
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UsageCleanupTaskClient) mutate(ctx context.Context, m *UsageCleanupTaskMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&UsageCleanupTaskCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&UsageCleanupTaskUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&UsageCleanupTaskUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&UsageCleanupTaskDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown UsageCleanupTask mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UsageLogClient is a client for the UsageLog schema.
|
// UsageLogClient is a client for the UsageLog schema.
|
||||||
type UsageLogClient struct {
|
type UsageLogClient struct {
|
||||||
config
|
config
|
||||||
@@ -2232,6 +2705,22 @@ func (c *UserClient) QueryAssignedSubscriptions(_m *User) *UserSubscriptionQuery
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncementReads queries the announcement_reads edge of a User.
|
||||||
|
func (c *UserClient) QueryAnnouncementReads(_m *User) *AnnouncementReadQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: c.config}).Query()
|
||||||
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
|
id := _m.ID
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(user.Table, user.FieldID, id),
|
||||||
|
sqlgraph.To(announcementread.Table, announcementread.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, user.AnnouncementReadsTable, user.AnnouncementReadsColumn),
|
||||||
|
)
|
||||||
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
|
return fromV, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
// QueryAllowedGroups queries the allowed_groups edge of a User.
|
// QueryAllowedGroups queries the allowed_groups edge of a User.
|
||||||
func (c *UserClient) QueryAllowedGroups(_m *User) *GroupQuery {
|
func (c *UserClient) QueryAllowedGroups(_m *User) *GroupQuery {
|
||||||
query := (&GroupClient{config: c.config}).Query()
|
query := (&GroupClient{config: c.config}).Query()
|
||||||
@@ -2973,14 +3462,16 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
|
|||||||
// hooks and interceptors per client, for fast access.
|
// hooks and interceptors per client, for fast access.
|
||||||
type (
|
type (
|
||||||
hooks struct {
|
hooks struct {
|
||||||
APIKey, Account, AccountGroup, Group, PromoCode, PromoCodeUsage, Proxy,
|
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, Group, PromoCode,
|
||||||
RedeemCode, Setting, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
PromoCodeUsage, Proxy, RedeemCode, Setting, UsageCleanupTask, UsageLog, User,
|
||||||
UserAttributeValue, UserSubscription []ent.Hook
|
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||||
|
UserSubscription []ent.Hook
|
||||||
}
|
}
|
||||||
inters struct {
|
inters struct {
|
||||||
APIKey, Account, AccountGroup, Group, PromoCode, PromoCodeUsage, Proxy,
|
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, Group, PromoCode,
|
||||||
RedeemCode, Setting, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
PromoCodeUsage, Proxy, RedeemCode, Setting, UsageCleanupTask, UsageLog, User,
|
||||||
UserAttributeValue, UserSubscription []ent.Interceptor
|
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||||
|
UserSubscription []ent.Interceptor
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/account"
|
"github.com/Wei-Shaw/sub2api/ent/account"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||||
@@ -21,6 +23,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/user"
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||||
@@ -90,12 +93,15 @@ func checkColumn(t, c string) error {
|
|||||||
apikey.Table: apikey.ValidColumn,
|
apikey.Table: apikey.ValidColumn,
|
||||||
account.Table: account.ValidColumn,
|
account.Table: account.ValidColumn,
|
||||||
accountgroup.Table: accountgroup.ValidColumn,
|
accountgroup.Table: accountgroup.ValidColumn,
|
||||||
|
announcement.Table: announcement.ValidColumn,
|
||||||
|
announcementread.Table: announcementread.ValidColumn,
|
||||||
group.Table: group.ValidColumn,
|
group.Table: group.ValidColumn,
|
||||||
promocode.Table: promocode.ValidColumn,
|
promocode.Table: promocode.ValidColumn,
|
||||||
promocodeusage.Table: promocodeusage.ValidColumn,
|
promocodeusage.Table: promocodeusage.ValidColumn,
|
||||||
proxy.Table: proxy.ValidColumn,
|
proxy.Table: proxy.ValidColumn,
|
||||||
redeemcode.Table: redeemcode.ValidColumn,
|
redeemcode.Table: redeemcode.ValidColumn,
|
||||||
setting.Table: setting.ValidColumn,
|
setting.Table: setting.ValidColumn,
|
||||||
|
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
|
||||||
usagelog.Table: usagelog.ValidColumn,
|
usagelog.Table: usagelog.ValidColumn,
|
||||||
user.Table: user.ValidColumn,
|
user.Table: user.ValidColumn,
|
||||||
userallowedgroup.Table: userallowedgroup.ValidColumn,
|
userallowedgroup.Table: userallowedgroup.ValidColumn,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -55,6 +56,10 @@ type Group struct {
|
|||||||
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
|
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
|
||||||
// 非 Claude Code 请求降级使用的分组 ID
|
// 非 Claude Code 请求降级使用的分组 ID
|
||||||
FallbackGroupID *int64 `json:"fallback_group_id,omitempty"`
|
FallbackGroupID *int64 `json:"fallback_group_id,omitempty"`
|
||||||
|
// 模型路由配置:模型模式 -> 优先账号ID列表
|
||||||
|
ModelRouting map[string][]int64 `json:"model_routing,omitempty"`
|
||||||
|
// 是否启用模型路由配置
|
||||||
|
ModelRoutingEnabled bool `json:"model_routing_enabled,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
// The values are being populated by the GroupQuery when eager-loading is set.
|
// The values are being populated by the GroupQuery when eager-loading is set.
|
||||||
Edges GroupEdges `json:"edges"`
|
Edges GroupEdges `json:"edges"`
|
||||||
@@ -161,7 +166,9 @@ func (*Group) scanValues(columns []string) ([]any, error) {
|
|||||||
values := make([]any, len(columns))
|
values := make([]any, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
case group.FieldIsExclusive, group.FieldClaudeCodeOnly:
|
case group.FieldModelRouting:
|
||||||
|
values[i] = new([]byte)
|
||||||
|
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k:
|
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
@@ -315,6 +322,20 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
|||||||
_m.FallbackGroupID = new(int64)
|
_m.FallbackGroupID = new(int64)
|
||||||
*_m.FallbackGroupID = value.Int64
|
*_m.FallbackGroupID = value.Int64
|
||||||
}
|
}
|
||||||
|
case group.FieldModelRouting:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field model_routing", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.ModelRouting); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field model_routing: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case group.FieldModelRoutingEnabled:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field model_routing_enabled", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.ModelRoutingEnabled = value.Bool
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
_m.selectValues.Set(columns[i], values[i])
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
@@ -465,6 +486,12 @@ func (_m *Group) String() string {
|
|||||||
builder.WriteString("fallback_group_id=")
|
builder.WriteString("fallback_group_id=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
}
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("model_routing=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.ModelRouting))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("model_routing_enabled=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.ModelRoutingEnabled))
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ const (
|
|||||||
FieldClaudeCodeOnly = "claude_code_only"
|
FieldClaudeCodeOnly = "claude_code_only"
|
||||||
// FieldFallbackGroupID holds the string denoting the fallback_group_id field in the database.
|
// FieldFallbackGroupID holds the string denoting the fallback_group_id field in the database.
|
||||||
FieldFallbackGroupID = "fallback_group_id"
|
FieldFallbackGroupID = "fallback_group_id"
|
||||||
|
// FieldModelRouting holds the string denoting the model_routing field in the database.
|
||||||
|
FieldModelRouting = "model_routing"
|
||||||
|
// FieldModelRoutingEnabled holds the string denoting the model_routing_enabled field in the database.
|
||||||
|
FieldModelRoutingEnabled = "model_routing_enabled"
|
||||||
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
||||||
EdgeAPIKeys = "api_keys"
|
EdgeAPIKeys = "api_keys"
|
||||||
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
||||||
@@ -147,6 +151,8 @@ var Columns = []string{
|
|||||||
FieldImagePrice4k,
|
FieldImagePrice4k,
|
||||||
FieldClaudeCodeOnly,
|
FieldClaudeCodeOnly,
|
||||||
FieldFallbackGroupID,
|
FieldFallbackGroupID,
|
||||||
|
FieldModelRouting,
|
||||||
|
FieldModelRoutingEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -204,6 +210,8 @@ var (
|
|||||||
DefaultDefaultValidityDays int
|
DefaultDefaultValidityDays int
|
||||||
// DefaultClaudeCodeOnly holds the default value on creation for the "claude_code_only" field.
|
// DefaultClaudeCodeOnly holds the default value on creation for the "claude_code_only" field.
|
||||||
DefaultClaudeCodeOnly bool
|
DefaultClaudeCodeOnly bool
|
||||||
|
// DefaultModelRoutingEnabled holds the default value on creation for the "model_routing_enabled" field.
|
||||||
|
DefaultModelRoutingEnabled bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderOption defines the ordering options for the Group queries.
|
// OrderOption defines the ordering options for the Group queries.
|
||||||
@@ -309,6 +317,11 @@ func ByFallbackGroupID(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldFallbackGroupID, opts...).ToFunc()
|
return sql.OrderByField(FieldFallbackGroupID, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByModelRoutingEnabled orders the results by the model_routing_enabled field.
|
||||||
|
func ByModelRoutingEnabled(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldModelRoutingEnabled, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByAPIKeysCount orders the results by api_keys count.
|
// ByAPIKeysCount orders the results by api_keys count.
|
||||||
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
|
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return func(s *sql.Selector) {
|
return func(s *sql.Selector) {
|
||||||
|
|||||||
@@ -150,6 +150,11 @@ func FallbackGroupID(v int64) predicate.Group {
|
|||||||
return predicate.Group(sql.FieldEQ(FieldFallbackGroupID, v))
|
return predicate.Group(sql.FieldEQ(FieldFallbackGroupID, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ModelRoutingEnabled applies equality check predicate on the "model_routing_enabled" field. It's identical to ModelRoutingEnabledEQ.
|
||||||
|
func ModelRoutingEnabled(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldModelRoutingEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
func CreatedAtEQ(v time.Time) predicate.Group {
|
func CreatedAtEQ(v time.Time) predicate.Group {
|
||||||
return predicate.Group(sql.FieldEQ(FieldCreatedAt, v))
|
return predicate.Group(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
@@ -1065,6 +1070,26 @@ func FallbackGroupIDNotNil() predicate.Group {
|
|||||||
return predicate.Group(sql.FieldNotNull(FieldFallbackGroupID))
|
return predicate.Group(sql.FieldNotNull(FieldFallbackGroupID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ModelRoutingIsNil applies the IsNil predicate on the "model_routing" field.
|
||||||
|
func ModelRoutingIsNil() predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldIsNull(FieldModelRouting))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelRoutingNotNil applies the NotNil predicate on the "model_routing" field.
|
||||||
|
func ModelRoutingNotNil() predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNotNull(FieldModelRouting))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelRoutingEnabledEQ applies the EQ predicate on the "model_routing_enabled" field.
|
||||||
|
func ModelRoutingEnabledEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldModelRoutingEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelRoutingEnabledNEQ applies the NEQ predicate on the "model_routing_enabled" field.
|
||||||
|
func ModelRoutingEnabledNEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNEQ(FieldModelRoutingEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
||||||
func HasAPIKeys() predicate.Group {
|
func HasAPIKeys() predicate.Group {
|
||||||
return predicate.Group(func(s *sql.Selector) {
|
return predicate.Group(func(s *sql.Selector) {
|
||||||
|
|||||||
@@ -286,6 +286,26 @@ func (_c *GroupCreate) SetNillableFallbackGroupID(v *int64) *GroupCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (_c *GroupCreate) SetModelRouting(v map[string][]int64) *GroupCreate {
|
||||||
|
_c.mutation.SetModelRouting(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (_c *GroupCreate) SetModelRoutingEnabled(v bool) *GroupCreate {
|
||||||
|
_c.mutation.SetModelRoutingEnabled(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelRoutingEnabled sets the "model_routing_enabled" field if the given value is not nil.
|
||||||
|
func (_c *GroupCreate) SetNillableModelRoutingEnabled(v *bool) *GroupCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetModelRoutingEnabled(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
|
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
|
||||||
_c.mutation.AddAPIKeyIDs(ids...)
|
_c.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -455,6 +475,10 @@ func (_c *GroupCreate) defaults() error {
|
|||||||
v := group.DefaultClaudeCodeOnly
|
v := group.DefaultClaudeCodeOnly
|
||||||
_c.mutation.SetClaudeCodeOnly(v)
|
_c.mutation.SetClaudeCodeOnly(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.ModelRoutingEnabled(); !ok {
|
||||||
|
v := group.DefaultModelRoutingEnabled
|
||||||
|
_c.mutation.SetModelRoutingEnabled(v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,6 +534,9 @@ func (_c *GroupCreate) check() error {
|
|||||||
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
||||||
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
|
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.ModelRoutingEnabled(); !ok {
|
||||||
|
return &ValidationError{Name: "model_routing_enabled", err: errors.New(`ent: missing required field "Group.model_routing_enabled"`)}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,6 +640,14 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(group.FieldFallbackGroupID, field.TypeInt64, value)
|
_spec.SetField(group.FieldFallbackGroupID, field.TypeInt64, value)
|
||||||
_node.FallbackGroupID = &value
|
_node.FallbackGroupID = &value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.ModelRouting(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRouting, field.TypeJSON, value)
|
||||||
|
_node.ModelRouting = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.ModelRoutingEnabled(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRoutingEnabled, field.TypeBool, value)
|
||||||
|
_node.ModelRoutingEnabled = value
|
||||||
|
}
|
||||||
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -1093,6 +1128,36 @@ func (u *GroupUpsert) ClearFallbackGroupID() *GroupUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (u *GroupUpsert) SetModelRouting(v map[string][]int64) *GroupUpsert {
|
||||||
|
u.Set(group.FieldModelRouting, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRouting sets the "model_routing" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateModelRouting() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldModelRouting)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelRouting clears the value of the "model_routing" field.
|
||||||
|
func (u *GroupUpsert) ClearModelRouting() *GroupUpsert {
|
||||||
|
u.SetNull(group.FieldModelRouting)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (u *GroupUpsert) SetModelRoutingEnabled(v bool) *GroupUpsert {
|
||||||
|
u.Set(group.FieldModelRoutingEnabled, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRoutingEnabled sets the "model_routing_enabled" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateModelRoutingEnabled() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldModelRoutingEnabled)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||||
// Using this option is equivalent to using:
|
// Using this option is equivalent to using:
|
||||||
//
|
//
|
||||||
@@ -1516,6 +1581,41 @@ func (u *GroupUpsertOne) ClearFallbackGroupID() *GroupUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (u *GroupUpsertOne) SetModelRouting(v map[string][]int64) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetModelRouting(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRouting sets the "model_routing" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateModelRouting() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateModelRouting()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelRouting clears the value of the "model_routing" field.
|
||||||
|
func (u *GroupUpsertOne) ClearModelRouting() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.ClearModelRouting()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (u *GroupUpsertOne) SetModelRoutingEnabled(v bool) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetModelRoutingEnabled(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRoutingEnabled sets the "model_routing_enabled" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateModelRoutingEnabled() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateModelRoutingEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
|
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
|
||||||
if len(u.create.conflict) == 0 {
|
if len(u.create.conflict) == 0 {
|
||||||
@@ -2105,6 +2205,41 @@ func (u *GroupUpsertBulk) ClearFallbackGroupID() *GroupUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (u *GroupUpsertBulk) SetModelRouting(v map[string][]int64) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetModelRouting(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRouting sets the "model_routing" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateModelRouting() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateModelRouting()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelRouting clears the value of the "model_routing" field.
|
||||||
|
func (u *GroupUpsertBulk) ClearModelRouting() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.ClearModelRouting()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (u *GroupUpsertBulk) SetModelRoutingEnabled(v bool) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetModelRoutingEnabled(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelRoutingEnabled sets the "model_routing_enabled" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateModelRoutingEnabled() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateModelRoutingEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
|
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
|
||||||
if u.create.err != nil {
|
if u.create.err != nil {
|
||||||
|
|||||||
@@ -395,6 +395,32 @@ func (_u *GroupUpdate) ClearFallbackGroupID() *GroupUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (_u *GroupUpdate) SetModelRouting(v map[string][]int64) *GroupUpdate {
|
||||||
|
_u.mutation.SetModelRouting(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelRouting clears the value of the "model_routing" field.
|
||||||
|
func (_u *GroupUpdate) ClearModelRouting() *GroupUpdate {
|
||||||
|
_u.mutation.ClearModelRouting()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (_u *GroupUpdate) SetModelRoutingEnabled(v bool) *GroupUpdate {
|
||||||
|
_u.mutation.SetModelRoutingEnabled(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelRoutingEnabled sets the "model_routing_enabled" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdate) SetNillableModelRoutingEnabled(v *bool) *GroupUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetModelRoutingEnabled(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
|
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -803,6 +829,15 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if _u.mutation.FallbackGroupIDCleared() {
|
if _u.mutation.FallbackGroupIDCleared() {
|
||||||
_spec.ClearField(group.FieldFallbackGroupID, field.TypeInt64)
|
_spec.ClearField(group.FieldFallbackGroupID, field.TypeInt64)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.ModelRouting(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRouting, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ModelRoutingCleared() {
|
||||||
|
_spec.ClearField(group.FieldModelRouting, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ModelRoutingEnabled(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRoutingEnabled, field.TypeBool, value)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -1478,6 +1513,32 @@ func (_u *GroupUpdateOne) ClearFallbackGroupID() *GroupUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetModelRouting sets the "model_routing" field.
|
||||||
|
func (_u *GroupUpdateOne) SetModelRouting(v map[string][]int64) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetModelRouting(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelRouting clears the value of the "model_routing" field.
|
||||||
|
func (_u *GroupUpdateOne) ClearModelRouting() *GroupUpdateOne {
|
||||||
|
_u.mutation.ClearModelRouting()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelRoutingEnabled sets the "model_routing_enabled" field.
|
||||||
|
func (_u *GroupUpdateOne) SetModelRoutingEnabled(v bool) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetModelRoutingEnabled(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelRoutingEnabled sets the "model_routing_enabled" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdateOne) SetNillableModelRoutingEnabled(v *bool) *GroupUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetModelRoutingEnabled(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
|
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -1916,6 +1977,15 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
|||||||
if _u.mutation.FallbackGroupIDCleared() {
|
if _u.mutation.FallbackGroupIDCleared() {
|
||||||
_spec.ClearField(group.FieldFallbackGroupID, field.TypeInt64)
|
_spec.ClearField(group.FieldFallbackGroupID, field.TypeInt64)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.ModelRouting(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRouting, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ModelRoutingCleared() {
|
||||||
|
_spec.ClearField(group.FieldModelRouting, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ModelRoutingEnabled(); ok {
|
||||||
|
_spec.SetField(group.FieldModelRoutingEnabled, field.TypeBool, value)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
|
|||||||
@@ -45,6 +45,30 @@ func (f AccountGroupFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value
|
|||||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AccountGroupMutation", m)
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AccountGroupMutation", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The AnnouncementFunc type is an adapter to allow the use of ordinary
|
||||||
|
// function as Announcement mutator.
|
||||||
|
type AnnouncementFunc func(context.Context, *ent.AnnouncementMutation) (ent.Value, error)
|
||||||
|
|
||||||
|
// Mutate calls f(ctx, m).
|
||||||
|
func (f AnnouncementFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
|
if mv, ok := m.(*ent.AnnouncementMutation); ok {
|
||||||
|
return f(ctx, mv)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AnnouncementMutation", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The AnnouncementReadFunc type is an adapter to allow the use of ordinary
|
||||||
|
// function as AnnouncementRead mutator.
|
||||||
|
type AnnouncementReadFunc func(context.Context, *ent.AnnouncementReadMutation) (ent.Value, error)
|
||||||
|
|
||||||
|
// Mutate calls f(ctx, m).
|
||||||
|
func (f AnnouncementReadFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
|
if mv, ok := m.(*ent.AnnouncementReadMutation); ok {
|
||||||
|
return f(ctx, mv)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AnnouncementReadMutation", m)
|
||||||
|
}
|
||||||
|
|
||||||
// The GroupFunc type is an adapter to allow the use of ordinary
|
// The GroupFunc type is an adapter to allow the use of ordinary
|
||||||
// function as Group mutator.
|
// function as Group mutator.
|
||||||
type GroupFunc func(context.Context, *ent.GroupMutation) (ent.Value, error)
|
type GroupFunc func(context.Context, *ent.GroupMutation) (ent.Value, error)
|
||||||
@@ -117,6 +141,18 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err
|
|||||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m)
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary
|
||||||
|
// function as UsageCleanupTask mutator.
|
||||||
|
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskMutation) (ent.Value, error)
|
||||||
|
|
||||||
|
// Mutate calls f(ctx, m).
|
||||||
|
func (f UsageCleanupTaskFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
|
if mv, ok := m.(*ent.UsageCleanupTaskMutation); ok {
|
||||||
|
return f(ctx, mv)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UsageCleanupTaskMutation", m)
|
||||||
|
}
|
||||||
|
|
||||||
// The UsageLogFunc type is an adapter to allow the use of ordinary
|
// The UsageLogFunc type is an adapter to allow the use of ordinary
|
||||||
// function as UsageLog mutator.
|
// function as UsageLog mutator.
|
||||||
type UsageLogFunc func(context.Context, *ent.UsageLogMutation) (ent.Value, error)
|
type UsageLogFunc func(context.Context, *ent.UsageLogMutation) (ent.Value, error)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent"
|
"github.com/Wei-Shaw/sub2api/ent"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/account"
|
"github.com/Wei-Shaw/sub2api/ent/account"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
@@ -18,6 +20,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/user"
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||||
@@ -163,6 +166,60 @@ func (f TraverseAccountGroup) Traverse(ctx context.Context, q ent.Query) error {
|
|||||||
return fmt.Errorf("unexpected query type %T. expect *ent.AccountGroupQuery", q)
|
return fmt.Errorf("unexpected query type %T. expect *ent.AccountGroupQuery", q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The AnnouncementFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
|
type AnnouncementFunc func(context.Context, *ent.AnnouncementQuery) (ent.Value, error)
|
||||||
|
|
||||||
|
// Query calls f(ctx, q).
|
||||||
|
func (f AnnouncementFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||||
|
if q, ok := q.(*ent.AnnouncementQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T. expect *ent.AnnouncementQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TraverseAnnouncement type is an adapter to allow the use of ordinary function as Traverser.
|
||||||
|
type TraverseAnnouncement func(context.Context, *ent.AnnouncementQuery) error
|
||||||
|
|
||||||
|
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||||
|
func (f TraverseAnnouncement) Intercept(next ent.Querier) ent.Querier {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse calls f(ctx, q).
|
||||||
|
func (f TraverseAnnouncement) Traverse(ctx context.Context, q ent.Query) error {
|
||||||
|
if q, ok := q.(*ent.AnnouncementQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected query type %T. expect *ent.AnnouncementQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The AnnouncementReadFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
|
type AnnouncementReadFunc func(context.Context, *ent.AnnouncementReadQuery) (ent.Value, error)
|
||||||
|
|
||||||
|
// Query calls f(ctx, q).
|
||||||
|
func (f AnnouncementReadFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||||
|
if q, ok := q.(*ent.AnnouncementReadQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T. expect *ent.AnnouncementReadQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TraverseAnnouncementRead type is an adapter to allow the use of ordinary function as Traverser.
|
||||||
|
type TraverseAnnouncementRead func(context.Context, *ent.AnnouncementReadQuery) error
|
||||||
|
|
||||||
|
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||||
|
func (f TraverseAnnouncementRead) Intercept(next ent.Querier) ent.Querier {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse calls f(ctx, q).
|
||||||
|
func (f TraverseAnnouncementRead) Traverse(ctx context.Context, q ent.Query) error {
|
||||||
|
if q, ok := q.(*ent.AnnouncementReadQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected query type %T. expect *ent.AnnouncementReadQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
// The GroupFunc type is an adapter to allow the use of ordinary function as a Querier.
|
// The GroupFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
type GroupFunc func(context.Context, *ent.GroupQuery) (ent.Value, error)
|
type GroupFunc func(context.Context, *ent.GroupQuery) (ent.Value, error)
|
||||||
|
|
||||||
@@ -325,6 +382,33 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error {
|
|||||||
return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q)
|
return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
|
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskQuery) (ent.Value, error)
|
||||||
|
|
||||||
|
// Query calls f(ctx, q).
|
||||||
|
func (f UsageCleanupTaskFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||||
|
if q, ok := q.(*ent.UsageCleanupTaskQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T. expect *ent.UsageCleanupTaskQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TraverseUsageCleanupTask type is an adapter to allow the use of ordinary function as Traverser.
|
||||||
|
type TraverseUsageCleanupTask func(context.Context, *ent.UsageCleanupTaskQuery) error
|
||||||
|
|
||||||
|
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||||
|
func (f TraverseUsageCleanupTask) Intercept(next ent.Querier) ent.Querier {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse calls f(ctx, q).
|
||||||
|
func (f TraverseUsageCleanupTask) Traverse(ctx context.Context, q ent.Query) error {
|
||||||
|
if q, ok := q.(*ent.UsageCleanupTaskQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected query type %T. expect *ent.UsageCleanupTaskQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
// The UsageLogFunc type is an adapter to allow the use of ordinary function as a Querier.
|
// The UsageLogFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
type UsageLogFunc func(context.Context, *ent.UsageLogQuery) (ent.Value, error)
|
type UsageLogFunc func(context.Context, *ent.UsageLogQuery) (ent.Value, error)
|
||||||
|
|
||||||
@@ -496,6 +580,10 @@ func NewQuery(q ent.Query) (Query, error) {
|
|||||||
return &query[*ent.AccountQuery, predicate.Account, account.OrderOption]{typ: ent.TypeAccount, tq: q}, nil
|
return &query[*ent.AccountQuery, predicate.Account, account.OrderOption]{typ: ent.TypeAccount, tq: q}, nil
|
||||||
case *ent.AccountGroupQuery:
|
case *ent.AccountGroupQuery:
|
||||||
return &query[*ent.AccountGroupQuery, predicate.AccountGroup, accountgroup.OrderOption]{typ: ent.TypeAccountGroup, tq: q}, nil
|
return &query[*ent.AccountGroupQuery, predicate.AccountGroup, accountgroup.OrderOption]{typ: ent.TypeAccountGroup, tq: q}, nil
|
||||||
|
case *ent.AnnouncementQuery:
|
||||||
|
return &query[*ent.AnnouncementQuery, predicate.Announcement, announcement.OrderOption]{typ: ent.TypeAnnouncement, tq: q}, nil
|
||||||
|
case *ent.AnnouncementReadQuery:
|
||||||
|
return &query[*ent.AnnouncementReadQuery, predicate.AnnouncementRead, announcementread.OrderOption]{typ: ent.TypeAnnouncementRead, tq: q}, nil
|
||||||
case *ent.GroupQuery:
|
case *ent.GroupQuery:
|
||||||
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
|
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
|
||||||
case *ent.PromoCodeQuery:
|
case *ent.PromoCodeQuery:
|
||||||
@@ -508,6 +596,8 @@ func NewQuery(q ent.Query) (Query, error) {
|
|||||||
return &query[*ent.RedeemCodeQuery, predicate.RedeemCode, redeemcode.OrderOption]{typ: ent.TypeRedeemCode, tq: q}, nil
|
return &query[*ent.RedeemCodeQuery, predicate.RedeemCode, redeemcode.OrderOption]{typ: ent.TypeRedeemCode, tq: q}, nil
|
||||||
case *ent.SettingQuery:
|
case *ent.SettingQuery:
|
||||||
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
|
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
|
||||||
|
case *ent.UsageCleanupTaskQuery:
|
||||||
|
return &query[*ent.UsageCleanupTaskQuery, predicate.UsageCleanupTask, usagecleanuptask.OrderOption]{typ: ent.TypeUsageCleanupTask, tq: q}, nil
|
||||||
case *ent.UsageLogQuery:
|
case *ent.UsageLogQuery:
|
||||||
return &query[*ent.UsageLogQuery, predicate.UsageLog, usagelog.OrderOption]{typ: ent.TypeUsageLog, tq: q}, nil
|
return &query[*ent.UsageLogQuery, predicate.UsageLog, usagelog.OrderOption]{typ: ent.TypeUsageLog, tq: q}, nil
|
||||||
case *ent.UserQuery:
|
case *ent.UserQuery:
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ var (
|
|||||||
{Name: "extra", 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: "concurrency", Type: field.TypeInt, Default: 3},
|
||||||
{Name: "priority", Type: field.TypeInt, Default: 50},
|
{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"},
|
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
|
||||||
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||||
{Name: "last_used_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
{Name: "last_used_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
@@ -101,7 +102,7 @@ var (
|
|||||||
ForeignKeys: []*schema.ForeignKey{
|
ForeignKeys: []*schema.ForeignKey{
|
||||||
{
|
{
|
||||||
Symbol: "accounts_proxies_proxy",
|
Symbol: "accounts_proxies_proxy",
|
||||||
Columns: []*schema.Column{AccountsColumns[24]},
|
Columns: []*schema.Column{AccountsColumns[25]},
|
||||||
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
@@ -120,12 +121,12 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "account_status",
|
Name: "account_status",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[12]},
|
Columns: []*schema.Column{AccountsColumns[13]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_proxy_id",
|
Name: "account_proxy_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[24]},
|
Columns: []*schema.Column{AccountsColumns[25]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_priority",
|
Name: "account_priority",
|
||||||
@@ -135,27 +136,27 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "account_last_used_at",
|
Name: "account_last_used_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[14]},
|
Columns: []*schema.Column{AccountsColumns[15]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_schedulable",
|
Name: "account_schedulable",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[17]},
|
Columns: []*schema.Column{AccountsColumns[18]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_rate_limited_at",
|
Name: "account_rate_limited_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[18]},
|
Columns: []*schema.Column{AccountsColumns[19]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_rate_limit_reset_at",
|
Name: "account_rate_limit_reset_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[19]},
|
Columns: []*schema.Column{AccountsColumns[20]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_overload_until",
|
Name: "account_overload_until",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[20]},
|
Columns: []*schema.Column{AccountsColumns[21]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_deleted_at",
|
Name: "account_deleted_at",
|
||||||
@@ -203,6 +204,98 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// AnnouncementsColumns holds the columns for the "announcements" table.
|
||||||
|
AnnouncementsColumns = []*schema.Column{
|
||||||
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
|
{Name: "title", Type: field.TypeString, Size: 200},
|
||||||
|
{Name: "content", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||||
|
{Name: "status", Type: field.TypeString, Size: 20, Default: "draft"},
|
||||||
|
{Name: "targeting", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "starts_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "ends_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "created_by", Type: field.TypeInt64, Nullable: true},
|
||||||
|
{Name: "updated_by", Type: field.TypeInt64, Nullable: 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"}},
|
||||||
|
}
|
||||||
|
// AnnouncementsTable holds the schema information for the "announcements" table.
|
||||||
|
AnnouncementsTable = &schema.Table{
|
||||||
|
Name: "announcements",
|
||||||
|
Columns: AnnouncementsColumns,
|
||||||
|
PrimaryKey: []*schema.Column{AnnouncementsColumns[0]},
|
||||||
|
Indexes: []*schema.Index{
|
||||||
|
{
|
||||||
|
Name: "announcement_status",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementsColumns[3]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcement_created_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementsColumns[9]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcement_starts_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementsColumns[5]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcement_ends_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementsColumns[6]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// AnnouncementReadsColumns holds the columns for the "announcement_reads" table.
|
||||||
|
AnnouncementReadsColumns = []*schema.Column{
|
||||||
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
|
{Name: "read_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "announcement_id", Type: field.TypeInt64},
|
||||||
|
{Name: "user_id", Type: field.TypeInt64},
|
||||||
|
}
|
||||||
|
// AnnouncementReadsTable holds the schema information for the "announcement_reads" table.
|
||||||
|
AnnouncementReadsTable = &schema.Table{
|
||||||
|
Name: "announcement_reads",
|
||||||
|
Columns: AnnouncementReadsColumns,
|
||||||
|
PrimaryKey: []*schema.Column{AnnouncementReadsColumns[0]},
|
||||||
|
ForeignKeys: []*schema.ForeignKey{
|
||||||
|
{
|
||||||
|
Symbol: "announcement_reads_announcements_reads",
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[3]},
|
||||||
|
RefColumns: []*schema.Column{AnnouncementsColumns[0]},
|
||||||
|
OnDelete: schema.NoAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Symbol: "announcement_reads_users_announcement_reads",
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[4]},
|
||||||
|
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||||
|
OnDelete: schema.NoAction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Indexes: []*schema.Index{
|
||||||
|
{
|
||||||
|
Name: "announcementread_announcement_id",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[3]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcementread_user_id",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[4]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcementread_read_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[1]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "announcementread_announcement_id_user_id",
|
||||||
|
Unique: true,
|
||||||
|
Columns: []*schema.Column{AnnouncementReadsColumns[3], AnnouncementReadsColumns[4]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
// GroupsColumns holds the columns for the "groups" table.
|
// GroupsColumns holds the columns for the "groups" table.
|
||||||
GroupsColumns = []*schema.Column{
|
GroupsColumns = []*schema.Column{
|
||||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
@@ -225,6 +318,8 @@ var (
|
|||||||
{Name: "image_price_4k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
{Name: "image_price_4k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||||
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
|
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
|
||||||
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
|
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
|
||||||
|
{Name: "model_routing", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "model_routing_enabled", Type: field.TypeBool, Default: false},
|
||||||
}
|
}
|
||||||
// GroupsTable holds the schema information for the "groups" table.
|
// GroupsTable holds the schema information for the "groups" table.
|
||||||
GroupsTable = &schema.Table{
|
GroupsTable = &schema.Table{
|
||||||
@@ -431,6 +526,44 @@ var (
|
|||||||
Columns: SettingsColumns,
|
Columns: SettingsColumns,
|
||||||
PrimaryKey: []*schema.Column{SettingsColumns[0]},
|
PrimaryKey: []*schema.Column{SettingsColumns[0]},
|
||||||
}
|
}
|
||||||
|
// UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table.
|
||||||
|
UsageCleanupTasksColumns = []*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: "status", Type: field.TypeString, Size: 20},
|
||||||
|
{Name: "filters", Type: field.TypeJSON},
|
||||||
|
{Name: "created_by", Type: field.TypeInt64},
|
||||||
|
{Name: "deleted_rows", Type: field.TypeInt64, Default: 0},
|
||||||
|
{Name: "error_message", Type: field.TypeString, Nullable: true},
|
||||||
|
{Name: "canceled_by", Type: field.TypeInt64, Nullable: true},
|
||||||
|
{Name: "canceled_at", Type: field.TypeTime, Nullable: true},
|
||||||
|
{Name: "started_at", Type: field.TypeTime, Nullable: true},
|
||||||
|
{Name: "finished_at", Type: field.TypeTime, Nullable: true},
|
||||||
|
}
|
||||||
|
// UsageCleanupTasksTable holds the schema information for the "usage_cleanup_tasks" table.
|
||||||
|
UsageCleanupTasksTable = &schema.Table{
|
||||||
|
Name: "usage_cleanup_tasks",
|
||||||
|
Columns: UsageCleanupTasksColumns,
|
||||||
|
PrimaryKey: []*schema.Column{UsageCleanupTasksColumns[0]},
|
||||||
|
Indexes: []*schema.Index{
|
||||||
|
{
|
||||||
|
Name: "usagecleanuptask_status_created_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{UsageCleanupTasksColumns[3], UsageCleanupTasksColumns[1]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "usagecleanuptask_created_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{UsageCleanupTasksColumns[1]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "usagecleanuptask_canceled_at",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{UsageCleanupTasksColumns[9]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
// UsageLogsColumns holds the columns for the "usage_logs" table.
|
// UsageLogsColumns holds the columns for the "usage_logs" table.
|
||||||
UsageLogsColumns = []*schema.Column{
|
UsageLogsColumns = []*schema.Column{
|
||||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
@@ -449,6 +582,7 @@ var (
|
|||||||
{Name: "total_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
|
{Name: "total_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
|
||||||
{Name: "actual_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
|
{Name: "actual_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
|
||||||
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||||
|
{Name: "account_rate_multiplier", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||||
{Name: "billing_type", Type: field.TypeInt8, Default: 0},
|
{Name: "billing_type", Type: field.TypeInt8, Default: 0},
|
||||||
{Name: "stream", Type: field.TypeBool, Default: false},
|
{Name: "stream", Type: field.TypeBool, Default: false},
|
||||||
{Name: "duration_ms", Type: field.TypeInt, Nullable: true},
|
{Name: "duration_ms", Type: field.TypeInt, Nullable: true},
|
||||||
@@ -472,31 +606,31 @@ var (
|
|||||||
ForeignKeys: []*schema.ForeignKey{
|
ForeignKeys: []*schema.ForeignKey{
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_api_keys_usage_logs",
|
Symbol: "usage_logs_api_keys_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||||
RefColumns: []*schema.Column{APIKeysColumns[0]},
|
RefColumns: []*schema.Column{APIKeysColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_accounts_usage_logs",
|
Symbol: "usage_logs_accounts_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||||
RefColumns: []*schema.Column{AccountsColumns[0]},
|
RefColumns: []*schema.Column{AccountsColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_groups_usage_logs",
|
Symbol: "usage_logs_groups_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||||
RefColumns: []*schema.Column{GroupsColumns[0]},
|
RefColumns: []*schema.Column{GroupsColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_users_usage_logs",
|
Symbol: "usage_logs_users_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
Columns: []*schema.Column{UsageLogsColumns[29]},
|
||||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||||
OnDelete: schema.NoAction,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Symbol: "usage_logs_user_subscriptions_usage_logs",
|
Symbol: "usage_logs_user_subscriptions_usage_logs",
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29]},
|
Columns: []*schema.Column{UsageLogsColumns[30]},
|
||||||
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
@@ -505,32 +639,32 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id",
|
Name: "usagelog_user_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
Columns: []*schema.Column{UsageLogsColumns[29]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id",
|
Name: "usagelog_api_key_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[25]},
|
Columns: []*schema.Column{UsageLogsColumns[26]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_account_id",
|
Name: "usagelog_account_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[26]},
|
Columns: []*schema.Column{UsageLogsColumns[27]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_group_id",
|
Name: "usagelog_group_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[28]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_subscription_id",
|
Name: "usagelog_subscription_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29]},
|
Columns: []*schema.Column{UsageLogsColumns[30]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_created_at",
|
Name: "usagelog_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[24]},
|
Columns: []*schema.Column{UsageLogsColumns[25]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_model",
|
Name: "usagelog_model",
|
||||||
@@ -545,12 +679,12 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id_created_at",
|
Name: "usagelog_user_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28], UsageLogsColumns[24]},
|
Columns: []*schema.Column{UsageLogsColumns[29], UsageLogsColumns[25]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id_created_at",
|
Name: "usagelog_api_key_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[25], UsageLogsColumns[24]},
|
Columns: []*schema.Column{UsageLogsColumns[26], UsageLogsColumns[25]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -568,6 +702,9 @@ var (
|
|||||||
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
|
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
|
||||||
{Name: "username", Type: field.TypeString, Size: 100, Default: ""},
|
{Name: "username", Type: field.TypeString, Size: 100, Default: ""},
|
||||||
{Name: "notes", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
|
{Name: "notes", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
|
||||||
|
{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},
|
||||||
}
|
}
|
||||||
// UsersTable holds the schema information for the "users" table.
|
// UsersTable holds the schema information for the "users" table.
|
||||||
UsersTable = &schema.Table{
|
UsersTable = &schema.Table{
|
||||||
@@ -795,12 +932,15 @@ var (
|
|||||||
APIKeysTable,
|
APIKeysTable,
|
||||||
AccountsTable,
|
AccountsTable,
|
||||||
AccountGroupsTable,
|
AccountGroupsTable,
|
||||||
|
AnnouncementsTable,
|
||||||
|
AnnouncementReadsTable,
|
||||||
GroupsTable,
|
GroupsTable,
|
||||||
PromoCodesTable,
|
PromoCodesTable,
|
||||||
PromoCodeUsagesTable,
|
PromoCodeUsagesTable,
|
||||||
ProxiesTable,
|
ProxiesTable,
|
||||||
RedeemCodesTable,
|
RedeemCodesTable,
|
||||||
SettingsTable,
|
SettingsTable,
|
||||||
|
UsageCleanupTasksTable,
|
||||||
UsageLogsTable,
|
UsageLogsTable,
|
||||||
UsersTable,
|
UsersTable,
|
||||||
UserAllowedGroupsTable,
|
UserAllowedGroupsTable,
|
||||||
@@ -825,6 +965,14 @@ func init() {
|
|||||||
AccountGroupsTable.Annotation = &entsql.Annotation{
|
AccountGroupsTable.Annotation = &entsql.Annotation{
|
||||||
Table: "account_groups",
|
Table: "account_groups",
|
||||||
}
|
}
|
||||||
|
AnnouncementsTable.Annotation = &entsql.Annotation{
|
||||||
|
Table: "announcements",
|
||||||
|
}
|
||||||
|
AnnouncementReadsTable.ForeignKeys[0].RefTable = AnnouncementsTable
|
||||||
|
AnnouncementReadsTable.ForeignKeys[1].RefTable = UsersTable
|
||||||
|
AnnouncementReadsTable.Annotation = &entsql.Annotation{
|
||||||
|
Table: "announcement_reads",
|
||||||
|
}
|
||||||
GroupsTable.Annotation = &entsql.Annotation{
|
GroupsTable.Annotation = &entsql.Annotation{
|
||||||
Table: "groups",
|
Table: "groups",
|
||||||
}
|
}
|
||||||
@@ -847,6 +995,9 @@ func init() {
|
|||||||
SettingsTable.Annotation = &entsql.Annotation{
|
SettingsTable.Annotation = &entsql.Annotation{
|
||||||
Table: "settings",
|
Table: "settings",
|
||||||
}
|
}
|
||||||
|
UsageCleanupTasksTable.Annotation = &entsql.Annotation{
|
||||||
|
Table: "usage_cleanup_tasks",
|
||||||
|
}
|
||||||
UsageLogsTable.ForeignKeys[0].RefTable = APIKeysTable
|
UsageLogsTable.ForeignKeys[0].RefTable = APIKeysTable
|
||||||
UsageLogsTable.ForeignKeys[1].RefTable = AccountsTable
|
UsageLogsTable.ForeignKeys[1].RefTable = AccountsTable
|
||||||
UsageLogsTable.ForeignKeys[2].RefTable = GroupsTable
|
UsageLogsTable.ForeignKeys[2].RefTable = GroupsTable
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,12 @@ type Account func(*sql.Selector)
|
|||||||
// AccountGroup is the predicate function for accountgroup builders.
|
// AccountGroup is the predicate function for accountgroup builders.
|
||||||
type AccountGroup func(*sql.Selector)
|
type AccountGroup func(*sql.Selector)
|
||||||
|
|
||||||
|
// Announcement is the predicate function for announcement builders.
|
||||||
|
type Announcement func(*sql.Selector)
|
||||||
|
|
||||||
|
// AnnouncementRead is the predicate function for announcementread builders.
|
||||||
|
type AnnouncementRead func(*sql.Selector)
|
||||||
|
|
||||||
// Group is the predicate function for group builders.
|
// Group is the predicate function for group builders.
|
||||||
type Group func(*sql.Selector)
|
type Group func(*sql.Selector)
|
||||||
|
|
||||||
@@ -33,6 +39,9 @@ type RedeemCode func(*sql.Selector)
|
|||||||
// Setting is the predicate function for setting builders.
|
// Setting is the predicate function for setting builders.
|
||||||
type Setting func(*sql.Selector)
|
type Setting func(*sql.Selector)
|
||||||
|
|
||||||
|
// UsageCleanupTask is the predicate function for usagecleanuptask builders.
|
||||||
|
type UsageCleanupTask func(*sql.Selector)
|
||||||
|
|
||||||
// UsageLog is the predicate function for usagelog builders.
|
// UsageLog is the predicate function for usagelog builders.
|
||||||
type UsageLog func(*sql.Selector)
|
type UsageLog func(*sql.Selector)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/ent/account"
|
"github.com/Wei-Shaw/sub2api/ent/account"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcement"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
"github.com/Wei-Shaw/sub2api/ent/promocode"
|
||||||
@@ -15,6 +17,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema"
|
"github.com/Wei-Shaw/sub2api/ent/schema"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/user"
|
"github.com/Wei-Shaw/sub2api/ent/user"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||||
@@ -177,22 +180,26 @@ func init() {
|
|||||||
accountDescPriority := accountFields[8].Descriptor()
|
accountDescPriority := accountFields[8].Descriptor()
|
||||||
// account.DefaultPriority holds the default value on creation for the priority field.
|
// account.DefaultPriority holds the default value on creation for the priority field.
|
||||||
account.DefaultPriority = accountDescPriority.Default.(int)
|
account.DefaultPriority = accountDescPriority.Default.(int)
|
||||||
|
// accountDescRateMultiplier is the schema descriptor for rate_multiplier field.
|
||||||
|
accountDescRateMultiplier := accountFields[9].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 is the schema descriptor for status field.
|
||||||
accountDescStatus := accountFields[9].Descriptor()
|
accountDescStatus := accountFields[10].Descriptor()
|
||||||
// account.DefaultStatus holds the default value on creation for the status field.
|
// account.DefaultStatus holds the default value on creation for the status field.
|
||||||
account.DefaultStatus = accountDescStatus.Default.(string)
|
account.DefaultStatus = accountDescStatus.Default.(string)
|
||||||
// account.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
// 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)
|
account.StatusValidator = accountDescStatus.Validators[0].(func(string) error)
|
||||||
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
|
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
|
||||||
accountDescAutoPauseOnExpired := accountFields[13].Descriptor()
|
accountDescAutoPauseOnExpired := accountFields[14].Descriptor()
|
||||||
// account.DefaultAutoPauseOnExpired holds the default value on creation for the auto_pause_on_expired field.
|
// account.DefaultAutoPauseOnExpired holds the default value on creation for the auto_pause_on_expired field.
|
||||||
account.DefaultAutoPauseOnExpired = accountDescAutoPauseOnExpired.Default.(bool)
|
account.DefaultAutoPauseOnExpired = accountDescAutoPauseOnExpired.Default.(bool)
|
||||||
// accountDescSchedulable is the schema descriptor for schedulable field.
|
// accountDescSchedulable is the schema descriptor for schedulable field.
|
||||||
accountDescSchedulable := accountFields[14].Descriptor()
|
accountDescSchedulable := accountFields[15].Descriptor()
|
||||||
// account.DefaultSchedulable holds the default value on creation for the schedulable field.
|
// account.DefaultSchedulable holds the default value on creation for the schedulable field.
|
||||||
account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
|
account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
|
||||||
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
|
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
|
||||||
accountDescSessionWindowStatus := accountFields[20].Descriptor()
|
accountDescSessionWindowStatus := accountFields[21].Descriptor()
|
||||||
// account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
|
// 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)
|
account.SessionWindowStatusValidator = accountDescSessionWindowStatus.Validators[0].(func(string) error)
|
||||||
accountgroupFields := schema.AccountGroup{}.Fields()
|
accountgroupFields := schema.AccountGroup{}.Fields()
|
||||||
@@ -205,6 +212,56 @@ func init() {
|
|||||||
accountgroupDescCreatedAt := accountgroupFields[3].Descriptor()
|
accountgroupDescCreatedAt := accountgroupFields[3].Descriptor()
|
||||||
// accountgroup.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// accountgroup.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
accountgroup.DefaultCreatedAt = accountgroupDescCreatedAt.Default.(func() time.Time)
|
accountgroup.DefaultCreatedAt = accountgroupDescCreatedAt.Default.(func() time.Time)
|
||||||
|
announcementFields := schema.Announcement{}.Fields()
|
||||||
|
_ = announcementFields
|
||||||
|
// announcementDescTitle is the schema descriptor for title field.
|
||||||
|
announcementDescTitle := announcementFields[0].Descriptor()
|
||||||
|
// announcement.TitleValidator is a validator for the "title" field. It is called by the builders before save.
|
||||||
|
announcement.TitleValidator = func() func(string) error {
|
||||||
|
validators := announcementDescTitle.Validators
|
||||||
|
fns := [...]func(string) error{
|
||||||
|
validators[0].(func(string) error),
|
||||||
|
validators[1].(func(string) error),
|
||||||
|
}
|
||||||
|
return func(title string) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(title); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// announcementDescContent is the schema descriptor for content field.
|
||||||
|
announcementDescContent := announcementFields[1].Descriptor()
|
||||||
|
// announcement.ContentValidator is a validator for the "content" field. It is called by the builders before save.
|
||||||
|
announcement.ContentValidator = announcementDescContent.Validators[0].(func(string) error)
|
||||||
|
// announcementDescStatus is the schema descriptor for status field.
|
||||||
|
announcementDescStatus := announcementFields[2].Descriptor()
|
||||||
|
// announcement.DefaultStatus holds the default value on creation for the status field.
|
||||||
|
announcement.DefaultStatus = announcementDescStatus.Default.(string)
|
||||||
|
// announcement.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
|
announcement.StatusValidator = announcementDescStatus.Validators[0].(func(string) error)
|
||||||
|
// announcementDescCreatedAt is the schema descriptor for created_at field.
|
||||||
|
announcementDescCreatedAt := announcementFields[8].Descriptor()
|
||||||
|
// announcement.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
|
announcement.DefaultCreatedAt = announcementDescCreatedAt.Default.(func() time.Time)
|
||||||
|
// announcementDescUpdatedAt is the schema descriptor for updated_at field.
|
||||||
|
announcementDescUpdatedAt := announcementFields[9].Descriptor()
|
||||||
|
// announcement.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||||
|
announcement.DefaultUpdatedAt = announcementDescUpdatedAt.Default.(func() time.Time)
|
||||||
|
// announcement.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||||
|
announcement.UpdateDefaultUpdatedAt = announcementDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||||
|
announcementreadFields := schema.AnnouncementRead{}.Fields()
|
||||||
|
_ = announcementreadFields
|
||||||
|
// announcementreadDescReadAt is the schema descriptor for read_at field.
|
||||||
|
announcementreadDescReadAt := announcementreadFields[2].Descriptor()
|
||||||
|
// announcementread.DefaultReadAt holds the default value on creation for the read_at field.
|
||||||
|
announcementread.DefaultReadAt = announcementreadDescReadAt.Default.(func() time.Time)
|
||||||
|
// announcementreadDescCreatedAt is the schema descriptor for created_at field.
|
||||||
|
announcementreadDescCreatedAt := announcementreadFields[3].Descriptor()
|
||||||
|
// announcementread.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
|
announcementread.DefaultCreatedAt = announcementreadDescCreatedAt.Default.(func() time.Time)
|
||||||
groupMixin := schema.Group{}.Mixin()
|
groupMixin := schema.Group{}.Mixin()
|
||||||
groupMixinHooks1 := groupMixin[1].Hooks()
|
groupMixinHooks1 := groupMixin[1].Hooks()
|
||||||
group.Hooks[0] = groupMixinHooks1[0]
|
group.Hooks[0] = groupMixinHooks1[0]
|
||||||
@@ -276,6 +333,10 @@ func init() {
|
|||||||
groupDescClaudeCodeOnly := groupFields[14].Descriptor()
|
groupDescClaudeCodeOnly := groupFields[14].Descriptor()
|
||||||
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
|
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
|
||||||
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
|
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
|
||||||
|
// groupDescModelRoutingEnabled is the schema descriptor for model_routing_enabled field.
|
||||||
|
groupDescModelRoutingEnabled := groupFields[17].Descriptor()
|
||||||
|
// group.DefaultModelRoutingEnabled holds the default value on creation for the model_routing_enabled field.
|
||||||
|
group.DefaultModelRoutingEnabled = groupDescModelRoutingEnabled.Default.(bool)
|
||||||
promocodeFields := schema.PromoCode{}.Fields()
|
promocodeFields := schema.PromoCode{}.Fields()
|
||||||
_ = promocodeFields
|
_ = promocodeFields
|
||||||
// promocodeDescCode is the schema descriptor for code field.
|
// promocodeDescCode is the schema descriptor for code field.
|
||||||
@@ -487,6 +548,43 @@ func init() {
|
|||||||
setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time)
|
setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time)
|
||||||
// setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
// setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||||
setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time)
|
setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||||
|
usagecleanuptaskMixin := schema.UsageCleanupTask{}.Mixin()
|
||||||
|
usagecleanuptaskMixinFields0 := usagecleanuptaskMixin[0].Fields()
|
||||||
|
_ = usagecleanuptaskMixinFields0
|
||||||
|
usagecleanuptaskFields := schema.UsageCleanupTask{}.Fields()
|
||||||
|
_ = usagecleanuptaskFields
|
||||||
|
// usagecleanuptaskDescCreatedAt is the schema descriptor for created_at field.
|
||||||
|
usagecleanuptaskDescCreatedAt := usagecleanuptaskMixinFields0[0].Descriptor()
|
||||||
|
// usagecleanuptask.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
|
usagecleanuptask.DefaultCreatedAt = usagecleanuptaskDescCreatedAt.Default.(func() time.Time)
|
||||||
|
// usagecleanuptaskDescUpdatedAt is the schema descriptor for updated_at field.
|
||||||
|
usagecleanuptaskDescUpdatedAt := usagecleanuptaskMixinFields0[1].Descriptor()
|
||||||
|
// usagecleanuptask.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||||
|
usagecleanuptask.DefaultUpdatedAt = usagecleanuptaskDescUpdatedAt.Default.(func() time.Time)
|
||||||
|
// usagecleanuptask.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||||
|
usagecleanuptask.UpdateDefaultUpdatedAt = usagecleanuptaskDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||||
|
// usagecleanuptaskDescStatus is the schema descriptor for status field.
|
||||||
|
usagecleanuptaskDescStatus := usagecleanuptaskFields[0].Descriptor()
|
||||||
|
// usagecleanuptask.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
|
usagecleanuptask.StatusValidator = func() func(string) error {
|
||||||
|
validators := usagecleanuptaskDescStatus.Validators
|
||||||
|
fns := [...]func(string) error{
|
||||||
|
validators[0].(func(string) error),
|
||||||
|
validators[1].(func(string) error),
|
||||||
|
}
|
||||||
|
return func(status string) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(status); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// usagecleanuptaskDescDeletedRows is the schema descriptor for deleted_rows field.
|
||||||
|
usagecleanuptaskDescDeletedRows := usagecleanuptaskFields[3].Descriptor()
|
||||||
|
// usagecleanuptask.DefaultDeletedRows holds the default value on creation for the deleted_rows field.
|
||||||
|
usagecleanuptask.DefaultDeletedRows = usagecleanuptaskDescDeletedRows.Default.(int64)
|
||||||
usagelogFields := schema.UsageLog{}.Fields()
|
usagelogFields := schema.UsageLog{}.Fields()
|
||||||
_ = usagelogFields
|
_ = usagelogFields
|
||||||
// usagelogDescRequestID is the schema descriptor for request_id field.
|
// usagelogDescRequestID is the schema descriptor for request_id field.
|
||||||
@@ -578,31 +676,31 @@ func init() {
|
|||||||
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
||||||
usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64)
|
usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64)
|
||||||
// usagelogDescBillingType is the schema descriptor for billing_type field.
|
// usagelogDescBillingType is the schema descriptor for billing_type field.
|
||||||
usagelogDescBillingType := usagelogFields[20].Descriptor()
|
usagelogDescBillingType := usagelogFields[21].Descriptor()
|
||||||
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
|
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
|
||||||
usagelog.DefaultBillingType = usagelogDescBillingType.Default.(int8)
|
usagelog.DefaultBillingType = usagelogDescBillingType.Default.(int8)
|
||||||
// usagelogDescStream is the schema descriptor for stream field.
|
// usagelogDescStream is the schema descriptor for stream field.
|
||||||
usagelogDescStream := usagelogFields[21].Descriptor()
|
usagelogDescStream := usagelogFields[22].Descriptor()
|
||||||
// usagelog.DefaultStream holds the default value on creation for the stream field.
|
// usagelog.DefaultStream holds the default value on creation for the stream field.
|
||||||
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
|
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
|
||||||
// usagelogDescUserAgent is the schema descriptor for user_agent field.
|
// usagelogDescUserAgent is the schema descriptor for user_agent field.
|
||||||
usagelogDescUserAgent := usagelogFields[24].Descriptor()
|
usagelogDescUserAgent := usagelogFields[25].Descriptor()
|
||||||
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
|
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
|
||||||
usagelog.UserAgentValidator = usagelogDescUserAgent.Validators[0].(func(string) error)
|
usagelog.UserAgentValidator = usagelogDescUserAgent.Validators[0].(func(string) error)
|
||||||
// usagelogDescIPAddress is the schema descriptor for ip_address field.
|
// usagelogDescIPAddress is the schema descriptor for ip_address field.
|
||||||
usagelogDescIPAddress := usagelogFields[25].Descriptor()
|
usagelogDescIPAddress := usagelogFields[26].Descriptor()
|
||||||
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
|
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
|
||||||
usagelog.IPAddressValidator = usagelogDescIPAddress.Validators[0].(func(string) error)
|
usagelog.IPAddressValidator = usagelogDescIPAddress.Validators[0].(func(string) error)
|
||||||
// usagelogDescImageCount is the schema descriptor for image_count field.
|
// usagelogDescImageCount is the schema descriptor for image_count field.
|
||||||
usagelogDescImageCount := usagelogFields[26].Descriptor()
|
usagelogDescImageCount := usagelogFields[27].Descriptor()
|
||||||
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
|
// usagelog.DefaultImageCount holds the default value on creation for the image_count field.
|
||||||
usagelog.DefaultImageCount = usagelogDescImageCount.Default.(int)
|
usagelog.DefaultImageCount = usagelogDescImageCount.Default.(int)
|
||||||
// usagelogDescImageSize is the schema descriptor for image_size field.
|
// usagelogDescImageSize is the schema descriptor for image_size field.
|
||||||
usagelogDescImageSize := usagelogFields[27].Descriptor()
|
usagelogDescImageSize := usagelogFields[28].Descriptor()
|
||||||
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
||||||
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
|
usagelog.ImageSizeValidator = usagelogDescImageSize.Validators[0].(func(string) error)
|
||||||
// usagelogDescCreatedAt is the schema descriptor for created_at field.
|
// usagelogDescCreatedAt is the schema descriptor for created_at field.
|
||||||
usagelogDescCreatedAt := usagelogFields[28].Descriptor()
|
usagelogDescCreatedAt := usagelogFields[29].Descriptor()
|
||||||
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
|
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
|
||||||
userMixin := schema.User{}.Mixin()
|
userMixin := schema.User{}.Mixin()
|
||||||
@@ -690,6 +788,10 @@ func init() {
|
|||||||
userDescNotes := userFields[7].Descriptor()
|
userDescNotes := userFields[7].Descriptor()
|
||||||
// user.DefaultNotes holds the default value on creation for the notes field.
|
// user.DefaultNotes holds the default value on creation for the notes field.
|
||||||
user.DefaultNotes = userDescNotes.Default.(string)
|
user.DefaultNotes = userDescNotes.Default.(string)
|
||||||
|
// userDescTotpEnabled is the schema descriptor for totp_enabled field.
|
||||||
|
userDescTotpEnabled := userFields[9].Descriptor()
|
||||||
|
// user.DefaultTotpEnabled holds the default value on creation for the totp_enabled field.
|
||||||
|
user.DefaultTotpEnabled = userDescTotpEnabled.Default.(bool)
|
||||||
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
|
userallowedgroupFields := schema.UserAllowedGroup{}.Fields()
|
||||||
_ = userallowedgroupFields
|
_ = userallowedgroupFields
|
||||||
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.
|
// userallowedgroupDescCreatedAt is the schema descriptor for created_at field.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package schema
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -102,10 +102,16 @@ func (Account) Fields() []ent.Field {
|
|||||||
field.Int("priority").
|
field.Int("priority").
|
||||||
Default(50),
|
Default(50),
|
||||||
|
|
||||||
|
// rate_multiplier: 账号计费倍率(>=0,允许 0 表示该账号计费为 0)
|
||||||
|
// 仅影响账号维度计费口径,不影响用户/API Key 扣费(分组倍率)
|
||||||
|
field.Float("rate_multiplier").
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
|
||||||
|
Default(1.0),
|
||||||
|
|
||||||
// status: 账户状态,如 "active", "error", "disabled"
|
// status: 账户状态,如 "active", "error", "disabled"
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.StatusActive),
|
Default(domain.StatusActive),
|
||||||
|
|
||||||
// error_message: 错误信息,记录账户异常时的详细信息
|
// error_message: 错误信息,记录账户异常时的详细信息
|
||||||
field.String("error_message").
|
field.String("error_message").
|
||||||
|
|||||||
90
backend/ent/schema/announcement.go
Normal file
90
backend/ent/schema/announcement.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/entsql"
|
||||||
|
"entgo.io/ent/schema"
|
||||||
|
"entgo.io/ent/schema/edge"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"entgo.io/ent/schema/index"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Announcement holds the schema definition for the Announcement entity.
|
||||||
|
//
|
||||||
|
// 删除策略:硬删除(已读记录通过外键级联删除)
|
||||||
|
type Announcement struct {
|
||||||
|
ent.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Announcement) Annotations() []schema.Annotation {
|
||||||
|
return []schema.Annotation{
|
||||||
|
entsql.Annotation{Table: "announcements"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Announcement) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.String("title").
|
||||||
|
MaxLen(200).
|
||||||
|
NotEmpty().
|
||||||
|
Comment("公告标题"),
|
||||||
|
field.String("content").
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "text"}).
|
||||||
|
NotEmpty().
|
||||||
|
Comment("公告内容(支持 Markdown)"),
|
||||||
|
field.String("status").
|
||||||
|
MaxLen(20).
|
||||||
|
Default(domain.AnnouncementStatusDraft).
|
||||||
|
Comment("状态: draft, active, archived"),
|
||||||
|
field.JSON("targeting", domain.AnnouncementTargeting{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
||||||
|
Comment("展示条件(JSON 规则)"),
|
||||||
|
field.Time("starts_at").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}).
|
||||||
|
Comment("开始展示时间(为空表示立即生效)"),
|
||||||
|
field.Time("ends_at").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}).
|
||||||
|
Comment("结束展示时间(为空表示永久生效)"),
|
||||||
|
field.Int64("created_by").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
Comment("创建人用户ID(管理员)"),
|
||||||
|
field.Int64("updated_by").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
Comment("更新人用户ID(管理员)"),
|
||||||
|
field.Time("created_at").
|
||||||
|
Immutable().
|
||||||
|
Default(time.Now).
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||||
|
field.Time("updated_at").
|
||||||
|
Default(time.Now).
|
||||||
|
UpdateDefault(time.Now).
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Announcement) Edges() []ent.Edge {
|
||||||
|
return []ent.Edge{
|
||||||
|
edge.To("reads", AnnouncementRead.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Announcement) Indexes() []ent.Index {
|
||||||
|
return []ent.Index{
|
||||||
|
index.Fields("status"),
|
||||||
|
index.Fields("created_at"),
|
||||||
|
index.Fields("starts_at"),
|
||||||
|
index.Fields("ends_at"),
|
||||||
|
}
|
||||||
|
}
|
||||||
65
backend/ent/schema/announcement_read.go
Normal file
65
backend/ent/schema/announcement_read.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/entsql"
|
||||||
|
"entgo.io/ent/schema"
|
||||||
|
"entgo.io/ent/schema/edge"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"entgo.io/ent/schema/index"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementRead holds the schema definition for the AnnouncementRead entity.
|
||||||
|
//
|
||||||
|
// 记录用户对公告的已读状态(首次已读时间)。
|
||||||
|
type AnnouncementRead struct {
|
||||||
|
ent.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AnnouncementRead) Annotations() []schema.Annotation {
|
||||||
|
return []schema.Annotation{
|
||||||
|
entsql.Annotation{Table: "announcement_reads"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AnnouncementRead) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.Int64("announcement_id"),
|
||||||
|
field.Int64("user_id"),
|
||||||
|
field.Time("read_at").
|
||||||
|
Default(time.Now).
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}).
|
||||||
|
Comment("用户首次已读时间"),
|
||||||
|
field.Time("created_at").
|
||||||
|
Immutable().
|
||||||
|
Default(time.Now).
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AnnouncementRead) Edges() []ent.Edge {
|
||||||
|
return []ent.Edge{
|
||||||
|
edge.From("announcement", Announcement.Type).
|
||||||
|
Ref("reads").
|
||||||
|
Field("announcement_id").
|
||||||
|
Unique().
|
||||||
|
Required(),
|
||||||
|
edge.From("user", User.Type).
|
||||||
|
Ref("announcement_reads").
|
||||||
|
Field("user_id").
|
||||||
|
Unique().
|
||||||
|
Required(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AnnouncementRead) Indexes() []ent.Index {
|
||||||
|
return []ent.Index{
|
||||||
|
index.Fields("announcement_id"),
|
||||||
|
index.Fields("user_id"),
|
||||||
|
index.Fields("read_at"),
|
||||||
|
index.Fields("announcement_id", "user_id").Unique(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package schema
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect/entsql"
|
"entgo.io/ent/dialect/entsql"
|
||||||
@@ -45,7 +45,7 @@ func (APIKey) Fields() []ent.Field {
|
|||||||
Nillable(),
|
Nillable(),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.StatusActive),
|
Default(domain.StatusActive),
|
||||||
field.JSON("ip_whitelist", []string{}).
|
field.JSON("ip_whitelist", []string{}).
|
||||||
Optional().
|
Optional().
|
||||||
Comment("Allowed IPs/CIDRs, e.g. [\"192.168.1.100\", \"10.0.0.0/8\"]"),
|
Comment("Allowed IPs/CIDRs, e.g. [\"192.168.1.100\", \"10.0.0.0/8\"]"),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package schema
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -49,15 +49,15 @@ func (Group) Fields() []ent.Field {
|
|||||||
Default(false),
|
Default(false),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.StatusActive),
|
Default(domain.StatusActive),
|
||||||
|
|
||||||
// Subscription-related fields (added by migration 003)
|
// Subscription-related fields (added by migration 003)
|
||||||
field.String("platform").
|
field.String("platform").
|
||||||
MaxLen(50).
|
MaxLen(50).
|
||||||
Default(service.PlatformAnthropic),
|
Default(domain.PlatformAnthropic),
|
||||||
field.String("subscription_type").
|
field.String("subscription_type").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.SubscriptionTypeStandard),
|
Default(domain.SubscriptionTypeStandard),
|
||||||
field.Float("daily_limit_usd").
|
field.Float("daily_limit_usd").
|
||||||
Optional().
|
Optional().
|
||||||
Nillable().
|
Nillable().
|
||||||
@@ -95,6 +95,17 @@ func (Group) Fields() []ent.Field {
|
|||||||
Optional().
|
Optional().
|
||||||
Nillable().
|
Nillable().
|
||||||
Comment("非 Claude Code 请求降级使用的分组 ID"),
|
Comment("非 Claude Code 请求降级使用的分组 ID"),
|
||||||
|
|
||||||
|
// 模型路由配置 (added by migration 040)
|
||||||
|
field.JSON("model_routing", map[string][]int64{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
||||||
|
Comment("模型路由配置:模型模式 -> 优先账号ID列表"),
|
||||||
|
|
||||||
|
// 模型路由开关 (added by migration 041)
|
||||||
|
field.Bool("model_routing_enabled").
|
||||||
|
Default(false).
|
||||||
|
Comment("是否启用模型路由配置"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package mixins
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
@@ -12,7 +13,6 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
"entgo.io/ent/schema/mixin"
|
"entgo.io/ent/schema/mixin"
|
||||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
|
||||||
"github.com/Wei-Shaw/sub2api/ent/intercept"
|
"github.com/Wei-Shaw/sub2api/ent/intercept"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,7 +113,6 @@ func (d SoftDeleteMixin) Hooks() []ent.Hook {
|
|||||||
SetOp(ent.Op)
|
SetOp(ent.Op)
|
||||||
SetDeletedAt(time.Time)
|
SetDeletedAt(time.Time)
|
||||||
WhereP(...func(*sql.Selector))
|
WhereP(...func(*sql.Selector))
|
||||||
Client() *dbent.Client
|
|
||||||
})
|
})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||||
@@ -124,7 +123,7 @@ func (d SoftDeleteMixin) Hooks() []ent.Hook {
|
|||||||
mx.SetOp(ent.OpUpdate)
|
mx.SetOp(ent.OpUpdate)
|
||||||
// 设置删除时间为当前时间
|
// 设置删除时间为当前时间
|
||||||
mx.SetDeletedAt(time.Now())
|
mx.SetDeletedAt(time.Now())
|
||||||
return mx.Client().Mutate(ctx, m)
|
return mutateWithClient(ctx, m, next)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -137,3 +136,41 @@ func (d SoftDeleteMixin) applyPredicate(w interface{ WhereP(...func(*sql.Selecto
|
|||||||
sql.FieldIsNull(d.Fields()[0].Descriptor().Name),
|
sql.FieldIsNull(d.Fields()[0].Descriptor().Name),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mutateWithClient(ctx context.Context, m ent.Mutation, fallback ent.Mutator) (ent.Value, error) {
|
||||||
|
clientMethod := reflect.ValueOf(m).MethodByName("Client")
|
||||||
|
if !clientMethod.IsValid() || clientMethod.Type().NumIn() != 0 || clientMethod.Type().NumOut() != 1 {
|
||||||
|
return nil, fmt.Errorf("soft delete: mutation client method not found for %T", m)
|
||||||
|
}
|
||||||
|
client := clientMethod.Call(nil)[0]
|
||||||
|
mutateMethod := client.MethodByName("Mutate")
|
||||||
|
if !mutateMethod.IsValid() {
|
||||||
|
return nil, fmt.Errorf("soft delete: mutation client missing Mutate for %T", m)
|
||||||
|
}
|
||||||
|
if mutateMethod.Type().NumIn() != 2 || mutateMethod.Type().NumOut() != 2 {
|
||||||
|
return nil, fmt.Errorf("soft delete: mutation client signature mismatch for %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := mutateMethod.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(m)})
|
||||||
|
value := results[0].Interface()
|
||||||
|
var err error
|
||||||
|
if !results[1].IsNil() {
|
||||||
|
errValue := results[1].Interface()
|
||||||
|
typedErr, ok := errValue.(error)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("soft delete: unexpected error type %T for %T", errValue, m)
|
||||||
|
}
|
||||||
|
err = typedErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if value == nil {
|
||||||
|
return nil, fmt.Errorf("soft delete: mutation client returned nil for %T", m)
|
||||||
|
}
|
||||||
|
v, ok := value.(ent.Value)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("soft delete: unexpected value type %T for %T", value, m)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -49,7 +49,7 @@ func (PromoCode) Fields() []ent.Field {
|
|||||||
Comment("已使用次数"),
|
Comment("已使用次数"),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.PromoCodeStatusActive).
|
Default(domain.PromoCodeStatusActive).
|
||||||
Comment("状态: active, disabled"),
|
Comment("状态: active, disabled"),
|
||||||
field.Time("expires_at").
|
field.Time("expires_at").
|
||||||
Optional().
|
Optional().
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -41,13 +41,13 @@ func (RedeemCode) Fields() []ent.Field {
|
|||||||
Unique(),
|
Unique(),
|
||||||
field.String("type").
|
field.String("type").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.RedeemTypeBalance),
|
Default(domain.RedeemTypeBalance),
|
||||||
field.Float("value").
|
field.Float("value").
|
||||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||||
Default(0),
|
Default(0),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.StatusUnused),
|
Default(domain.StatusUnused),
|
||||||
field.Int64("used_by").
|
field.Int64("used_by").
|
||||||
Optional().
|
Optional().
|
||||||
Nillable(),
|
Nillable(),
|
||||||
|
|||||||
75
backend/ent/schema/usage_cleanup_task.go
Normal file
75
backend/ent/schema/usage_cleanup_task.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/entsql"
|
||||||
|
"entgo.io/ent/schema"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"entgo.io/ent/schema/index"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageCleanupTask 定义使用记录清理任务的 schema。
|
||||||
|
type UsageCleanupTask struct {
|
||||||
|
ent.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UsageCleanupTask) Annotations() []schema.Annotation {
|
||||||
|
return []schema.Annotation{
|
||||||
|
entsql.Annotation{Table: "usage_cleanup_tasks"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UsageCleanupTask) Mixin() []ent.Mixin {
|
||||||
|
return []ent.Mixin{
|
||||||
|
mixins.TimeMixin{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UsageCleanupTask) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
field.String("status").
|
||||||
|
MaxLen(20).
|
||||||
|
Validate(validateUsageCleanupStatus),
|
||||||
|
field.JSON("filters", json.RawMessage{}),
|
||||||
|
field.Int64("created_by"),
|
||||||
|
field.Int64("deleted_rows").
|
||||||
|
Default(0),
|
||||||
|
field.String("error_message").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Int64("canceled_by").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Time("canceled_at").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Time("started_at").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Time("finished_at").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UsageCleanupTask) Indexes() []ent.Index {
|
||||||
|
return []ent.Index{
|
||||||
|
index.Fields("status", "created_at"),
|
||||||
|
index.Fields("created_at"),
|
||||||
|
index.Fields("canceled_at"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUsageCleanupStatus(status string) error {
|
||||||
|
switch status {
|
||||||
|
case "pending", "running", "succeeded", "failed", "canceled":
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid usage cleanup status: %s", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,12 @@ func (UsageLog) Fields() []ent.Field {
|
|||||||
Default(1).
|
Default(1).
|
||||||
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
|
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
|
||||||
|
|
||||||
|
// account_rate_multiplier: 账号计费倍率快照(NULL 表示按 1.0 处理)
|
||||||
|
field.Float("account_rate_multiplier").
|
||||||
|
Optional().
|
||||||
|
Nillable().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
|
||||||
|
|
||||||
// 其他字段
|
// 其他字段
|
||||||
field.Int8("billing_type").
|
field.Int8("billing_type").
|
||||||
Default(0),
|
Default(0),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package schema
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -43,7 +43,7 @@ func (User) Fields() []ent.Field {
|
|||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
field.String("role").
|
field.String("role").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.RoleUser),
|
Default(domain.RoleUser),
|
||||||
field.Float("balance").
|
field.Float("balance").
|
||||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
|
||||||
Default(0),
|
Default(0),
|
||||||
@@ -51,7 +51,7 @@ func (User) Fields() []ent.Field {
|
|||||||
Default(5),
|
Default(5),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.StatusActive),
|
Default(domain.StatusActive),
|
||||||
|
|
||||||
// Optional profile fields (added later; default '' in DB migration)
|
// Optional profile fields (added later; default '' in DB migration)
|
||||||
field.String("username").
|
field.String("username").
|
||||||
@@ -61,6 +61,17 @@ func (User) Fields() []ent.Field {
|
|||||||
field.String("notes").
|
field.String("notes").
|
||||||
SchemaType(map[string]string{dialect.Postgres: "text"}).
|
SchemaType(map[string]string{dialect.Postgres: "text"}).
|
||||||
Default(""),
|
Default(""),
|
||||||
|
|
||||||
|
// TOTP 双因素认证字段
|
||||||
|
field.String("totp_secret_encrypted").
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "text"}).
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Bool("totp_enabled").
|
||||||
|
Default(false),
|
||||||
|
field.Time("totp_enabled_at").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +81,7 @@ func (User) Edges() []ent.Edge {
|
|||||||
edge.To("redeem_codes", RedeemCode.Type),
|
edge.To("redeem_codes", RedeemCode.Type),
|
||||||
edge.To("subscriptions", UserSubscription.Type),
|
edge.To("subscriptions", UserSubscription.Type),
|
||||||
edge.To("assigned_subscriptions", UserSubscription.Type),
|
edge.To("assigned_subscriptions", UserSubscription.Type),
|
||||||
|
edge.To("announcement_reads", AnnouncementRead.Type),
|
||||||
edge.To("allowed_groups", Group.Type).
|
edge.To("allowed_groups", Group.Type).
|
||||||
Through("user_allowed_groups", UserAllowedGroup.Type),
|
Through("user_allowed_groups", UserAllowedGroup.Type),
|
||||||
edge.To("usage_logs", UsageLog.Type),
|
edge.To("usage_logs", UsageLog.Type),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -44,7 +44,7 @@ func (UserSubscription) Fields() []ent.Field {
|
|||||||
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
|
||||||
field.String("status").
|
field.String("status").
|
||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(service.SubscriptionStatusActive),
|
Default(domain.SubscriptionStatusActive),
|
||||||
|
|
||||||
field.Time("daily_window_start").
|
field.Time("daily_window_start").
|
||||||
Optional().
|
Optional().
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ type Tx struct {
|
|||||||
Account *AccountClient
|
Account *AccountClient
|
||||||
// AccountGroup is the client for interacting with the AccountGroup builders.
|
// AccountGroup is the client for interacting with the AccountGroup builders.
|
||||||
AccountGroup *AccountGroupClient
|
AccountGroup *AccountGroupClient
|
||||||
|
// Announcement is the client for interacting with the Announcement builders.
|
||||||
|
Announcement *AnnouncementClient
|
||||||
|
// AnnouncementRead is the client for interacting with the AnnouncementRead builders.
|
||||||
|
AnnouncementRead *AnnouncementReadClient
|
||||||
// Group is the client for interacting with the Group builders.
|
// Group is the client for interacting with the Group builders.
|
||||||
Group *GroupClient
|
Group *GroupClient
|
||||||
// PromoCode is the client for interacting with the PromoCode builders.
|
// PromoCode is the client for interacting with the PromoCode builders.
|
||||||
@@ -32,6 +36,8 @@ type Tx struct {
|
|||||||
RedeemCode *RedeemCodeClient
|
RedeemCode *RedeemCodeClient
|
||||||
// Setting is the client for interacting with the Setting builders.
|
// Setting is the client for interacting with the Setting builders.
|
||||||
Setting *SettingClient
|
Setting *SettingClient
|
||||||
|
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||||
|
UsageCleanupTask *UsageCleanupTaskClient
|
||||||
// UsageLog is the client for interacting with the UsageLog builders.
|
// UsageLog is the client for interacting with the UsageLog builders.
|
||||||
UsageLog *UsageLogClient
|
UsageLog *UsageLogClient
|
||||||
// User is the client for interacting with the User builders.
|
// User is the client for interacting with the User builders.
|
||||||
@@ -178,12 +184,15 @@ func (tx *Tx) init() {
|
|||||||
tx.APIKey = NewAPIKeyClient(tx.config)
|
tx.APIKey = NewAPIKeyClient(tx.config)
|
||||||
tx.Account = NewAccountClient(tx.config)
|
tx.Account = NewAccountClient(tx.config)
|
||||||
tx.AccountGroup = NewAccountGroupClient(tx.config)
|
tx.AccountGroup = NewAccountGroupClient(tx.config)
|
||||||
|
tx.Announcement = NewAnnouncementClient(tx.config)
|
||||||
|
tx.AnnouncementRead = NewAnnouncementReadClient(tx.config)
|
||||||
tx.Group = NewGroupClient(tx.config)
|
tx.Group = NewGroupClient(tx.config)
|
||||||
tx.PromoCode = NewPromoCodeClient(tx.config)
|
tx.PromoCode = NewPromoCodeClient(tx.config)
|
||||||
tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
|
tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
|
||||||
tx.Proxy = NewProxyClient(tx.config)
|
tx.Proxy = NewProxyClient(tx.config)
|
||||||
tx.RedeemCode = NewRedeemCodeClient(tx.config)
|
tx.RedeemCode = NewRedeemCodeClient(tx.config)
|
||||||
tx.Setting = NewSettingClient(tx.config)
|
tx.Setting = NewSettingClient(tx.config)
|
||||||
|
tx.UsageCleanupTask = NewUsageCleanupTaskClient(tx.config)
|
||||||
tx.UsageLog = NewUsageLogClient(tx.config)
|
tx.UsageLog = NewUsageLogClient(tx.config)
|
||||||
tx.User = NewUserClient(tx.config)
|
tx.User = NewUserClient(tx.config)
|
||||||
tx.UserAllowedGroup = NewUserAllowedGroupClient(tx.config)
|
tx.UserAllowedGroup = NewUserAllowedGroupClient(tx.config)
|
||||||
|
|||||||
236
backend/ent/usagecleanuptask.go
Normal file
236
backend/ent/usagecleanuptask.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageCleanupTask is the model entity for the UsageCleanupTask schema.
|
||||||
|
type UsageCleanupTask 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"`
|
||||||
|
// Status holds the value of the "status" field.
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
// Filters holds the value of the "filters" field.
|
||||||
|
Filters json.RawMessage `json:"filters,omitempty"`
|
||||||
|
// CreatedBy holds the value of the "created_by" field.
|
||||||
|
CreatedBy int64 `json:"created_by,omitempty"`
|
||||||
|
// DeletedRows holds the value of the "deleted_rows" field.
|
||||||
|
DeletedRows int64 `json:"deleted_rows,omitempty"`
|
||||||
|
// ErrorMessage holds the value of the "error_message" field.
|
||||||
|
ErrorMessage *string `json:"error_message,omitempty"`
|
||||||
|
// CanceledBy holds the value of the "canceled_by" field.
|
||||||
|
CanceledBy *int64 `json:"canceled_by,omitempty"`
|
||||||
|
// CanceledAt holds the value of the "canceled_at" field.
|
||||||
|
CanceledAt *time.Time `json:"canceled_at,omitempty"`
|
||||||
|
// StartedAt holds the value of the "started_at" field.
|
||||||
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||||
|
// FinishedAt holds the value of the "finished_at" field.
|
||||||
|
FinishedAt *time.Time `json:"finished_at,omitempty"`
|
||||||
|
selectValues sql.SelectValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
|
func (*UsageCleanupTask) scanValues(columns []string) ([]any, error) {
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case usagecleanuptask.FieldFilters:
|
||||||
|
values[i] = new([]byte)
|
||||||
|
case usagecleanuptask.FieldID, usagecleanuptask.FieldCreatedBy, usagecleanuptask.FieldDeletedRows, usagecleanuptask.FieldCanceledBy:
|
||||||
|
values[i] = new(sql.NullInt64)
|
||||||
|
case usagecleanuptask.FieldStatus, usagecleanuptask.FieldErrorMessage:
|
||||||
|
values[i] = new(sql.NullString)
|
||||||
|
case usagecleanuptask.FieldCreatedAt, usagecleanuptask.FieldUpdatedAt, usagecleanuptask.FieldCanceledAt, usagecleanuptask.FieldStartedAt, usagecleanuptask.FieldFinishedAt:
|
||||||
|
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 UsageCleanupTask fields.
|
||||||
|
func (_m *UsageCleanupTask) 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 usagecleanuptask.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 usagecleanuptask.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 usagecleanuptask.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 usagecleanuptask.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 usagecleanuptask.FieldFilters:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field filters", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.Filters); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field filters: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldCreatedBy:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field created_by", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CreatedBy = value.Int64
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldDeletedRows:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field deleted_rows", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.DeletedRows = value.Int64
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldErrorMessage:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field error_message", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.ErrorMessage = new(string)
|
||||||
|
*_m.ErrorMessage = value.String
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldCanceledBy:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field canceled_by", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CanceledBy = new(int64)
|
||||||
|
*_m.CanceledBy = value.Int64
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldCanceledAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field canceled_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.CanceledAt = new(time.Time)
|
||||||
|
*_m.CanceledAt = value.Time
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldStartedAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field started_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.StartedAt = new(time.Time)
|
||||||
|
*_m.StartedAt = value.Time
|
||||||
|
}
|
||||||
|
case usagecleanuptask.FieldFinishedAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field finished_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.FinishedAt = new(time.Time)
|
||||||
|
*_m.FinishedAt = value.Time
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the UsageCleanupTask.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *UsageCleanupTask) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns a builder for updating this UsageCleanupTask.
|
||||||
|
// Note that you need to call UsageCleanupTask.Unwrap() before calling this method if this UsageCleanupTask
|
||||||
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
|
func (_m *UsageCleanupTask) Update() *UsageCleanupTaskUpdateOne {
|
||||||
|
return NewUsageCleanupTaskClient(_m.config).UpdateOne(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the UsageCleanupTask 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 *UsageCleanupTask) Unwrap() *UsageCleanupTask {
|
||||||
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
|
if !ok {
|
||||||
|
panic("ent: UsageCleanupTask is not a transactional entity")
|
||||||
|
}
|
||||||
|
_m.config.driver = _tx.drv
|
||||||
|
return _m
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer.
|
||||||
|
func (_m *UsageCleanupTask) String() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString("UsageCleanupTask(")
|
||||||
|
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("status=")
|
||||||
|
builder.WriteString(_m.Status)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("filters=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Filters))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("created_by=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.CreatedBy))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("deleted_rows=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.DeletedRows))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.ErrorMessage; v != nil {
|
||||||
|
builder.WriteString("error_message=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.CanceledBy; v != nil {
|
||||||
|
builder.WriteString("canceled_by=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.CanceledAt; v != nil {
|
||||||
|
builder.WriteString("canceled_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.StartedAt; v != nil {
|
||||||
|
builder.WriteString("started_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.FinishedAt; v != nil {
|
||||||
|
builder.WriteString("finished_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
|
builder.WriteByte(')')
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTasks is a parsable slice of UsageCleanupTask.
|
||||||
|
type UsageCleanupTasks []*UsageCleanupTask
|
||||||
137
backend/ent/usagecleanuptask/usagecleanuptask.go
Normal file
137
backend/ent/usagecleanuptask/usagecleanuptask.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package usagecleanuptask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Label holds the string label denoting the usagecleanuptask type in the database.
|
||||||
|
Label = "usage_cleanup_task"
|
||||||
|
// 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"
|
||||||
|
// FieldStatus holds the string denoting the status field in the database.
|
||||||
|
FieldStatus = "status"
|
||||||
|
// FieldFilters holds the string denoting the filters field in the database.
|
||||||
|
FieldFilters = "filters"
|
||||||
|
// FieldCreatedBy holds the string denoting the created_by field in the database.
|
||||||
|
FieldCreatedBy = "created_by"
|
||||||
|
// FieldDeletedRows holds the string denoting the deleted_rows field in the database.
|
||||||
|
FieldDeletedRows = "deleted_rows"
|
||||||
|
// FieldErrorMessage holds the string denoting the error_message field in the database.
|
||||||
|
FieldErrorMessage = "error_message"
|
||||||
|
// FieldCanceledBy holds the string denoting the canceled_by field in the database.
|
||||||
|
FieldCanceledBy = "canceled_by"
|
||||||
|
// FieldCanceledAt holds the string denoting the canceled_at field in the database.
|
||||||
|
FieldCanceledAt = "canceled_at"
|
||||||
|
// FieldStartedAt holds the string denoting the started_at field in the database.
|
||||||
|
FieldStartedAt = "started_at"
|
||||||
|
// FieldFinishedAt holds the string denoting the finished_at field in the database.
|
||||||
|
FieldFinishedAt = "finished_at"
|
||||||
|
// Table holds the table name of the usagecleanuptask in the database.
|
||||||
|
Table = "usage_cleanup_tasks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Columns holds all SQL columns for usagecleanuptask fields.
|
||||||
|
var Columns = []string{
|
||||||
|
FieldID,
|
||||||
|
FieldCreatedAt,
|
||||||
|
FieldUpdatedAt,
|
||||||
|
FieldStatus,
|
||||||
|
FieldFilters,
|
||||||
|
FieldCreatedBy,
|
||||||
|
FieldDeletedRows,
|
||||||
|
FieldErrorMessage,
|
||||||
|
FieldCanceledBy,
|
||||||
|
FieldCanceledAt,
|
||||||
|
FieldStartedAt,
|
||||||
|
FieldFinishedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
|
StatusValidator func(string) error
|
||||||
|
// DefaultDeletedRows holds the default value on creation for the "deleted_rows" field.
|
||||||
|
DefaultDeletedRows int64
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the UsageCleanupTask 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByStatus orders the results by the status field.
|
||||||
|
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedBy orders the results by the created_by field.
|
||||||
|
func ByCreatedBy(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedBy, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByDeletedRows orders the results by the deleted_rows field.
|
||||||
|
func ByDeletedRows(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldDeletedRows, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByErrorMessage orders the results by the error_message field.
|
||||||
|
func ByErrorMessage(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldErrorMessage, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCanceledBy orders the results by the canceled_by field.
|
||||||
|
func ByCanceledBy(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCanceledBy, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCanceledAt orders the results by the canceled_at field.
|
||||||
|
func ByCanceledAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCanceledAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByStartedAt orders the results by the started_at field.
|
||||||
|
func ByStartedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldStartedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByFinishedAt orders the results by the finished_at field.
|
||||||
|
func ByFinishedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldFinishedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
620
backend/ent/usagecleanuptask/where.go
Normal file
620
backend/ent/usagecleanuptask/where.go
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package usagecleanuptask
|
||||||
|
|
||||||
|
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.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
|
func IDEQ(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
|
func IDNEQ(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDIn applies the In predicate on the ID field.
|
||||||
|
func IDIn(ids ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
|
func IDNotIn(ids ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGT applies the GT predicate on the ID field.
|
||||||
|
func IDGT(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
|
func IDGTE(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLT applies the LT predicate on the ID field.
|
||||||
|
func IDLT(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
|
func IDLTE(id int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(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.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(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.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
|
||||||
|
func Status(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedBy applies equality check predicate on the "created_by" field. It's identical to CreatedByEQ.
|
||||||
|
func CreatedBy(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRows applies equality check predicate on the "deleted_rows" field. It's identical to DeletedRowsEQ.
|
||||||
|
func DeletedRows(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessage applies equality check predicate on the "error_message" field. It's identical to ErrorMessageEQ.
|
||||||
|
func ErrorMessage(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledBy applies equality check predicate on the "canceled_by" field. It's identical to CanceledByEQ.
|
||||||
|
func CanceledBy(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAt applies equality check predicate on the "canceled_at" field. It's identical to CanceledAtEQ.
|
||||||
|
func CanceledAt(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAt applies equality check predicate on the "started_at" field. It's identical to StartedAtEQ.
|
||||||
|
func StartedAt(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAt applies equality check predicate on the "finished_at" field. It's identical to FinishedAtEQ.
|
||||||
|
func FinishedAt(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtNEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
|
func CreatedAtIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
|
func CreatedAtNotIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
|
func CreatedAtGT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtGTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
|
func CreatedAtLT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtLTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNotIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusEQ applies the EQ predicate on the "status" field.
|
||||||
|
func StatusEQ(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusNEQ applies the NEQ predicate on the "status" field.
|
||||||
|
func StatusNEQ(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusIn applies the In predicate on the "status" field.
|
||||||
|
func StatusIn(vs ...string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldStatus, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusNotIn applies the NotIn predicate on the "status" field.
|
||||||
|
func StatusNotIn(vs ...string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldStatus, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusGT applies the GT predicate on the "status" field.
|
||||||
|
func StatusGT(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusGTE applies the GTE predicate on the "status" field.
|
||||||
|
func StatusGTE(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusLT applies the LT predicate on the "status" field.
|
||||||
|
func StatusLT(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusLTE applies the LTE predicate on the "status" field.
|
||||||
|
func StatusLTE(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusContains applies the Contains predicate on the "status" field.
|
||||||
|
func StatusContains(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldContains(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
|
||||||
|
func StatusHasPrefix(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldHasPrefix(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
|
||||||
|
func StatusHasSuffix(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldHasSuffix(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusEqualFold applies the EqualFold predicate on the "status" field.
|
||||||
|
func StatusEqualFold(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEqualFold(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusContainsFold applies the ContainsFold predicate on the "status" field.
|
||||||
|
func StatusContainsFold(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldContainsFold(FieldStatus, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByEQ applies the EQ predicate on the "created_by" field.
|
||||||
|
func CreatedByEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByNEQ applies the NEQ predicate on the "created_by" field.
|
||||||
|
func CreatedByNEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByIn applies the In predicate on the "created_by" field.
|
||||||
|
func CreatedByIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldCreatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByNotIn applies the NotIn predicate on the "created_by" field.
|
||||||
|
func CreatedByNotIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldCreatedBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByGT applies the GT predicate on the "created_by" field.
|
||||||
|
func CreatedByGT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByGTE applies the GTE predicate on the "created_by" field.
|
||||||
|
func CreatedByGTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByLT applies the LT predicate on the "created_by" field.
|
||||||
|
func CreatedByLT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedByLTE applies the LTE predicate on the "created_by" field.
|
||||||
|
func CreatedByLTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldCreatedBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsEQ applies the EQ predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsNEQ applies the NEQ predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsNEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsIn applies the In predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldDeletedRows, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsNotIn applies the NotIn predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsNotIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldDeletedRows, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsGT applies the GT predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsGT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsGTE applies the GTE predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsGTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsLT applies the LT predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsLT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedRowsLTE applies the LTE predicate on the "deleted_rows" field.
|
||||||
|
func DeletedRowsLTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldDeletedRows, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageEQ applies the EQ predicate on the "error_message" field.
|
||||||
|
func ErrorMessageEQ(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageNEQ applies the NEQ predicate on the "error_message" field.
|
||||||
|
func ErrorMessageNEQ(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageIn applies the In predicate on the "error_message" field.
|
||||||
|
func ErrorMessageIn(vs ...string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldErrorMessage, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageNotIn applies the NotIn predicate on the "error_message" field.
|
||||||
|
func ErrorMessageNotIn(vs ...string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldErrorMessage, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageGT applies the GT predicate on the "error_message" field.
|
||||||
|
func ErrorMessageGT(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageGTE applies the GTE predicate on the "error_message" field.
|
||||||
|
func ErrorMessageGTE(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageLT applies the LT predicate on the "error_message" field.
|
||||||
|
func ErrorMessageLT(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageLTE applies the LTE predicate on the "error_message" field.
|
||||||
|
func ErrorMessageLTE(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageContains applies the Contains predicate on the "error_message" field.
|
||||||
|
func ErrorMessageContains(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldContains(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageHasPrefix applies the HasPrefix predicate on the "error_message" field.
|
||||||
|
func ErrorMessageHasPrefix(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldHasPrefix(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageHasSuffix applies the HasSuffix predicate on the "error_message" field.
|
||||||
|
func ErrorMessageHasSuffix(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldHasSuffix(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageIsNil applies the IsNil predicate on the "error_message" field.
|
||||||
|
func ErrorMessageIsNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIsNull(FieldErrorMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageNotNil applies the NotNil predicate on the "error_message" field.
|
||||||
|
func ErrorMessageNotNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotNull(FieldErrorMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageEqualFold applies the EqualFold predicate on the "error_message" field.
|
||||||
|
func ErrorMessageEqualFold(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEqualFold(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorMessageContainsFold applies the ContainsFold predicate on the "error_message" field.
|
||||||
|
func ErrorMessageContainsFold(v string) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldContainsFold(FieldErrorMessage, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByEQ applies the EQ predicate on the "canceled_by" field.
|
||||||
|
func CanceledByEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByNEQ applies the NEQ predicate on the "canceled_by" field.
|
||||||
|
func CanceledByNEQ(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByIn applies the In predicate on the "canceled_by" field.
|
||||||
|
func CanceledByIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldCanceledBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByNotIn applies the NotIn predicate on the "canceled_by" field.
|
||||||
|
func CanceledByNotIn(vs ...int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldCanceledBy, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByGT applies the GT predicate on the "canceled_by" field.
|
||||||
|
func CanceledByGT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByGTE applies the GTE predicate on the "canceled_by" field.
|
||||||
|
func CanceledByGTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByLT applies the LT predicate on the "canceled_by" field.
|
||||||
|
func CanceledByLT(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByLTE applies the LTE predicate on the "canceled_by" field.
|
||||||
|
func CanceledByLTE(v int64) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldCanceledBy, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByIsNil applies the IsNil predicate on the "canceled_by" field.
|
||||||
|
func CanceledByIsNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIsNull(FieldCanceledBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledByNotNil applies the NotNil predicate on the "canceled_by" field.
|
||||||
|
func CanceledByNotNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotNull(FieldCanceledBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtEQ applies the EQ predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtNEQ applies the NEQ predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtNEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtIn applies the In predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldCanceledAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtNotIn applies the NotIn predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtNotIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldCanceledAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtGT applies the GT predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtGT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtGTE applies the GTE predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtGTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtLT applies the LT predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtLT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtLTE applies the LTE predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtLTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldCanceledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtIsNil applies the IsNil predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtIsNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIsNull(FieldCanceledAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledAtNotNil applies the NotNil predicate on the "canceled_at" field.
|
||||||
|
func CanceledAtNotNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotNull(FieldCanceledAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtEQ applies the EQ predicate on the "started_at" field.
|
||||||
|
func StartedAtEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtNEQ applies the NEQ predicate on the "started_at" field.
|
||||||
|
func StartedAtNEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtIn applies the In predicate on the "started_at" field.
|
||||||
|
func StartedAtIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldStartedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtNotIn applies the NotIn predicate on the "started_at" field.
|
||||||
|
func StartedAtNotIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldStartedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtGT applies the GT predicate on the "started_at" field.
|
||||||
|
func StartedAtGT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtGTE applies the GTE predicate on the "started_at" field.
|
||||||
|
func StartedAtGTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtLT applies the LT predicate on the "started_at" field.
|
||||||
|
func StartedAtLT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtLTE applies the LTE predicate on the "started_at" field.
|
||||||
|
func StartedAtLTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldStartedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtIsNil applies the IsNil predicate on the "started_at" field.
|
||||||
|
func StartedAtIsNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIsNull(FieldStartedAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedAtNotNil applies the NotNil predicate on the "started_at" field.
|
||||||
|
func StartedAtNotNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotNull(FieldStartedAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtEQ applies the EQ predicate on the "finished_at" field.
|
||||||
|
func FinishedAtEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldEQ(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtNEQ applies the NEQ predicate on the "finished_at" field.
|
||||||
|
func FinishedAtNEQ(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNEQ(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtIn applies the In predicate on the "finished_at" field.
|
||||||
|
func FinishedAtIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIn(FieldFinishedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtNotIn applies the NotIn predicate on the "finished_at" field.
|
||||||
|
func FinishedAtNotIn(vs ...time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotIn(FieldFinishedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtGT applies the GT predicate on the "finished_at" field.
|
||||||
|
func FinishedAtGT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGT(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtGTE applies the GTE predicate on the "finished_at" field.
|
||||||
|
func FinishedAtGTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldGTE(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtLT applies the LT predicate on the "finished_at" field.
|
||||||
|
func FinishedAtLT(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLT(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtLTE applies the LTE predicate on the "finished_at" field.
|
||||||
|
func FinishedAtLTE(v time.Time) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldLTE(FieldFinishedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtIsNil applies the IsNil predicate on the "finished_at" field.
|
||||||
|
func FinishedAtIsNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldIsNull(FieldFinishedAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishedAtNotNil applies the NotNil predicate on the "finished_at" field.
|
||||||
|
func FinishedAtNotNil() predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.FieldNotNull(FieldFinishedAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// And groups predicates with the AND operator between them.
|
||||||
|
func And(predicates ...predicate.UsageCleanupTask) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.AndPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or groups predicates with the OR operator between them.
|
||||||
|
func Or(predicates ...predicate.UsageCleanupTask) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.OrPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not applies the not operator on the given predicate.
|
||||||
|
func Not(p predicate.UsageCleanupTask) predicate.UsageCleanupTask {
|
||||||
|
return predicate.UsageCleanupTask(sql.NotPredicates(p))
|
||||||
|
}
|
||||||
1190
backend/ent/usagecleanuptask_create.go
Normal file
1190
backend/ent/usagecleanuptask_create.go
Normal file
File diff suppressed because it is too large
Load Diff
88
backend/ent/usagecleanuptask_delete.go
Normal file
88
backend/ent/usagecleanuptask_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/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageCleanupTaskDelete is the builder for deleting a UsageCleanupTask entity.
|
||||||
|
type UsageCleanupTaskDelete struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *UsageCleanupTaskMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UsageCleanupTaskDelete builder.
|
||||||
|
func (_d *UsageCleanupTaskDelete) Where(ps ...predicate.UsageCleanupTask) *UsageCleanupTaskDelete {
|
||||||
|
_d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
|
func (_d *UsageCleanupTaskDelete) 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 *UsageCleanupTaskDelete) ExecX(ctx context.Context) int {
|
||||||
|
n, err := _d.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_d *UsageCleanupTaskDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
|
_spec := sqlgraph.NewDeleteSpec(usagecleanuptask.Table, sqlgraph.NewFieldSpec(usagecleanuptask.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTaskDeleteOne is the builder for deleting a single UsageCleanupTask entity.
|
||||||
|
type UsageCleanupTaskDeleteOne struct {
|
||||||
|
_d *UsageCleanupTaskDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UsageCleanupTaskDelete builder.
|
||||||
|
func (_d *UsageCleanupTaskDeleteOne) Where(ps ...predicate.UsageCleanupTask) *UsageCleanupTaskDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query.
|
||||||
|
func (_d *UsageCleanupTaskDeleteOne) Exec(ctx context.Context) error {
|
||||||
|
n, err := _d._d.Exec(ctx)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case n == 0:
|
||||||
|
return &NotFoundError{usagecleanuptask.Label}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *UsageCleanupTaskDeleteOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
564
backend/ent/usagecleanuptask_query.go
Normal file
564
backend/ent/usagecleanuptask_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/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageCleanupTaskQuery is the builder for querying UsageCleanupTask entities.
|
||||||
|
type UsageCleanupTaskQuery struct {
|
||||||
|
config
|
||||||
|
ctx *QueryContext
|
||||||
|
order []usagecleanuptask.OrderOption
|
||||||
|
inters []Interceptor
|
||||||
|
predicates []predicate.UsageCleanupTask
|
||||||
|
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 UsageCleanupTaskQuery builder.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Where(ps ...predicate.UsageCleanupTask) *UsageCleanupTaskQuery {
|
||||||
|
_q.predicates = append(_q.predicates, ps...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the number of records to be returned by this query.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Limit(limit int) *UsageCleanupTaskQuery {
|
||||||
|
_q.ctx.Limit = &limit
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset to start from.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Offset(offset int) *UsageCleanupTaskQuery {
|
||||||
|
_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 *UsageCleanupTaskQuery) Unique(unique bool) *UsageCleanupTaskQuery {
|
||||||
|
_q.ctx.Unique = &unique
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order specifies how the records should be ordered.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Order(o ...usagecleanuptask.OrderOption) *UsageCleanupTaskQuery {
|
||||||
|
_q.order = append(_q.order, o...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first UsageCleanupTask entity from the query.
|
||||||
|
// Returns a *NotFoundError when no UsageCleanupTask was found.
|
||||||
|
func (_q *UsageCleanupTaskQuery) First(ctx context.Context) (*UsageCleanupTask, 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{usagecleanuptask.Label}
|
||||||
|
}
|
||||||
|
return nodes[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstX is like First, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) FirstX(ctx context.Context) *UsageCleanupTask {
|
||||||
|
node, err := _q.First(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstID returns the first UsageCleanupTask ID from the query.
|
||||||
|
// Returns a *NotFoundError when no UsageCleanupTask ID was found.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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{usagecleanuptask.Label}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ids[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) FirstIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.FirstID(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only returns a single UsageCleanupTask entity found by the query, ensuring it only returns one.
|
||||||
|
// Returns a *NotSingularError when more than one UsageCleanupTask entity is found.
|
||||||
|
// Returns a *NotFoundError when no UsageCleanupTask entities are found.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Only(ctx context.Context) (*UsageCleanupTask, 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{usagecleanuptask.Label}
|
||||||
|
default:
|
||||||
|
return nil, &NotSingularError{usagecleanuptask.Label}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyX is like Only, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) OnlyX(ctx context.Context) *UsageCleanupTask {
|
||||||
|
node, err := _q.Only(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyID is like Only, but returns the only UsageCleanupTask ID in the query.
|
||||||
|
// Returns a *NotSingularError when more than one UsageCleanupTask ID is found.
|
||||||
|
// Returns a *NotFoundError when no entities are found.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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{usagecleanuptask.Label}
|
||||||
|
default:
|
||||||
|
err = &NotSingularError{usagecleanuptask.Label}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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 UsageCleanupTasks.
|
||||||
|
func (_q *UsageCleanupTaskQuery) All(ctx context.Context) ([]*UsageCleanupTask, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qr := querierAll[[]*UsageCleanupTask, *UsageCleanupTaskQuery]()
|
||||||
|
return withInterceptors[[]*UsageCleanupTask](ctx, _q, qr, _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllX is like All, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) AllX(ctx context.Context) []*UsageCleanupTask {
|
||||||
|
nodes, err := _q.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs executes the query and returns a list of UsageCleanupTask IDs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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(usagecleanuptask.FieldID).Scan(ctx, &ids); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDsX is like IDs, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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 *UsageCleanupTaskQuery) 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[*UsageCleanupTaskQuery](), _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountX is like Count, but panics if an error occurs.
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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 *UsageCleanupTaskQuery) 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 *UsageCleanupTaskQuery) ExistX(ctx context.Context) bool {
|
||||||
|
exist, err := _q.Exist(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the UsageCleanupTaskQuery 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 *UsageCleanupTaskQuery) Clone() *UsageCleanupTaskQuery {
|
||||||
|
if _q == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &UsageCleanupTaskQuery{
|
||||||
|
config: _q.config,
|
||||||
|
ctx: _q.ctx.Clone(),
|
||||||
|
order: append([]usagecleanuptask.OrderOption{}, _q.order...),
|
||||||
|
inters: append([]Interceptor{}, _q.inters...),
|
||||||
|
predicates: append([]predicate.UsageCleanupTask{}, _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.UsageCleanupTask.Query().
|
||||||
|
// GroupBy(usagecleanuptask.FieldCreatedAt).
|
||||||
|
// Aggregate(ent.Count()).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *UsageCleanupTaskQuery) GroupBy(field string, fields ...string) *UsageCleanupTaskGroupBy {
|
||||||
|
_q.ctx.Fields = append([]string{field}, fields...)
|
||||||
|
grbuild := &UsageCleanupTaskGroupBy{build: _q}
|
||||||
|
grbuild.flds = &_q.ctx.Fields
|
||||||
|
grbuild.label = usagecleanuptask.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.UsageCleanupTask.Query().
|
||||||
|
// Select(usagecleanuptask.FieldCreatedAt).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *UsageCleanupTaskQuery) Select(fields ...string) *UsageCleanupTaskSelect {
|
||||||
|
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||||
|
sbuild := &UsageCleanupTaskSelect{UsageCleanupTaskQuery: _q}
|
||||||
|
sbuild.label = usagecleanuptask.Label
|
||||||
|
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||||
|
return sbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate returns a UsageCleanupTaskSelect configured with the given aggregations.
|
||||||
|
func (_q *UsageCleanupTaskQuery) Aggregate(fns ...AggregateFunc) *UsageCleanupTaskSelect {
|
||||||
|
return _q.Select().Aggregate(fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *UsageCleanupTaskQuery) 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 !usagecleanuptask.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 *UsageCleanupTaskQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UsageCleanupTask, error) {
|
||||||
|
var (
|
||||||
|
nodes = []*UsageCleanupTask{}
|
||||||
|
_spec = _q.querySpec()
|
||||||
|
)
|
||||||
|
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||||
|
return (*UsageCleanupTask).scanValues(nil, columns)
|
||||||
|
}
|
||||||
|
_spec.Assign = func(columns []string, values []any) error {
|
||||||
|
node := &UsageCleanupTask{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 *UsageCleanupTaskQuery) 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 *UsageCleanupTaskQuery) querySpec() *sqlgraph.QuerySpec {
|
||||||
|
_spec := sqlgraph.NewQuerySpec(usagecleanuptask.Table, usagecleanuptask.Columns, sqlgraph.NewFieldSpec(usagecleanuptask.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, usagecleanuptask.FieldID)
|
||||||
|
for i := range fields {
|
||||||
|
if fields[i] != usagecleanuptask.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 *UsageCleanupTaskQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||||
|
builder := sql.Dialect(_q.driver.Dialect())
|
||||||
|
t1 := builder.Table(usagecleanuptask.Table)
|
||||||
|
columns := _q.ctx.Fields
|
||||||
|
if len(columns) == 0 {
|
||||||
|
columns = usagecleanuptask.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 *UsageCleanupTaskQuery) ForUpdate(opts ...sql.LockOption) *UsageCleanupTaskQuery {
|
||||||
|
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 *UsageCleanupTaskQuery) ForShare(opts ...sql.LockOption) *UsageCleanupTaskQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForShare(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTaskGroupBy is the group-by builder for UsageCleanupTask entities.
|
||||||
|
type UsageCleanupTaskGroupBy struct {
|
||||||
|
selector
|
||||||
|
build *UsageCleanupTaskQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the group-by query.
|
||||||
|
func (_g *UsageCleanupTaskGroupBy) Aggregate(fns ...AggregateFunc) *UsageCleanupTaskGroupBy {
|
||||||
|
_g.fns = append(_g.fns, fns...)
|
||||||
|
return _g
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_g *UsageCleanupTaskGroupBy) 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[*UsageCleanupTaskQuery, *UsageCleanupTaskGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_g *UsageCleanupTaskGroupBy) sqlScan(ctx context.Context, root *UsageCleanupTaskQuery, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTaskSelect is the builder for selecting fields of UsageCleanupTask entities.
|
||||||
|
type UsageCleanupTaskSelect struct {
|
||||||
|
*UsageCleanupTaskQuery
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the selector query.
|
||||||
|
func (_s *UsageCleanupTaskSelect) Aggregate(fns ...AggregateFunc) *UsageCleanupTaskSelect {
|
||||||
|
_s.fns = append(_s.fns, fns...)
|
||||||
|
return _s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_s *UsageCleanupTaskSelect) 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[*UsageCleanupTaskQuery, *UsageCleanupTaskSelect](ctx, _s.UsageCleanupTaskQuery, _s, _s.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_s *UsageCleanupTaskSelect) sqlScan(ctx context.Context, root *UsageCleanupTaskQuery, 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)
|
||||||
|
}
|
||||||
702
backend/ent/usagecleanuptask_update.go
Normal file
702
backend/ent/usagecleanuptask_update.go
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package ent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"entgo.io/ent/dialect/sql/sqljson"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsageCleanupTaskUpdate is the builder for updating UsageCleanupTask entities.
|
||||||
|
type UsageCleanupTaskUpdate struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *UsageCleanupTaskMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UsageCleanupTaskUpdate builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) Where(ps ...predicate.UsageCleanupTask) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetUpdatedAt(v time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus sets the "status" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetStatus(v string) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetStatus(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableStatus(v *string) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStatus(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFilters sets the "filters" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetFilters(v json.RawMessage) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetFilters(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendFilters appends value to the "filters" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) AppendFilters(v json.RawMessage) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.AppendFilters(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedBy sets the "created_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetCreatedBy(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ResetCreatedBy()
|
||||||
|
_u.mutation.SetCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableCreatedBy(v *int64) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCreatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCreatedBy adds value to the "created_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) AddCreatedBy(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.AddCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeletedRows sets the "deleted_rows" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetDeletedRows(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ResetDeletedRows()
|
||||||
|
_u.mutation.SetDeletedRows(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDeletedRows sets the "deleted_rows" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableDeletedRows(v *int64) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDeletedRows(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDeletedRows adds value to the "deleted_rows" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) AddDeletedRows(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.AddDeletedRows(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorMessage sets the "error_message" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetErrorMessage(v string) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetErrorMessage(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableErrorMessage sets the "error_message" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableErrorMessage(v *string) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetErrorMessage(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearErrorMessage clears the value of the "error_message" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) ClearErrorMessage() *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ClearErrorMessage()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanceledBy sets the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetCanceledBy(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ResetCanceledBy()
|
||||||
|
_u.mutation.SetCanceledBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCanceledBy sets the "canceled_by" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableCanceledBy(v *int64) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCanceledBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCanceledBy adds value to the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) AddCanceledBy(v int64) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.AddCanceledBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCanceledBy clears the value of the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) ClearCanceledBy() *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ClearCanceledBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanceledAt sets the "canceled_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetCanceledAt(v time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetCanceledAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCanceledAt sets the "canceled_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableCanceledAt(v *time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCanceledAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCanceledAt clears the value of the "canceled_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) ClearCanceledAt() *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ClearCanceledAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartedAt sets the "started_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetStartedAt(v time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetStartedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStartedAt sets the "started_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableStartedAt(v *time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStartedAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStartedAt clears the value of the "started_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) ClearStartedAt() *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ClearStartedAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFinishedAt sets the "finished_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetFinishedAt(v time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.SetFinishedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableFinishedAt sets the "finished_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) SetNillableFinishedAt(v *time.Time) *UsageCleanupTaskUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetFinishedAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearFinishedAt clears the value of the "finished_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) ClearFinishedAt() *UsageCleanupTaskUpdate {
|
||||||
|
_u.mutation.ClearFinishedAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the UsageCleanupTaskMutation object of the builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) Mutation() *UsageCleanupTaskMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) 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 *UsageCleanupTaskUpdate) SaveX(ctx context.Context) int {
|
||||||
|
affected, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) 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 *UsageCleanupTaskUpdate) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := usagecleanuptask.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdate) check() error {
|
||||||
|
if v, ok := _u.mutation.Status(); ok {
|
||||||
|
if err := usagecleanuptask.StatusValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "UsageCleanupTask.status": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *UsageCleanupTaskUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(usagecleanuptask.Table, usagecleanuptask.Columns, sqlgraph.NewFieldSpec(usagecleanuptask.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(usagecleanuptask.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldStatus, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Filters(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldFilters, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedFilters(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, usagecleanuptask.FieldFilters, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.DeletedRows(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldDeletedRows, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedDeletedRows(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldDeletedRows, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ErrorMessage(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldErrorMessage, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ErrorMessageCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldErrorMessage, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CanceledBy(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCanceledBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCanceledBy(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldCanceledBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CanceledByCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldCanceledBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CanceledAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCanceledAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CanceledAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldCanceledAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.StartedAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldStartedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.StartedAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldStartedAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.FinishedAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldFinishedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.FinishedAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldFinishedAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{usagecleanuptask.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupTaskUpdateOne is the builder for updating a single UsageCleanupTask entity.
|
||||||
|
type UsageCleanupTaskUpdateOne struct {
|
||||||
|
config
|
||||||
|
fields []string
|
||||||
|
hooks []Hook
|
||||||
|
mutation *UsageCleanupTaskMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetUpdatedAt(v time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus sets the "status" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetStatus(v string) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetStatus(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableStatus(v *string) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStatus(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFilters sets the "filters" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetFilters(v json.RawMessage) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetFilters(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendFilters appends value to the "filters" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) AppendFilters(v json.RawMessage) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.AppendFilters(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCreatedBy sets the "created_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetCreatedBy(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ResetCreatedBy()
|
||||||
|
_u.mutation.SetCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableCreatedBy(v *int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCreatedBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCreatedBy adds value to the "created_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) AddCreatedBy(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.AddCreatedBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeletedRows sets the "deleted_rows" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetDeletedRows(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ResetDeletedRows()
|
||||||
|
_u.mutation.SetDeletedRows(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDeletedRows sets the "deleted_rows" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableDeletedRows(v *int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDeletedRows(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDeletedRows adds value to the "deleted_rows" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) AddDeletedRows(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.AddDeletedRows(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorMessage sets the "error_message" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetErrorMessage(v string) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetErrorMessage(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableErrorMessage sets the "error_message" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableErrorMessage(v *string) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetErrorMessage(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearErrorMessage clears the value of the "error_message" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) ClearErrorMessage() *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ClearErrorMessage()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanceledBy sets the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetCanceledBy(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ResetCanceledBy()
|
||||||
|
_u.mutation.SetCanceledBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCanceledBy sets the "canceled_by" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableCanceledBy(v *int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCanceledBy(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCanceledBy adds value to the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) AddCanceledBy(v int64) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.AddCanceledBy(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCanceledBy clears the value of the "canceled_by" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) ClearCanceledBy() *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ClearCanceledBy()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanceledAt sets the "canceled_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetCanceledAt(v time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetCanceledAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableCanceledAt sets the "canceled_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableCanceledAt(v *time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetCanceledAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCanceledAt clears the value of the "canceled_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) ClearCanceledAt() *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ClearCanceledAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartedAt sets the "started_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetStartedAt(v time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetStartedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableStartedAt sets the "started_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableStartedAt(v *time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetStartedAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStartedAt clears the value of the "started_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) ClearStartedAt() *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ClearStartedAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFinishedAt sets the "finished_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetFinishedAt(v time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.SetFinishedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableFinishedAt sets the "finished_at" field if the given value is not nil.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SetNillableFinishedAt(v *time.Time) *UsageCleanupTaskUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetFinishedAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearFinishedAt clears the value of the "finished_at" field.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) ClearFinishedAt() *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.mutation.ClearFinishedAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the UsageCleanupTaskMutation object of the builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) Mutation() *UsageCleanupTaskMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UsageCleanupTaskUpdate builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) Where(ps ...predicate.UsageCleanupTask) *UsageCleanupTaskUpdateOne {
|
||||||
|
_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 *UsageCleanupTaskUpdateOne) Select(field string, fields ...string) *UsageCleanupTaskUpdateOne {
|
||||||
|
_u.fields = append([]string{field}, fields...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the updated UsageCleanupTask entity.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) Save(ctx context.Context) (*UsageCleanupTask, error) {
|
||||||
|
_u.defaults()
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) SaveX(ctx context.Context) *UsageCleanupTask {
|
||||||
|
node, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query on the entity.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) 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 *UsageCleanupTaskUpdateOne) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := usagecleanuptask.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) check() error {
|
||||||
|
if v, ok := _u.mutation.Status(); ok {
|
||||||
|
if err := usagecleanuptask.StatusValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "UsageCleanupTask.status": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *UsageCleanupTaskUpdateOne) sqlSave(ctx context.Context) (_node *UsageCleanupTask, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(usagecleanuptask.Table, usagecleanuptask.Columns, sqlgraph.NewFieldSpec(usagecleanuptask.FieldID, field.TypeInt64))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
|
if !ok {
|
||||||
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UsageCleanupTask.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, usagecleanuptask.FieldID)
|
||||||
|
for _, f := range fields {
|
||||||
|
if !usagecleanuptask.ValidColumn(f) {
|
||||||
|
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
if f != usagecleanuptask.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(usagecleanuptask.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldStatus, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Filters(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldFilters, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedFilters(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, usagecleanuptask.FieldFilters, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldCreatedBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.DeletedRows(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldDeletedRows, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedDeletedRows(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldDeletedRows, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ErrorMessage(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldErrorMessage, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ErrorMessageCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldErrorMessage, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CanceledBy(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCanceledBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedCanceledBy(); ok {
|
||||||
|
_spec.AddField(usagecleanuptask.FieldCanceledBy, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CanceledByCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldCanceledBy, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CanceledAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldCanceledAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.CanceledAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldCanceledAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.StartedAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldStartedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.StartedAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldStartedAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.FinishedAt(); ok {
|
||||||
|
_spec.SetField(usagecleanuptask.FieldFinishedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.FinishedAtCleared() {
|
||||||
|
_spec.ClearField(usagecleanuptask.FieldFinishedAt, field.TypeTime)
|
||||||
|
}
|
||||||
|
_node = &UsageCleanupTask{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{usagecleanuptask.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
@@ -62,6 +62,8 @@ type UsageLog struct {
|
|||||||
ActualCost float64 `json:"actual_cost,omitempty"`
|
ActualCost float64 `json:"actual_cost,omitempty"`
|
||||||
// RateMultiplier holds the value of the "rate_multiplier" field.
|
// RateMultiplier holds the value of the "rate_multiplier" field.
|
||||||
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
|
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
|
||||||
|
// AccountRateMultiplier holds the value of the "account_rate_multiplier" field.
|
||||||
|
AccountRateMultiplier *float64 `json:"account_rate_multiplier,omitempty"`
|
||||||
// BillingType holds the value of the "billing_type" field.
|
// BillingType holds the value of the "billing_type" field.
|
||||||
BillingType int8 `json:"billing_type,omitempty"`
|
BillingType int8 `json:"billing_type,omitempty"`
|
||||||
// Stream holds the value of the "stream" field.
|
// Stream holds the value of the "stream" field.
|
||||||
@@ -165,7 +167,7 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
|
|||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
case usagelog.FieldStream:
|
case usagelog.FieldStream:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier:
|
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier, usagelog.FieldAccountRateMultiplier:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount:
|
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldImageCount:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
@@ -316,6 +318,13 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.RateMultiplier = value.Float64
|
_m.RateMultiplier = value.Float64
|
||||||
}
|
}
|
||||||
|
case usagelog.FieldAccountRateMultiplier:
|
||||||
|
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field account_rate_multiplier", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.AccountRateMultiplier = new(float64)
|
||||||
|
*_m.AccountRateMultiplier = value.Float64
|
||||||
|
}
|
||||||
case usagelog.FieldBillingType:
|
case usagelog.FieldBillingType:
|
||||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field billing_type", values[i])
|
return fmt.Errorf("unexpected type %T for field billing_type", values[i])
|
||||||
@@ -500,6 +509,11 @@ func (_m *UsageLog) String() string {
|
|||||||
builder.WriteString("rate_multiplier=")
|
builder.WriteString("rate_multiplier=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
|
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
if v := _m.AccountRateMultiplier; v != nil {
|
||||||
|
builder.WriteString("account_rate_multiplier=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
builder.WriteString("billing_type=")
|
builder.WriteString("billing_type=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.BillingType))
|
builder.WriteString(fmt.Sprintf("%v", _m.BillingType))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ const (
|
|||||||
FieldActualCost = "actual_cost"
|
FieldActualCost = "actual_cost"
|
||||||
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
||||||
FieldRateMultiplier = "rate_multiplier"
|
FieldRateMultiplier = "rate_multiplier"
|
||||||
|
// FieldAccountRateMultiplier holds the string denoting the account_rate_multiplier field in the database.
|
||||||
|
FieldAccountRateMultiplier = "account_rate_multiplier"
|
||||||
// FieldBillingType holds the string denoting the billing_type field in the database.
|
// FieldBillingType holds the string denoting the billing_type field in the database.
|
||||||
FieldBillingType = "billing_type"
|
FieldBillingType = "billing_type"
|
||||||
// FieldStream holds the string denoting the stream field in the database.
|
// FieldStream holds the string denoting the stream field in the database.
|
||||||
@@ -144,6 +146,7 @@ var Columns = []string{
|
|||||||
FieldTotalCost,
|
FieldTotalCost,
|
||||||
FieldActualCost,
|
FieldActualCost,
|
||||||
FieldRateMultiplier,
|
FieldRateMultiplier,
|
||||||
|
FieldAccountRateMultiplier,
|
||||||
FieldBillingType,
|
FieldBillingType,
|
||||||
FieldStream,
|
FieldStream,
|
||||||
FieldDurationMs,
|
FieldDurationMs,
|
||||||
@@ -320,6 +323,11 @@ func ByRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
|
return sql.OrderByField(FieldRateMultiplier, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByAccountRateMultiplier orders the results by the account_rate_multiplier field.
|
||||||
|
func ByAccountRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldAccountRateMultiplier, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByBillingType orders the results by the billing_type field.
|
// ByBillingType orders the results by the billing_type field.
|
||||||
func ByBillingType(opts ...sql.OrderTermOption) OrderOption {
|
func ByBillingType(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldBillingType, opts...).ToFunc()
|
return sql.OrderByField(FieldBillingType, opts...).ToFunc()
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ func RateMultiplier(v float64) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldEQ(FieldRateMultiplier, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldRateMultiplier, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplier applies equality check predicate on the "account_rate_multiplier" field. It's identical to AccountRateMultiplierEQ.
|
||||||
|
func AccountRateMultiplier(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
// BillingType applies equality check predicate on the "billing_type" field. It's identical to BillingTypeEQ.
|
// BillingType applies equality check predicate on the "billing_type" field. It's identical to BillingTypeEQ.
|
||||||
func BillingType(v int8) predicate.UsageLog {
|
func BillingType(v int8) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldBillingType, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingType, v))
|
||||||
@@ -970,6 +975,56 @@ func RateMultiplierLTE(v float64) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldLTE(FieldRateMultiplier, v))
|
return predicate.UsageLog(sql.FieldLTE(FieldRateMultiplier, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierEQ applies the EQ predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierEQ(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierNEQ applies the NEQ predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierNEQ(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierIn applies the In predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierIn(vs ...float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldAccountRateMultiplier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierNotIn applies the NotIn predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierNotIn(vs ...float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldAccountRateMultiplier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierGT applies the GT predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierGT(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierGTE applies the GTE predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierGTE(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierLT applies the LT predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierLT(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierLTE applies the LTE predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierLTE(v float64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldAccountRateMultiplier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierIsNil applies the IsNil predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldAccountRateMultiplier))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountRateMultiplierNotNil applies the NotNil predicate on the "account_rate_multiplier" field.
|
||||||
|
func AccountRateMultiplierNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldAccountRateMultiplier))
|
||||||
|
}
|
||||||
|
|
||||||
// BillingTypeEQ applies the EQ predicate on the "billing_type" field.
|
// BillingTypeEQ applies the EQ predicate on the "billing_type" field.
|
||||||
func BillingTypeEQ(v int8) predicate.UsageLog {
|
func BillingTypeEQ(v int8) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldBillingType, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingType, v))
|
||||||
|
|||||||
@@ -267,6 +267,20 @@ func (_c *UsageLogCreate) SetNillableRateMultiplier(v *float64) *UsageLogCreate
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (_c *UsageLogCreate) SetAccountRateMultiplier(v float64) *UsageLogCreate {
|
||||||
|
_c.mutation.SetAccountRateMultiplier(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAccountRateMultiplier sets the "account_rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableAccountRateMultiplier(v *float64) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetAccountRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (_c *UsageLogCreate) SetBillingType(v int8) *UsageLogCreate {
|
func (_c *UsageLogCreate) SetBillingType(v int8) *UsageLogCreate {
|
||||||
_c.mutation.SetBillingType(v)
|
_c.mutation.SetBillingType(v)
|
||||||
@@ -712,6 +726,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
_spec.SetField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
_node.RateMultiplier = value
|
_node.RateMultiplier = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.AccountRateMultiplier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
|
||||||
|
_node.AccountRateMultiplier = &value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.BillingType(); ok {
|
if value, ok := _c.mutation.BillingType(); ok {
|
||||||
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
||||||
_node.BillingType = value
|
_node.BillingType = value
|
||||||
@@ -1215,6 +1233,30 @@ func (u *UsageLogUpsert) AddRateMultiplier(v float64) *UsageLogUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsert) SetAccountRateMultiplier(v float64) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldAccountRateMultiplier, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountRateMultiplier sets the "account_rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateAccountRateMultiplier() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldAccountRateMultiplier)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccountRateMultiplier adds v to the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsert) AddAccountRateMultiplier(v float64) *UsageLogUpsert {
|
||||||
|
u.Add(usagelog.FieldAccountRateMultiplier, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAccountRateMultiplier clears the value of the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsert) ClearAccountRateMultiplier() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldAccountRateMultiplier)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (u *UsageLogUpsert) SetBillingType(v int8) *UsageLogUpsert {
|
func (u *UsageLogUpsert) SetBillingType(v int8) *UsageLogUpsert {
|
||||||
u.Set(usagelog.FieldBillingType, v)
|
u.Set(usagelog.FieldBillingType, v)
|
||||||
@@ -1795,6 +1837,34 @@ func (u *UsageLogUpsertOne) UpdateRateMultiplier() *UsageLogUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetAccountRateMultiplier(v float64) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetAccountRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccountRateMultiplier adds v to the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertOne) AddAccountRateMultiplier(v float64) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.AddAccountRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountRateMultiplier sets the "account_rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateAccountRateMultiplier() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateAccountRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAccountRateMultiplier clears the value of the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearAccountRateMultiplier() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearAccountRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (u *UsageLogUpsertOne) SetBillingType(v int8) *UsageLogUpsertOne {
|
func (u *UsageLogUpsertOne) SetBillingType(v int8) *UsageLogUpsertOne {
|
||||||
return u.Update(func(s *UsageLogUpsert) {
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
@@ -2566,6 +2636,34 @@ func (u *UsageLogUpsertBulk) UpdateRateMultiplier() *UsageLogUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetAccountRateMultiplier(v float64) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetAccountRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccountRateMultiplier adds v to the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertBulk) AddAccountRateMultiplier(v float64) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.AddAccountRateMultiplier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountRateMultiplier sets the "account_rate_multiplier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateAccountRateMultiplier() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateAccountRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAccountRateMultiplier clears the value of the "account_rate_multiplier" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearAccountRateMultiplier() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearAccountRateMultiplier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (u *UsageLogUpsertBulk) SetBillingType(v int8) *UsageLogUpsertBulk {
|
func (u *UsageLogUpsertBulk) SetBillingType(v int8) *UsageLogUpsertBulk {
|
||||||
return u.Update(func(s *UsageLogUpsert) {
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
|||||||
@@ -415,6 +415,33 @@ func (_u *UsageLogUpdate) AddRateMultiplier(v float64) *UsageLogUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdate) SetAccountRateMultiplier(v float64) *UsageLogUpdate {
|
||||||
|
_u.mutation.ResetAccountRateMultiplier()
|
||||||
|
_u.mutation.SetAccountRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAccountRateMultiplier sets the "account_rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableAccountRateMultiplier(v *float64) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAccountRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccountRateMultiplier adds value to the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdate) AddAccountRateMultiplier(v float64) *UsageLogUpdate {
|
||||||
|
_u.mutation.AddAccountRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAccountRateMultiplier clears the value of the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearAccountRateMultiplier() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearAccountRateMultiplier()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (_u *UsageLogUpdate) SetBillingType(v int8) *UsageLogUpdate {
|
func (_u *UsageLogUpdate) SetBillingType(v int8) *UsageLogUpdate {
|
||||||
_u.mutation.ResetBillingType()
|
_u.mutation.ResetBillingType()
|
||||||
@@ -807,6 +834,15 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
||||||
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.AccountRateMultiplier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedAccountRateMultiplier(); ok {
|
||||||
|
_spec.AddField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.AccountRateMultiplierCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.BillingType(); ok {
|
if value, ok := _u.mutation.BillingType(); ok {
|
||||||
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
||||||
}
|
}
|
||||||
@@ -1406,6 +1442,33 @@ func (_u *UsageLogUpdateOne) AddRateMultiplier(v float64) *UsageLogUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccountRateMultiplier sets the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetAccountRateMultiplier(v float64) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ResetAccountRateMultiplier()
|
||||||
|
_u.mutation.SetAccountRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAccountRateMultiplier sets the "account_rate_multiplier" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableAccountRateMultiplier(v *float64) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAccountRateMultiplier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccountRateMultiplier adds value to the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdateOne) AddAccountRateMultiplier(v float64) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.AddAccountRateMultiplier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAccountRateMultiplier clears the value of the "account_rate_multiplier" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearAccountRateMultiplier() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearAccountRateMultiplier()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetBillingType sets the "billing_type" field.
|
// SetBillingType sets the "billing_type" field.
|
||||||
func (_u *UsageLogUpdateOne) SetBillingType(v int8) *UsageLogUpdateOne {
|
func (_u *UsageLogUpdateOne) SetBillingType(v int8) *UsageLogUpdateOne {
|
||||||
_u.mutation.ResetBillingType()
|
_u.mutation.ResetBillingType()
|
||||||
@@ -1828,6 +1891,15 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
|
|||||||
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
if value, ok := _u.mutation.AddedRateMultiplier(); ok {
|
||||||
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
_spec.AddField(usagelog.FieldRateMultiplier, field.TypeFloat64, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.AccountRateMultiplier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedAccountRateMultiplier(); ok {
|
||||||
|
_spec.AddField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.AccountRateMultiplierCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldAccountRateMultiplier, field.TypeFloat64)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.BillingType(); ok {
|
if value, ok := _u.mutation.BillingType(); ok {
|
||||||
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
_spec.SetField(usagelog.FieldBillingType, field.TypeInt8, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ type User struct {
|
|||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
// Notes holds the value of the "notes" field.
|
// Notes holds the value of the "notes" field.
|
||||||
Notes string `json:"notes,omitempty"`
|
Notes string `json:"notes,omitempty"`
|
||||||
|
// TotpSecretEncrypted holds the value of the "totp_secret_encrypted" field.
|
||||||
|
TotpSecretEncrypted *string `json:"totp_secret_encrypted,omitempty"`
|
||||||
|
// TotpEnabled holds the value of the "totp_enabled" field.
|
||||||
|
TotpEnabled bool `json:"totp_enabled,omitempty"`
|
||||||
|
// TotpEnabledAt holds the value of the "totp_enabled_at" field.
|
||||||
|
TotpEnabledAt *time.Time `json:"totp_enabled_at,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
// The values are being populated by the UserQuery when eager-loading is set.
|
// The values are being populated by the UserQuery when eager-loading is set.
|
||||||
Edges UserEdges `json:"edges"`
|
Edges UserEdges `json:"edges"`
|
||||||
@@ -55,6 +61,8 @@ type UserEdges struct {
|
|||||||
Subscriptions []*UserSubscription `json:"subscriptions,omitempty"`
|
Subscriptions []*UserSubscription `json:"subscriptions,omitempty"`
|
||||||
// AssignedSubscriptions holds the value of the assigned_subscriptions edge.
|
// AssignedSubscriptions holds the value of the assigned_subscriptions edge.
|
||||||
AssignedSubscriptions []*UserSubscription `json:"assigned_subscriptions,omitempty"`
|
AssignedSubscriptions []*UserSubscription `json:"assigned_subscriptions,omitempty"`
|
||||||
|
// AnnouncementReads holds the value of the announcement_reads edge.
|
||||||
|
AnnouncementReads []*AnnouncementRead `json:"announcement_reads,omitempty"`
|
||||||
// AllowedGroups holds the value of the allowed_groups edge.
|
// AllowedGroups holds the value of the allowed_groups edge.
|
||||||
AllowedGroups []*Group `json:"allowed_groups,omitempty"`
|
AllowedGroups []*Group `json:"allowed_groups,omitempty"`
|
||||||
// UsageLogs holds the value of the usage_logs edge.
|
// UsageLogs holds the value of the usage_logs edge.
|
||||||
@@ -67,7 +75,7 @@ type UserEdges struct {
|
|||||||
UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"`
|
UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"`
|
||||||
// loadedTypes holds the information for reporting if a
|
// loadedTypes holds the information for reporting if a
|
||||||
// type was loaded (or requested) in eager-loading or not.
|
// type was loaded (or requested) in eager-loading or not.
|
||||||
loadedTypes [9]bool
|
loadedTypes [10]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIKeysOrErr returns the APIKeys value or an error if the edge
|
// APIKeysOrErr returns the APIKeys value or an error if the edge
|
||||||
@@ -106,10 +114,19 @@ func (e UserEdges) AssignedSubscriptionsOrErr() ([]*UserSubscription, error) {
|
|||||||
return nil, &NotLoadedError{edge: "assigned_subscriptions"}
|
return nil, &NotLoadedError{edge: "assigned_subscriptions"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnouncementReadsOrErr returns the AnnouncementReads value or an error if the edge
|
||||||
|
// was not loaded in eager-loading.
|
||||||
|
func (e UserEdges) AnnouncementReadsOrErr() ([]*AnnouncementRead, error) {
|
||||||
|
if e.loadedTypes[4] {
|
||||||
|
return e.AnnouncementReads, nil
|
||||||
|
}
|
||||||
|
return nil, &NotLoadedError{edge: "announcement_reads"}
|
||||||
|
}
|
||||||
|
|
||||||
// AllowedGroupsOrErr returns the AllowedGroups value or an error if the edge
|
// AllowedGroupsOrErr returns the AllowedGroups value or an error if the edge
|
||||||
// was not loaded in eager-loading.
|
// was not loaded in eager-loading.
|
||||||
func (e UserEdges) AllowedGroupsOrErr() ([]*Group, error) {
|
func (e UserEdges) AllowedGroupsOrErr() ([]*Group, error) {
|
||||||
if e.loadedTypes[4] {
|
if e.loadedTypes[5] {
|
||||||
return e.AllowedGroups, nil
|
return e.AllowedGroups, nil
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "allowed_groups"}
|
return nil, &NotLoadedError{edge: "allowed_groups"}
|
||||||
@@ -118,7 +135,7 @@ func (e UserEdges) AllowedGroupsOrErr() ([]*Group, error) {
|
|||||||
// UsageLogsOrErr returns the UsageLogs value or an error if the edge
|
// UsageLogsOrErr returns the UsageLogs value or an error if the edge
|
||||||
// was not loaded in eager-loading.
|
// was not loaded in eager-loading.
|
||||||
func (e UserEdges) UsageLogsOrErr() ([]*UsageLog, error) {
|
func (e UserEdges) UsageLogsOrErr() ([]*UsageLog, error) {
|
||||||
if e.loadedTypes[5] {
|
if e.loadedTypes[6] {
|
||||||
return e.UsageLogs, nil
|
return e.UsageLogs, nil
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "usage_logs"}
|
return nil, &NotLoadedError{edge: "usage_logs"}
|
||||||
@@ -127,7 +144,7 @@ func (e UserEdges) UsageLogsOrErr() ([]*UsageLog, error) {
|
|||||||
// AttributeValuesOrErr returns the AttributeValues value or an error if the edge
|
// AttributeValuesOrErr returns the AttributeValues value or an error if the edge
|
||||||
// was not loaded in eager-loading.
|
// was not loaded in eager-loading.
|
||||||
func (e UserEdges) AttributeValuesOrErr() ([]*UserAttributeValue, error) {
|
func (e UserEdges) AttributeValuesOrErr() ([]*UserAttributeValue, error) {
|
||||||
if e.loadedTypes[6] {
|
if e.loadedTypes[7] {
|
||||||
return e.AttributeValues, nil
|
return e.AttributeValues, nil
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "attribute_values"}
|
return nil, &NotLoadedError{edge: "attribute_values"}
|
||||||
@@ -136,7 +153,7 @@ func (e UserEdges) AttributeValuesOrErr() ([]*UserAttributeValue, error) {
|
|||||||
// PromoCodeUsagesOrErr returns the PromoCodeUsages value or an error if the edge
|
// PromoCodeUsagesOrErr returns the PromoCodeUsages value or an error if the edge
|
||||||
// was not loaded in eager-loading.
|
// was not loaded in eager-loading.
|
||||||
func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) {
|
func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) {
|
||||||
if e.loadedTypes[7] {
|
if e.loadedTypes[8] {
|
||||||
return e.PromoCodeUsages, nil
|
return e.PromoCodeUsages, nil
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "promo_code_usages"}
|
return nil, &NotLoadedError{edge: "promo_code_usages"}
|
||||||
@@ -145,7 +162,7 @@ func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) {
|
|||||||
// UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge
|
// UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge
|
||||||
// was not loaded in eager-loading.
|
// was not loaded in eager-loading.
|
||||||
func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) {
|
func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) {
|
||||||
if e.loadedTypes[8] {
|
if e.loadedTypes[9] {
|
||||||
return e.UserAllowedGroups, nil
|
return e.UserAllowedGroups, nil
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "user_allowed_groups"}
|
return nil, &NotLoadedError{edge: "user_allowed_groups"}
|
||||||
@@ -156,13 +173,15 @@ func (*User) scanValues(columns []string) ([]any, error) {
|
|||||||
values := make([]any, len(columns))
|
values := make([]any, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
|
case user.FieldTotpEnabled:
|
||||||
|
values[i] = new(sql.NullBool)
|
||||||
case user.FieldBalance:
|
case user.FieldBalance:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
case user.FieldID, user.FieldConcurrency:
|
case user.FieldID, user.FieldConcurrency:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case user.FieldEmail, user.FieldPasswordHash, user.FieldRole, user.FieldStatus, user.FieldUsername, user.FieldNotes:
|
case user.FieldEmail, user.FieldPasswordHash, user.FieldRole, user.FieldStatus, user.FieldUsername, user.FieldNotes, user.FieldTotpSecretEncrypted:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
case user.FieldCreatedAt, user.FieldUpdatedAt, user.FieldDeletedAt:
|
case user.FieldCreatedAt, user.FieldUpdatedAt, user.FieldDeletedAt, user.FieldTotpEnabledAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
default:
|
default:
|
||||||
values[i] = new(sql.UnknownType)
|
values[i] = new(sql.UnknownType)
|
||||||
@@ -252,6 +271,26 @@ func (_m *User) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.Notes = value.String
|
_m.Notes = value.String
|
||||||
}
|
}
|
||||||
|
case user.FieldTotpSecretEncrypted:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field totp_secret_encrypted", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.TotpSecretEncrypted = new(string)
|
||||||
|
*_m.TotpSecretEncrypted = value.String
|
||||||
|
}
|
||||||
|
case user.FieldTotpEnabled:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field totp_enabled", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.TotpEnabled = value.Bool
|
||||||
|
}
|
||||||
|
case user.FieldTotpEnabledAt:
|
||||||
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field totp_enabled_at", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.TotpEnabledAt = new(time.Time)
|
||||||
|
*_m.TotpEnabledAt = value.Time
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
_m.selectValues.Set(columns[i], values[i])
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
@@ -285,6 +324,11 @@ func (_m *User) QueryAssignedSubscriptions() *UserSubscriptionQuery {
|
|||||||
return NewUserClient(_m.config).QueryAssignedSubscriptions(_m)
|
return NewUserClient(_m.config).QueryAssignedSubscriptions(_m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncementReads queries the "announcement_reads" edge of the User entity.
|
||||||
|
func (_m *User) QueryAnnouncementReads() *AnnouncementReadQuery {
|
||||||
|
return NewUserClient(_m.config).QueryAnnouncementReads(_m)
|
||||||
|
}
|
||||||
|
|
||||||
// QueryAllowedGroups queries the "allowed_groups" edge of the User entity.
|
// QueryAllowedGroups queries the "allowed_groups" edge of the User entity.
|
||||||
func (_m *User) QueryAllowedGroups() *GroupQuery {
|
func (_m *User) QueryAllowedGroups() *GroupQuery {
|
||||||
return NewUserClient(_m.config).QueryAllowedGroups(_m)
|
return NewUserClient(_m.config).QueryAllowedGroups(_m)
|
||||||
@@ -367,6 +411,19 @@ func (_m *User) String() string {
|
|||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
builder.WriteString("notes=")
|
builder.WriteString("notes=")
|
||||||
builder.WriteString(_m.Notes)
|
builder.WriteString(_m.Notes)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.TotpSecretEncrypted; v != nil {
|
||||||
|
builder.WriteString("totp_secret_encrypted=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("totp_enabled=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.TotpEnabled))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.TotpEnabledAt; v != nil {
|
||||||
|
builder.WriteString("totp_enabled_at=")
|
||||||
|
builder.WriteString(v.Format(time.ANSIC))
|
||||||
|
}
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ const (
|
|||||||
FieldUsername = "username"
|
FieldUsername = "username"
|
||||||
// FieldNotes holds the string denoting the notes field in the database.
|
// FieldNotes holds the string denoting the notes field in the database.
|
||||||
FieldNotes = "notes"
|
FieldNotes = "notes"
|
||||||
|
// FieldTotpSecretEncrypted holds the string denoting the totp_secret_encrypted field in the database.
|
||||||
|
FieldTotpSecretEncrypted = "totp_secret_encrypted"
|
||||||
|
// FieldTotpEnabled holds the string denoting the totp_enabled field in the database.
|
||||||
|
FieldTotpEnabled = "totp_enabled"
|
||||||
|
// FieldTotpEnabledAt holds the string denoting the totp_enabled_at field in the database.
|
||||||
|
FieldTotpEnabledAt = "totp_enabled_at"
|
||||||
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
|
||||||
EdgeAPIKeys = "api_keys"
|
EdgeAPIKeys = "api_keys"
|
||||||
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
|
||||||
@@ -45,6 +51,8 @@ const (
|
|||||||
EdgeSubscriptions = "subscriptions"
|
EdgeSubscriptions = "subscriptions"
|
||||||
// EdgeAssignedSubscriptions holds the string denoting the assigned_subscriptions edge name in mutations.
|
// EdgeAssignedSubscriptions holds the string denoting the assigned_subscriptions edge name in mutations.
|
||||||
EdgeAssignedSubscriptions = "assigned_subscriptions"
|
EdgeAssignedSubscriptions = "assigned_subscriptions"
|
||||||
|
// EdgeAnnouncementReads holds the string denoting the announcement_reads edge name in mutations.
|
||||||
|
EdgeAnnouncementReads = "announcement_reads"
|
||||||
// EdgeAllowedGroups holds the string denoting the allowed_groups edge name in mutations.
|
// EdgeAllowedGroups holds the string denoting the allowed_groups edge name in mutations.
|
||||||
EdgeAllowedGroups = "allowed_groups"
|
EdgeAllowedGroups = "allowed_groups"
|
||||||
// EdgeUsageLogs holds the string denoting the usage_logs edge name in mutations.
|
// EdgeUsageLogs holds the string denoting the usage_logs edge name in mutations.
|
||||||
@@ -85,6 +93,13 @@ const (
|
|||||||
AssignedSubscriptionsInverseTable = "user_subscriptions"
|
AssignedSubscriptionsInverseTable = "user_subscriptions"
|
||||||
// AssignedSubscriptionsColumn is the table column denoting the assigned_subscriptions relation/edge.
|
// AssignedSubscriptionsColumn is the table column denoting the assigned_subscriptions relation/edge.
|
||||||
AssignedSubscriptionsColumn = "assigned_by"
|
AssignedSubscriptionsColumn = "assigned_by"
|
||||||
|
// AnnouncementReadsTable is the table that holds the announcement_reads relation/edge.
|
||||||
|
AnnouncementReadsTable = "announcement_reads"
|
||||||
|
// AnnouncementReadsInverseTable is the table name for the AnnouncementRead entity.
|
||||||
|
// It exists in this package in order to avoid circular dependency with the "announcementread" package.
|
||||||
|
AnnouncementReadsInverseTable = "announcement_reads"
|
||||||
|
// AnnouncementReadsColumn is the table column denoting the announcement_reads relation/edge.
|
||||||
|
AnnouncementReadsColumn = "user_id"
|
||||||
// AllowedGroupsTable is the table that holds the allowed_groups relation/edge. The primary key declared below.
|
// AllowedGroupsTable is the table that holds the allowed_groups relation/edge. The primary key declared below.
|
||||||
AllowedGroupsTable = "user_allowed_groups"
|
AllowedGroupsTable = "user_allowed_groups"
|
||||||
// AllowedGroupsInverseTable is the table name for the Group entity.
|
// AllowedGroupsInverseTable is the table name for the Group entity.
|
||||||
@@ -134,6 +149,9 @@ var Columns = []string{
|
|||||||
FieldStatus,
|
FieldStatus,
|
||||||
FieldUsername,
|
FieldUsername,
|
||||||
FieldNotes,
|
FieldNotes,
|
||||||
|
FieldTotpSecretEncrypted,
|
||||||
|
FieldTotpEnabled,
|
||||||
|
FieldTotpEnabledAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -188,6 +206,8 @@ var (
|
|||||||
UsernameValidator func(string) error
|
UsernameValidator func(string) error
|
||||||
// DefaultNotes holds the default value on creation for the "notes" field.
|
// DefaultNotes holds the default value on creation for the "notes" field.
|
||||||
DefaultNotes string
|
DefaultNotes string
|
||||||
|
// DefaultTotpEnabled holds the default value on creation for the "totp_enabled" field.
|
||||||
|
DefaultTotpEnabled bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderOption defines the ordering options for the User queries.
|
// OrderOption defines the ordering options for the User queries.
|
||||||
@@ -253,6 +273,21 @@ func ByNotes(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldNotes, opts...).ToFunc()
|
return sql.OrderByField(FieldNotes, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByTotpSecretEncrypted orders the results by the totp_secret_encrypted field.
|
||||||
|
func ByTotpSecretEncrypted(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldTotpSecretEncrypted, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByTotpEnabled orders the results by the totp_enabled field.
|
||||||
|
func ByTotpEnabled(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldTotpEnabled, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByTotpEnabledAt orders the results by the totp_enabled_at field.
|
||||||
|
func ByTotpEnabledAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldTotpEnabledAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByAPIKeysCount orders the results by api_keys count.
|
// ByAPIKeysCount orders the results by api_keys count.
|
||||||
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
|
func ByAPIKeysCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return func(s *sql.Selector) {
|
return func(s *sql.Selector) {
|
||||||
@@ -309,6 +344,20 @@ func ByAssignedSubscriptions(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByAnnouncementReadsCount orders the results by announcement_reads count.
|
||||||
|
func ByAnnouncementReadsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborsCount(s, newAnnouncementReadsStep(), opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByAnnouncementReads orders the results by announcement_reads terms.
|
||||||
|
func ByAnnouncementReads(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newAnnouncementReadsStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ByAllowedGroupsCount orders the results by allowed_groups count.
|
// ByAllowedGroupsCount orders the results by allowed_groups count.
|
||||||
func ByAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption {
|
func ByAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return func(s *sql.Selector) {
|
return func(s *sql.Selector) {
|
||||||
@@ -406,6 +455,13 @@ func newAssignedSubscriptionsStep() *sqlgraph.Step {
|
|||||||
sqlgraph.Edge(sqlgraph.O2M, false, AssignedSubscriptionsTable, AssignedSubscriptionsColumn),
|
sqlgraph.Edge(sqlgraph.O2M, false, AssignedSubscriptionsTable, AssignedSubscriptionsColumn),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
func newAnnouncementReadsStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(AnnouncementReadsInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, AnnouncementReadsTable, AnnouncementReadsColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
func newAllowedGroupsStep() *sqlgraph.Step {
|
func newAllowedGroupsStep() *sqlgraph.Step {
|
||||||
return sqlgraph.NewStep(
|
return sqlgraph.NewStep(
|
||||||
sqlgraph.From(Table, FieldID),
|
sqlgraph.From(Table, FieldID),
|
||||||
|
|||||||
@@ -110,6 +110,21 @@ func Notes(v string) predicate.User {
|
|||||||
return predicate.User(sql.FieldEQ(FieldNotes, v))
|
return predicate.User(sql.FieldEQ(FieldNotes, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncrypted applies equality check predicate on the "totp_secret_encrypted" field. It's identical to TotpSecretEncryptedEQ.
|
||||||
|
func TotpSecretEncrypted(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabled applies equality check predicate on the "totp_enabled" field. It's identical to TotpEnabledEQ.
|
||||||
|
func TotpEnabled(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAt applies equality check predicate on the "totp_enabled_at" field. It's identical to TotpEnabledAtEQ.
|
||||||
|
func TotpEnabledAt(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
func CreatedAtEQ(v time.Time) predicate.User {
|
func CreatedAtEQ(v time.Time) predicate.User {
|
||||||
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
@@ -710,6 +725,141 @@ func NotesContainsFold(v string) predicate.User {
|
|||||||
return predicate.User(sql.FieldContainsFold(FieldNotes, v))
|
return predicate.User(sql.FieldContainsFold(FieldNotes, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedEQ applies the EQ predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedEQ(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedNEQ applies the NEQ predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedNEQ(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNEQ(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedIn applies the In predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedIn(vs ...string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldIn(FieldTotpSecretEncrypted, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedNotIn applies the NotIn predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedNotIn(vs ...string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNotIn(FieldTotpSecretEncrypted, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedGT applies the GT predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedGT(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldGT(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedGTE applies the GTE predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedGTE(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldGTE(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedLT applies the LT predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedLT(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldLT(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedLTE applies the LTE predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedLTE(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldLTE(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedContains applies the Contains predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedContains(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldContains(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedHasPrefix applies the HasPrefix predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedHasPrefix(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldHasPrefix(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedHasSuffix applies the HasSuffix predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedHasSuffix(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldHasSuffix(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedIsNil applies the IsNil predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedIsNil() predicate.User {
|
||||||
|
return predicate.User(sql.FieldIsNull(FieldTotpSecretEncrypted))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedNotNil applies the NotNil predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedNotNil() predicate.User {
|
||||||
|
return predicate.User(sql.FieldNotNull(FieldTotpSecretEncrypted))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedEqualFold applies the EqualFold predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedEqualFold(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEqualFold(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpSecretEncryptedContainsFold applies the ContainsFold predicate on the "totp_secret_encrypted" field.
|
||||||
|
func TotpSecretEncryptedContainsFold(v string) predicate.User {
|
||||||
|
return predicate.User(sql.FieldContainsFold(FieldTotpSecretEncrypted, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledEQ applies the EQ predicate on the "totp_enabled" field.
|
||||||
|
func TotpEnabledEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledNEQ applies the NEQ predicate on the "totp_enabled" field.
|
||||||
|
func TotpEnabledNEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNEQ(FieldTotpEnabled, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtEQ applies the EQ predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtEQ(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtNEQ applies the NEQ predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtNEQ(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNEQ(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtIn applies the In predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtIn(vs ...time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldIn(FieldTotpEnabledAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtNotIn applies the NotIn predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtNotIn(vs ...time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNotIn(FieldTotpEnabledAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtGT applies the GT predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtGT(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldGT(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtGTE applies the GTE predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtGTE(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldGTE(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtLT applies the LT predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtLT(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldLT(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtLTE applies the LTE predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtLTE(v time.Time) predicate.User {
|
||||||
|
return predicate.User(sql.FieldLTE(FieldTotpEnabledAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtIsNil applies the IsNil predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtIsNil() predicate.User {
|
||||||
|
return predicate.User(sql.FieldIsNull(FieldTotpEnabledAt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotpEnabledAtNotNil applies the NotNil predicate on the "totp_enabled_at" field.
|
||||||
|
func TotpEnabledAtNotNil() predicate.User {
|
||||||
|
return predicate.User(sql.FieldNotNull(FieldTotpEnabledAt))
|
||||||
|
}
|
||||||
|
|
||||||
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
||||||
func HasAPIKeys() predicate.User {
|
func HasAPIKeys() predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(func(s *sql.Selector) {
|
||||||
@@ -802,6 +952,29 @@ func HasAssignedSubscriptionsWith(preds ...predicate.UserSubscription) predicate
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAnnouncementReads applies the HasEdge predicate on the "announcement_reads" edge.
|
||||||
|
func HasAnnouncementReads() predicate.User {
|
||||||
|
return predicate.User(func(s *sql.Selector) {
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, AnnouncementReadsTable, AnnouncementReadsColumn),
|
||||||
|
)
|
||||||
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAnnouncementReadsWith applies the HasEdge predicate on the "announcement_reads" edge with a given conditions (other predicates).
|
||||||
|
func HasAnnouncementReadsWith(preds ...predicate.AnnouncementRead) predicate.User {
|
||||||
|
return predicate.User(func(s *sql.Selector) {
|
||||||
|
step := newAnnouncementReadsStep()
|
||||||
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
|
for _, p := range preds {
|
||||||
|
p(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// HasAllowedGroups applies the HasEdge predicate on the "allowed_groups" edge.
|
// HasAllowedGroups applies the HasEdge predicate on the "allowed_groups" edge.
|
||||||
func HasAllowedGroups() predicate.User {
|
func HasAllowedGroups() predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(func(s *sql.Selector) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||||
@@ -167,6 +168,48 @@ func (_c *UserCreate) SetNillableNotes(v *string) *UserCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (_c *UserCreate) SetTotpSecretEncrypted(v string) *UserCreate {
|
||||||
|
_c.mutation.SetTotpSecretEncrypted(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
|
||||||
|
func (_c *UserCreate) SetNillableTotpSecretEncrypted(v *string) *UserCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetTotpSecretEncrypted(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (_c *UserCreate) SetTotpEnabled(v bool) *UserCreate {
|
||||||
|
_c.mutation.SetTotpEnabled(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
|
||||||
|
func (_c *UserCreate) SetNillableTotpEnabled(v *bool) *UserCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetTotpEnabled(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (_c *UserCreate) SetTotpEnabledAt(v time.Time) *UserCreate {
|
||||||
|
_c.mutation.SetTotpEnabledAt(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
|
||||||
|
func (_c *UserCreate) SetNillableTotpEnabledAt(v *time.Time) *UserCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetTotpEnabledAt(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_c *UserCreate) AddAPIKeyIDs(ids ...int64) *UserCreate {
|
func (_c *UserCreate) AddAPIKeyIDs(ids ...int64) *UserCreate {
|
||||||
_c.mutation.AddAPIKeyIDs(ids...)
|
_c.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -227,6 +270,21 @@ func (_c *UserCreate) AddAssignedSubscriptions(v ...*UserSubscription) *UserCrea
|
|||||||
return _c.AddAssignedSubscriptionIDs(ids...)
|
return _c.AddAssignedSubscriptionIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReadIDs adds the "announcement_reads" edge to the AnnouncementRead entity by IDs.
|
||||||
|
func (_c *UserCreate) AddAnnouncementReadIDs(ids ...int64) *UserCreate {
|
||||||
|
_c.mutation.AddAnnouncementReadIDs(ids...)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReads adds the "announcement_reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_c *UserCreate) AddAnnouncementReads(v ...*AnnouncementRead) *UserCreate {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _c.AddAnnouncementReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
||||||
func (_c *UserCreate) AddAllowedGroupIDs(ids ...int64) *UserCreate {
|
func (_c *UserCreate) AddAllowedGroupIDs(ids ...int64) *UserCreate {
|
||||||
_c.mutation.AddAllowedGroupIDs(ids...)
|
_c.mutation.AddAllowedGroupIDs(ids...)
|
||||||
@@ -362,6 +420,10 @@ func (_c *UserCreate) defaults() error {
|
|||||||
v := user.DefaultNotes
|
v := user.DefaultNotes
|
||||||
_c.mutation.SetNotes(v)
|
_c.mutation.SetNotes(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.TotpEnabled(); !ok {
|
||||||
|
v := user.DefaultTotpEnabled
|
||||||
|
_c.mutation.SetTotpEnabled(v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,6 +484,9 @@ func (_c *UserCreate) check() error {
|
|||||||
if _, ok := _c.mutation.Notes(); !ok {
|
if _, ok := _c.mutation.Notes(); !ok {
|
||||||
return &ValidationError{Name: "notes", err: errors.New(`ent: missing required field "User.notes"`)}
|
return &ValidationError{Name: "notes", err: errors.New(`ent: missing required field "User.notes"`)}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.TotpEnabled(); !ok {
|
||||||
|
return &ValidationError{Name: "totp_enabled", err: errors.New(`ent: missing required field "User.totp_enabled"`)}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,6 +558,18 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
||||||
_node.Notes = value
|
_node.Notes = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.TotpSecretEncrypted(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpSecretEncrypted, field.TypeString, value)
|
||||||
|
_node.TotpSecretEncrypted = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.TotpEnabled(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabled, field.TypeBool, value)
|
||||||
|
_node.TotpEnabled = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.TotpEnabledAt(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabledAt, field.TypeTime, value)
|
||||||
|
_node.TotpEnabledAt = &value
|
||||||
|
}
|
||||||
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -557,6 +634,22 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
|||||||
}
|
}
|
||||||
_spec.Edges = append(_spec.Edges, edge)
|
_spec.Edges = append(_spec.Edges, edge)
|
||||||
}
|
}
|
||||||
|
if nodes := _c.mutation.AnnouncementReadsIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges = append(_spec.Edges, edge)
|
||||||
|
}
|
||||||
if nodes := _c.mutation.AllowedGroupsIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.AllowedGroupsIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2M,
|
Rel: sqlgraph.M2M,
|
||||||
@@ -815,6 +908,54 @@ func (u *UserUpsert) UpdateNotes() *UserUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsert) SetTotpSecretEncrypted(v string) *UserUpsert {
|
||||||
|
u.Set(user.FieldTotpSecretEncrypted, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsert) UpdateTotpSecretEncrypted() *UserUpsert {
|
||||||
|
u.SetExcluded(user.FieldTotpSecretEncrypted)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsert) ClearTotpSecretEncrypted() *UserUpsert {
|
||||||
|
u.SetNull(user.FieldTotpSecretEncrypted)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (u *UserUpsert) SetTotpEnabled(v bool) *UserUpsert {
|
||||||
|
u.Set(user.FieldTotpEnabled, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsert) UpdateTotpEnabled() *UserUpsert {
|
||||||
|
u.SetExcluded(user.FieldTotpEnabled)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsert) SetTotpEnabledAt(v time.Time) *UserUpsert {
|
||||||
|
u.Set(user.FieldTotpEnabledAt, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsert) UpdateTotpEnabledAt() *UserUpsert {
|
||||||
|
u.SetExcluded(user.FieldTotpEnabledAt)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsert) ClearTotpEnabledAt() *UserUpsert {
|
||||||
|
u.SetNull(user.FieldTotpEnabledAt)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||||
// Using this option is equivalent to using:
|
// Using this option is equivalent to using:
|
||||||
//
|
//
|
||||||
@@ -1021,6 +1162,62 @@ func (u *UserUpsertOne) UpdateNotes() *UserUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsertOne) SetTotpSecretEncrypted(v string) *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpSecretEncrypted(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertOne) UpdateTotpSecretEncrypted() *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpSecretEncrypted()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsertOne) ClearTotpSecretEncrypted() *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.ClearTotpSecretEncrypted()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (u *UserUpsertOne) SetTotpEnabled(v bool) *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpEnabled(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertOne) UpdateTotpEnabled() *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsertOne) SetTotpEnabledAt(v time.Time) *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpEnabledAt(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertOne) UpdateTotpEnabledAt() *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpEnabledAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsertOne) ClearTotpEnabledAt() *UserUpsertOne {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.ClearTotpEnabledAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *UserUpsertOne) Exec(ctx context.Context) error {
|
func (u *UserUpsertOne) Exec(ctx context.Context) error {
|
||||||
if len(u.create.conflict) == 0 {
|
if len(u.create.conflict) == 0 {
|
||||||
@@ -1393,6 +1590,62 @@ func (u *UserUpsertBulk) UpdateNotes() *UserUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsertBulk) SetTotpSecretEncrypted(v string) *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpSecretEncrypted(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpSecretEncrypted sets the "totp_secret_encrypted" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertBulk) UpdateTotpSecretEncrypted() *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpSecretEncrypted()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
|
||||||
|
func (u *UserUpsertBulk) ClearTotpSecretEncrypted() *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.ClearTotpSecretEncrypted()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (u *UserUpsertBulk) SetTotpEnabled(v bool) *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpEnabled(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabled sets the "totp_enabled" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertBulk) UpdateTotpEnabled() *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsertBulk) SetTotpEnabledAt(v time.Time) *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.SetTotpEnabledAt(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTotpEnabledAt sets the "totp_enabled_at" field to the value that was provided on create.
|
||||||
|
func (u *UserUpsertBulk) UpdateTotpEnabledAt() *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.UpdateTotpEnabledAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
|
||||||
|
func (u *UserUpsertBulk) ClearTotpEnabledAt() *UserUpsertBulk {
|
||||||
|
return u.Update(func(s *UserUpsert) {
|
||||||
|
s.ClearTotpEnabledAt()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (u *UserUpsertBulk) Exec(ctx context.Context) error {
|
func (u *UserUpsertBulk) Exec(ctx context.Context) error {
|
||||||
if u.create.err != nil {
|
if u.create.err != nil {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
@@ -36,6 +37,7 @@ type UserQuery struct {
|
|||||||
withRedeemCodes *RedeemCodeQuery
|
withRedeemCodes *RedeemCodeQuery
|
||||||
withSubscriptions *UserSubscriptionQuery
|
withSubscriptions *UserSubscriptionQuery
|
||||||
withAssignedSubscriptions *UserSubscriptionQuery
|
withAssignedSubscriptions *UserSubscriptionQuery
|
||||||
|
withAnnouncementReads *AnnouncementReadQuery
|
||||||
withAllowedGroups *GroupQuery
|
withAllowedGroups *GroupQuery
|
||||||
withUsageLogs *UsageLogQuery
|
withUsageLogs *UsageLogQuery
|
||||||
withAttributeValues *UserAttributeValueQuery
|
withAttributeValues *UserAttributeValueQuery
|
||||||
@@ -166,6 +168,28 @@ func (_q *UserQuery) QueryAssignedSubscriptions() *UserSubscriptionQuery {
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryAnnouncementReads chains the current query on the "announcement_reads" edge.
|
||||||
|
func (_q *UserQuery) QueryAnnouncementReads() *AnnouncementReadQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: _q.config}).Query()
|
||||||
|
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector := _q.sqlQuery(ctx)
|
||||||
|
if err := selector.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
step := sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(user.Table, user.FieldID, selector),
|
||||||
|
sqlgraph.To(announcementread.Table, announcementread.FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, false, user.AnnouncementReadsTable, user.AnnouncementReadsColumn),
|
||||||
|
)
|
||||||
|
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||||
|
return fromU, nil
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
// QueryAllowedGroups chains the current query on the "allowed_groups" edge.
|
// QueryAllowedGroups chains the current query on the "allowed_groups" edge.
|
||||||
func (_q *UserQuery) QueryAllowedGroups() *GroupQuery {
|
func (_q *UserQuery) QueryAllowedGroups() *GroupQuery {
|
||||||
query := (&GroupClient{config: _q.config}).Query()
|
query := (&GroupClient{config: _q.config}).Query()
|
||||||
@@ -472,6 +496,7 @@ func (_q *UserQuery) Clone() *UserQuery {
|
|||||||
withRedeemCodes: _q.withRedeemCodes.Clone(),
|
withRedeemCodes: _q.withRedeemCodes.Clone(),
|
||||||
withSubscriptions: _q.withSubscriptions.Clone(),
|
withSubscriptions: _q.withSubscriptions.Clone(),
|
||||||
withAssignedSubscriptions: _q.withAssignedSubscriptions.Clone(),
|
withAssignedSubscriptions: _q.withAssignedSubscriptions.Clone(),
|
||||||
|
withAnnouncementReads: _q.withAnnouncementReads.Clone(),
|
||||||
withAllowedGroups: _q.withAllowedGroups.Clone(),
|
withAllowedGroups: _q.withAllowedGroups.Clone(),
|
||||||
withUsageLogs: _q.withUsageLogs.Clone(),
|
withUsageLogs: _q.withUsageLogs.Clone(),
|
||||||
withAttributeValues: _q.withAttributeValues.Clone(),
|
withAttributeValues: _q.withAttributeValues.Clone(),
|
||||||
@@ -527,6 +552,17 @@ func (_q *UserQuery) WithAssignedSubscriptions(opts ...func(*UserSubscriptionQue
|
|||||||
return _q
|
return _q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAnnouncementReads tells the query-builder to eager-load the nodes that are connected to
|
||||||
|
// the "announcement_reads" edge. The optional arguments are used to configure the query builder of the edge.
|
||||||
|
func (_q *UserQuery) WithAnnouncementReads(opts ...func(*AnnouncementReadQuery)) *UserQuery {
|
||||||
|
query := (&AnnouncementReadClient{config: _q.config}).Query()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(query)
|
||||||
|
}
|
||||||
|
_q.withAnnouncementReads = query
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
// WithAllowedGroups tells the query-builder to eager-load the nodes that are connected to
|
// WithAllowedGroups tells the query-builder to eager-load the nodes that are connected to
|
||||||
// the "allowed_groups" edge. The optional arguments are used to configure the query builder of the edge.
|
// the "allowed_groups" edge. The optional arguments are used to configure the query builder of the edge.
|
||||||
func (_q *UserQuery) WithAllowedGroups(opts ...func(*GroupQuery)) *UserQuery {
|
func (_q *UserQuery) WithAllowedGroups(opts ...func(*GroupQuery)) *UserQuery {
|
||||||
@@ -660,11 +696,12 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
|
|||||||
var (
|
var (
|
||||||
nodes = []*User{}
|
nodes = []*User{}
|
||||||
_spec = _q.querySpec()
|
_spec = _q.querySpec()
|
||||||
loadedTypes = [9]bool{
|
loadedTypes = [10]bool{
|
||||||
_q.withAPIKeys != nil,
|
_q.withAPIKeys != nil,
|
||||||
_q.withRedeemCodes != nil,
|
_q.withRedeemCodes != nil,
|
||||||
_q.withSubscriptions != nil,
|
_q.withSubscriptions != nil,
|
||||||
_q.withAssignedSubscriptions != nil,
|
_q.withAssignedSubscriptions != nil,
|
||||||
|
_q.withAnnouncementReads != nil,
|
||||||
_q.withAllowedGroups != nil,
|
_q.withAllowedGroups != nil,
|
||||||
_q.withUsageLogs != nil,
|
_q.withUsageLogs != nil,
|
||||||
_q.withAttributeValues != nil,
|
_q.withAttributeValues != nil,
|
||||||
@@ -723,6 +760,13 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if query := _q.withAnnouncementReads; query != nil {
|
||||||
|
if err := _q.loadAnnouncementReads(ctx, query, nodes,
|
||||||
|
func(n *User) { n.Edges.AnnouncementReads = []*AnnouncementRead{} },
|
||||||
|
func(n *User, e *AnnouncementRead) { n.Edges.AnnouncementReads = append(n.Edges.AnnouncementReads, e) }); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if query := _q.withAllowedGroups; query != nil {
|
if query := _q.withAllowedGroups; query != nil {
|
||||||
if err := _q.loadAllowedGroups(ctx, query, nodes,
|
if err := _q.loadAllowedGroups(ctx, query, nodes,
|
||||||
func(n *User) { n.Edges.AllowedGroups = []*Group{} },
|
func(n *User) { n.Edges.AllowedGroups = []*Group{} },
|
||||||
@@ -887,6 +931,36 @@ func (_q *UserQuery) loadAssignedSubscriptions(ctx context.Context, query *UserS
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (_q *UserQuery) loadAnnouncementReads(ctx context.Context, query *AnnouncementReadQuery, nodes []*User, init func(*User), assign func(*User, *AnnouncementRead)) error {
|
||||||
|
fks := make([]driver.Value, 0, len(nodes))
|
||||||
|
nodeids := make(map[int64]*User)
|
||||||
|
for i := range nodes {
|
||||||
|
fks = append(fks, nodes[i].ID)
|
||||||
|
nodeids[nodes[i].ID] = nodes[i]
|
||||||
|
if init != nil {
|
||||||
|
init(nodes[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(query.ctx.Fields) > 0 {
|
||||||
|
query.ctx.AppendFieldOnce(announcementread.FieldUserID)
|
||||||
|
}
|
||||||
|
query.Where(predicate.AnnouncementRead(func(s *sql.Selector) {
|
||||||
|
s.Where(sql.InValues(s.C(user.AnnouncementReadsColumn), fks...))
|
||||||
|
}))
|
||||||
|
neighbors, err := query.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, n := range neighbors {
|
||||||
|
fk := n.UserID
|
||||||
|
node, ok := nodeids[fk]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
|
||||||
|
}
|
||||||
|
assign(node, n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (_q *UserQuery) loadAllowedGroups(ctx context.Context, query *GroupQuery, nodes []*User, init func(*User), assign func(*User, *Group)) error {
|
func (_q *UserQuery) loadAllowedGroups(ctx context.Context, query *GroupQuery, nodes []*User, init func(*User), assign func(*User, *Group)) error {
|
||||||
edgeIDs := make([]driver.Value, len(nodes))
|
edgeIDs := make([]driver.Value, len(nodes))
|
||||||
byID := make(map[int64]*User)
|
byID := make(map[int64]*User)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/announcementread"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
@@ -187,6 +188,60 @@ func (_u *UserUpdate) SetNillableNotes(v *string) *UserUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (_u *UserUpdate) SetTotpSecretEncrypted(v string) *UserUpdate {
|
||||||
|
_u.mutation.SetTotpSecretEncrypted(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableTotpSecretEncrypted(v *string) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpSecretEncrypted(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
|
||||||
|
func (_u *UserUpdate) ClearTotpSecretEncrypted() *UserUpdate {
|
||||||
|
_u.mutation.ClearTotpSecretEncrypted()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (_u *UserUpdate) SetTotpEnabled(v bool) *UserUpdate {
|
||||||
|
_u.mutation.SetTotpEnabled(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableTotpEnabled(v *bool) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpEnabled(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (_u *UserUpdate) SetTotpEnabledAt(v time.Time) *UserUpdate {
|
||||||
|
_u.mutation.SetTotpEnabledAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableTotpEnabledAt(v *time.Time) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpEnabledAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
|
||||||
|
func (_u *UserUpdate) ClearTotpEnabledAt() *UserUpdate {
|
||||||
|
_u.mutation.ClearTotpEnabledAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate {
|
func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -247,6 +302,21 @@ func (_u *UserUpdate) AddAssignedSubscriptions(v ...*UserSubscription) *UserUpda
|
|||||||
return _u.AddAssignedSubscriptionIDs(ids...)
|
return _u.AddAssignedSubscriptionIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReadIDs adds the "announcement_reads" edge to the AnnouncementRead entity by IDs.
|
||||||
|
func (_u *UserUpdate) AddAnnouncementReadIDs(ids ...int64) *UserUpdate {
|
||||||
|
_u.mutation.AddAnnouncementReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReads adds the "announcement_reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *UserUpdate) AddAnnouncementReads(v ...*AnnouncementRead) *UserUpdate {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.AddAnnouncementReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
||||||
func (_u *UserUpdate) AddAllowedGroupIDs(ids ...int64) *UserUpdate {
|
func (_u *UserUpdate) AddAllowedGroupIDs(ids ...int64) *UserUpdate {
|
||||||
_u.mutation.AddAllowedGroupIDs(ids...)
|
_u.mutation.AddAllowedGroupIDs(ids...)
|
||||||
@@ -396,6 +466,27 @@ func (_u *UserUpdate) RemoveAssignedSubscriptions(v ...*UserSubscription) *UserU
|
|||||||
return _u.RemoveAssignedSubscriptionIDs(ids...)
|
return _u.RemoveAssignedSubscriptionIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearAnnouncementReads clears all "announcement_reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *UserUpdate) ClearAnnouncementReads() *UserUpdate {
|
||||||
|
_u.mutation.ClearAnnouncementReads()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAnnouncementReadIDs removes the "announcement_reads" edge to AnnouncementRead entities by IDs.
|
||||||
|
func (_u *UserUpdate) RemoveAnnouncementReadIDs(ids ...int64) *UserUpdate {
|
||||||
|
_u.mutation.RemoveAnnouncementReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAnnouncementReads removes "announcement_reads" edges to AnnouncementRead entities.
|
||||||
|
func (_u *UserUpdate) RemoveAnnouncementReads(v ...*AnnouncementRead) *UserUpdate {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.RemoveAnnouncementReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
// ClearAllowedGroups clears all "allowed_groups" edges to the Group entity.
|
// ClearAllowedGroups clears all "allowed_groups" edges to the Group entity.
|
||||||
func (_u *UserUpdate) ClearAllowedGroups() *UserUpdate {
|
func (_u *UserUpdate) ClearAllowedGroups() *UserUpdate {
|
||||||
_u.mutation.ClearAllowedGroups()
|
_u.mutation.ClearAllowedGroups()
|
||||||
@@ -603,6 +694,21 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.Notes(); ok {
|
if value, ok := _u.mutation.Notes(); ok {
|
||||||
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.TotpSecretEncrypted(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpSecretEncrypted, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TotpSecretEncryptedCleared() {
|
||||||
|
_spec.ClearField(user.FieldTotpSecretEncrypted, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.TotpEnabled(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabled, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.TotpEnabledAt(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabledAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TotpEnabledAtCleared() {
|
||||||
|
_spec.ClearField(user.FieldTotpEnabledAt, field.TypeTime)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -783,6 +889,51 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
|
if _u.mutation.AnnouncementReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.RemovedAnnouncementReadsIDs(); len(nodes) > 0 && !_u.mutation.AnnouncementReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.AnnouncementReadsIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
if _u.mutation.AllowedGroupsCleared() {
|
if _u.mutation.AllowedGroupsCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2M,
|
Rel: sqlgraph.M2M,
|
||||||
@@ -1147,6 +1298,60 @@ func (_u *UserUpdateOne) SetNillableNotes(v *string) *UserUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotpSecretEncrypted sets the "totp_secret_encrypted" field.
|
||||||
|
func (_u *UserUpdateOne) SetTotpSecretEncrypted(v string) *UserUpdateOne {
|
||||||
|
_u.mutation.SetTotpSecretEncrypted(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpSecretEncrypted sets the "totp_secret_encrypted" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableTotpSecretEncrypted(v *string) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpSecretEncrypted(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpSecretEncrypted clears the value of the "totp_secret_encrypted" field.
|
||||||
|
func (_u *UserUpdateOne) ClearTotpSecretEncrypted() *UserUpdateOne {
|
||||||
|
_u.mutation.ClearTotpSecretEncrypted()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabled sets the "totp_enabled" field.
|
||||||
|
func (_u *UserUpdateOne) SetTotpEnabled(v bool) *UserUpdateOne {
|
||||||
|
_u.mutation.SetTotpEnabled(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabled sets the "totp_enabled" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableTotpEnabled(v *bool) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpEnabled(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTotpEnabledAt sets the "totp_enabled_at" field.
|
||||||
|
func (_u *UserUpdateOne) SetTotpEnabledAt(v time.Time) *UserUpdateOne {
|
||||||
|
_u.mutation.SetTotpEnabledAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableTotpEnabledAt sets the "totp_enabled_at" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableTotpEnabledAt(v *time.Time) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetTotpEnabledAt(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTotpEnabledAt clears the value of the "totp_enabled_at" field.
|
||||||
|
func (_u *UserUpdateOne) ClearTotpEnabledAt() *UserUpdateOne {
|
||||||
|
_u.mutation.ClearTotpEnabledAt()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
|
||||||
func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne {
|
func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne {
|
||||||
_u.mutation.AddAPIKeyIDs(ids...)
|
_u.mutation.AddAPIKeyIDs(ids...)
|
||||||
@@ -1207,6 +1412,21 @@ func (_u *UserUpdateOne) AddAssignedSubscriptions(v ...*UserSubscription) *UserU
|
|||||||
return _u.AddAssignedSubscriptionIDs(ids...)
|
return _u.AddAssignedSubscriptionIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReadIDs adds the "announcement_reads" edge to the AnnouncementRead entity by IDs.
|
||||||
|
func (_u *UserUpdateOne) AddAnnouncementReadIDs(ids ...int64) *UserUpdateOne {
|
||||||
|
_u.mutation.AddAnnouncementReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAnnouncementReads adds the "announcement_reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *UserUpdateOne) AddAnnouncementReads(v ...*AnnouncementRead) *UserUpdateOne {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.AddAnnouncementReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
// AddAllowedGroupIDs adds the "allowed_groups" edge to the Group entity by IDs.
|
||||||
func (_u *UserUpdateOne) AddAllowedGroupIDs(ids ...int64) *UserUpdateOne {
|
func (_u *UserUpdateOne) AddAllowedGroupIDs(ids ...int64) *UserUpdateOne {
|
||||||
_u.mutation.AddAllowedGroupIDs(ids...)
|
_u.mutation.AddAllowedGroupIDs(ids...)
|
||||||
@@ -1356,6 +1576,27 @@ func (_u *UserUpdateOne) RemoveAssignedSubscriptions(v ...*UserSubscription) *Us
|
|||||||
return _u.RemoveAssignedSubscriptionIDs(ids...)
|
return _u.RemoveAssignedSubscriptionIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearAnnouncementReads clears all "announcement_reads" edges to the AnnouncementRead entity.
|
||||||
|
func (_u *UserUpdateOne) ClearAnnouncementReads() *UserUpdateOne {
|
||||||
|
_u.mutation.ClearAnnouncementReads()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAnnouncementReadIDs removes the "announcement_reads" edge to AnnouncementRead entities by IDs.
|
||||||
|
func (_u *UserUpdateOne) RemoveAnnouncementReadIDs(ids ...int64) *UserUpdateOne {
|
||||||
|
_u.mutation.RemoveAnnouncementReadIDs(ids...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAnnouncementReads removes "announcement_reads" edges to AnnouncementRead entities.
|
||||||
|
func (_u *UserUpdateOne) RemoveAnnouncementReads(v ...*AnnouncementRead) *UserUpdateOne {
|
||||||
|
ids := make([]int64, len(v))
|
||||||
|
for i := range v {
|
||||||
|
ids[i] = v[i].ID
|
||||||
|
}
|
||||||
|
return _u.RemoveAnnouncementReadIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
// ClearAllowedGroups clears all "allowed_groups" edges to the Group entity.
|
// ClearAllowedGroups clears all "allowed_groups" edges to the Group entity.
|
||||||
func (_u *UserUpdateOne) ClearAllowedGroups() *UserUpdateOne {
|
func (_u *UserUpdateOne) ClearAllowedGroups() *UserUpdateOne {
|
||||||
_u.mutation.ClearAllowedGroups()
|
_u.mutation.ClearAllowedGroups()
|
||||||
@@ -1593,6 +1834,21 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
|||||||
if value, ok := _u.mutation.Notes(); ok {
|
if value, ok := _u.mutation.Notes(); ok {
|
||||||
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
_spec.SetField(user.FieldNotes, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.TotpSecretEncrypted(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpSecretEncrypted, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TotpSecretEncryptedCleared() {
|
||||||
|
_spec.ClearField(user.FieldTotpSecretEncrypted, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.TotpEnabled(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabled, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.TotpEnabledAt(); ok {
|
||||||
|
_spec.SetField(user.FieldTotpEnabledAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.TotpEnabledAtCleared() {
|
||||||
|
_spec.ClearField(user.FieldTotpEnabledAt, field.TypeTime)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -1773,6 +2029,51 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
|||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
|
if _u.mutation.AnnouncementReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.RemovedAnnouncementReadsIDs(); len(nodes) > 0 && !_u.mutation.AnnouncementReadsCleared() {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
|
}
|
||||||
|
if nodes := _u.mutation.AnnouncementReadsIDs(); len(nodes) > 0 {
|
||||||
|
edge := &sqlgraph.EdgeSpec{
|
||||||
|
Rel: sqlgraph.O2M,
|
||||||
|
Inverse: false,
|
||||||
|
Table: user.AnnouncementReadsTable,
|
||||||
|
Columns: []string{user.AnnouncementReadsColumn},
|
||||||
|
Bidi: false,
|
||||||
|
Target: &sqlgraph.EdgeTarget{
|
||||||
|
IDSpec: sqlgraph.NewFieldSpec(announcementread.FieldID, field.TypeInt64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, k := range nodes {
|
||||||
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
|
}
|
||||||
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
|
}
|
||||||
if _u.mutation.AllowedGroupsCleared() {
|
if _u.mutation.AllowedGroupsCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2M,
|
Rel: sqlgraph.M2M,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/Wei-Shaw/sub2api
|
module github.com/Wei-Shaw/sub2api
|
||||||
|
|
||||||
go 1.25.5
|
go 1.25.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.14.5
|
entgo.io/ent v0.14.5
|
||||||
@@ -8,9 +8,11 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/google/wire v0.7.0
|
github.com/google/wire v0.7.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/imroc/req/v3 v3.57.0
|
github.com/imroc/req/v3 v3.57.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/redis/go-redis/v9 v9.17.2
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.6
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
|
||||||
@@ -29,11 +31,13 @@ require (
|
|||||||
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@@ -44,11 +48,13 @@ require (
|
|||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/docker v28.5.1+incompatible // indirect
|
github.com/docker/docker v28.5.1+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
@@ -93,6 +99,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
@@ -100,13 +107,15 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/pquerna/otp v1.5.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.1 // indirect
|
github.com/refraction-networking/utls v1.8.1 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
@@ -135,7 +144,7 @@ require (
|
|||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/mod v0.30.0 // indirect
|
golang.org/x/mod v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
@@ -144,4 +153,8 @@ require (
|
|||||||
google.golang.org/grpc v1.75.1 // indirect
|
google.golang.org/grpc v1.75.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
modernc.org/libc v1.67.6 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/sqlite v1.44.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
|||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -51,6 +53,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||||
|
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
@@ -61,6 +65,8 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM
|
|||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
@@ -113,6 +119,8 @@ 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/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
@@ -135,6 +143,7 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -193,6 +202,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
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 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
@@ -208,6 +219,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
@@ -218,8 +231,12 @@ github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4Vi
|
|||||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@@ -330,6 +347,8 @@ golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
|||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
@@ -357,6 +376,7 @@ golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
|||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
|
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
|
||||||
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||||
|
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -379,4 +399,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/sqlite v1.44.1 h1:qybx/rNpfQipX/t47OxbHmkkJuv2JWifCMH8SVUiDas=
|
||||||
|
modernc.org/sqlite v1.44.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ const (
|
|||||||
RunModeSimple = "simple"
|
RunModeSimple = "simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultCSPPolicy = "default-src 'self'; script-src 'self' https://challenges.cloudflare.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'"
|
// DefaultCSPPolicy is the default Content-Security-Policy with nonce support
|
||||||
|
// __CSP_NONCE__ will be replaced with actual nonce at request time by the SecurityHeaders middleware
|
||||||
|
const DefaultCSPPolicy = "default-src 'self'; script-src 'self' __CSP_NONCE__ https://challenges.cloudflare.com https://static.cloudflareinsights.com; 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'"
|
||||||
|
|
||||||
// 连接池隔离策略常量
|
// 连接池隔离策略常量
|
||||||
// 用于控制上游 HTTP 连接池的隔离粒度,影响连接复用和资源消耗
|
// 用于控制上游 HTTP 连接池的隔离粒度,影响连接复用和资源消耗
|
||||||
@@ -36,33 +38,31 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `mapstructure:"server"`
|
Server ServerConfig `mapstructure:"server"`
|
||||||
CORS CORSConfig `mapstructure:"cors"`
|
CORS CORSConfig `mapstructure:"cors"`
|
||||||
Security SecurityConfig `mapstructure:"security"`
|
Security SecurityConfig `mapstructure:"security"`
|
||||||
Billing BillingConfig `mapstructure:"billing"`
|
Billing BillingConfig `mapstructure:"billing"`
|
||||||
Turnstile TurnstileConfig `mapstructure:"turnstile"`
|
Turnstile TurnstileConfig `mapstructure:"turnstile"`
|
||||||
Database DatabaseConfig `mapstructure:"database"`
|
Database DatabaseConfig `mapstructure:"database"`
|
||||||
Redis RedisConfig `mapstructure:"redis"`
|
Redis RedisConfig `mapstructure:"redis"`
|
||||||
JWT JWTConfig `mapstructure:"jwt"`
|
Ops OpsConfig `mapstructure:"ops"`
|
||||||
LinuxDo LinuxDoConnectConfig `mapstructure:"linuxdo_connect"`
|
JWT JWTConfig `mapstructure:"jwt"`
|
||||||
Default DefaultConfig `mapstructure:"default"`
|
Totp TotpConfig `mapstructure:"totp"`
|
||||||
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
LinuxDo LinuxDoConnectConfig `mapstructure:"linuxdo_connect"`
|
||||||
Pricing PricingConfig `mapstructure:"pricing"`
|
Default DefaultConfig `mapstructure:"default"`
|
||||||
Gateway GatewayConfig `mapstructure:"gateway"`
|
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
||||||
Concurrency ConcurrencyConfig `mapstructure:"concurrency"`
|
Pricing PricingConfig `mapstructure:"pricing"`
|
||||||
TokenRefresh TokenRefreshConfig `mapstructure:"token_refresh"`
|
Gateway GatewayConfig `mapstructure:"gateway"`
|
||||||
RunMode string `mapstructure:"run_mode" yaml:"run_mode"`
|
APIKeyAuth APIKeyAuthCacheConfig `mapstructure:"api_key_auth_cache"`
|
||||||
Timezone string `mapstructure:"timezone"` // e.g. "Asia/Shanghai", "UTC"
|
Dashboard DashboardCacheConfig `mapstructure:"dashboard_cache"`
|
||||||
Gemini GeminiConfig `mapstructure:"gemini"`
|
DashboardAgg DashboardAggregationConfig `mapstructure:"dashboard_aggregation"`
|
||||||
Update UpdateConfig `mapstructure:"update"`
|
UsageCleanup UsageCleanupConfig `mapstructure:"usage_cleanup"`
|
||||||
}
|
Concurrency ConcurrencyConfig `mapstructure:"concurrency"`
|
||||||
|
TokenRefresh TokenRefreshConfig `mapstructure:"token_refresh"`
|
||||||
// UpdateConfig 在线更新相关配置
|
RunMode string `mapstructure:"run_mode" yaml:"run_mode"`
|
||||||
type UpdateConfig struct {
|
Timezone string `mapstructure:"timezone"` // e.g. "Asia/Shanghai", "UTC"
|
||||||
// ProxyURL 用于访问 GitHub 的代理地址
|
Gemini GeminiConfig `mapstructure:"gemini"`
|
||||||
// 支持 http/https/socks5/socks5h 协议
|
Update UpdateConfig `mapstructure:"update"`
|
||||||
// 例如: "http://127.0.0.1:7890", "socks5://127.0.0.1:1080"
|
|
||||||
ProxyURL string `mapstructure:"proxy_url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeminiConfig struct {
|
type GeminiConfig struct {
|
||||||
@@ -87,6 +87,33 @@ type GeminiTierQuotaConfig struct {
|
|||||||
CooldownMinutes *int `mapstructure:"cooldown_minutes" json:"cooldown_minutes"`
|
CooldownMinutes *int `mapstructure:"cooldown_minutes" json:"cooldown_minutes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateConfig struct {
|
||||||
|
// ProxyURL 用于访问 GitHub 的代理地址
|
||||||
|
// 支持 http/https/socks5/socks5h 协议
|
||||||
|
// 例如: "http://127.0.0.1:7890", "socks5://127.0.0.1:1080"
|
||||||
|
ProxyURL string `mapstructure:"proxy_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinuxDoConnectConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
ClientID string `mapstructure:"client_id"`
|
||||||
|
ClientSecret string `mapstructure:"client_secret"`
|
||||||
|
AuthorizeURL string `mapstructure:"authorize_url"`
|
||||||
|
TokenURL string `mapstructure:"token_url"`
|
||||||
|
UserInfoURL string `mapstructure:"userinfo_url"`
|
||||||
|
Scopes string `mapstructure:"scopes"`
|
||||||
|
RedirectURL string `mapstructure:"redirect_url"` // 后端回调地址(需在提供方后台登记)
|
||||||
|
FrontendRedirectURL string `mapstructure:"frontend_redirect_url"` // 前端接收 token 的路由(默认:/auth/linuxdo/callback)
|
||||||
|
TokenAuthMethod string `mapstructure:"token_auth_method"` // client_secret_post / client_secret_basic / none
|
||||||
|
UsePKCE bool `mapstructure:"use_pkce"`
|
||||||
|
|
||||||
|
// 可选:用于从 userinfo JSON 中提取字段的 gjson 路径。
|
||||||
|
// 为空时,服务端会尝试一组常见字段名。
|
||||||
|
UserInfoEmailPath string `mapstructure:"userinfo_email_path"`
|
||||||
|
UserInfoIDPath string `mapstructure:"userinfo_id_path"`
|
||||||
|
UserInfoUsernamePath string `mapstructure:"userinfo_username_path"`
|
||||||
|
}
|
||||||
|
|
||||||
// TokenRefreshConfig OAuth token自动刷新配置
|
// TokenRefreshConfig OAuth token自动刷新配置
|
||||||
type TokenRefreshConfig struct {
|
type TokenRefreshConfig struct {
|
||||||
// 是否启用自动刷新
|
// 是否启用自动刷新
|
||||||
@@ -209,6 +236,10 @@ type GatewayConfig struct {
|
|||||||
// ConcurrencySlotTTLMinutes: 并发槽位过期时间(分钟)
|
// ConcurrencySlotTTLMinutes: 并发槽位过期时间(分钟)
|
||||||
// 应大于最长 LLM 请求时间,防止请求完成前槽位过期
|
// 应大于最长 LLM 请求时间,防止请求完成前槽位过期
|
||||||
ConcurrencySlotTTLMinutes int `mapstructure:"concurrency_slot_ttl_minutes"`
|
ConcurrencySlotTTLMinutes int `mapstructure:"concurrency_slot_ttl_minutes"`
|
||||||
|
// SessionIdleTimeoutMinutes: 会话空闲超时时间(分钟),默认 5 分钟
|
||||||
|
// 用于 Anthropic OAuth/SetupToken 账号的会话数量限制功能
|
||||||
|
// 空闲超过此时间的会话将被自动释放
|
||||||
|
SessionIdleTimeoutMinutes int `mapstructure:"session_idle_timeout_minutes"`
|
||||||
|
|
||||||
// StreamDataIntervalTimeout: 流数据间隔超时(秒),0表示禁用
|
// StreamDataIntervalTimeout: 流数据间隔超时(秒),0表示禁用
|
||||||
StreamDataIntervalTimeout int `mapstructure:"stream_data_interval_timeout"`
|
StreamDataIntervalTimeout int `mapstructure:"stream_data_interval_timeout"`
|
||||||
@@ -228,8 +259,43 @@ type GatewayConfig struct {
|
|||||||
// 是否允许对部分 400 错误触发 failover(默认关闭以避免改变语义)
|
// 是否允许对部分 400 错误触发 failover(默认关闭以避免改变语义)
|
||||||
FailoverOn400 bool `mapstructure:"failover_on_400"`
|
FailoverOn400 bool `mapstructure:"failover_on_400"`
|
||||||
|
|
||||||
|
// 账户切换最大次数(遇到上游错误时切换到其他账户的次数上限)
|
||||||
|
MaxAccountSwitches int `mapstructure:"max_account_switches"`
|
||||||
|
// Gemini 账户切换最大次数(Gemini 平台单独配置,因 API 限制更严格)
|
||||||
|
MaxAccountSwitchesGemini int `mapstructure:"max_account_switches_gemini"`
|
||||||
|
|
||||||
|
// Antigravity 429 fallback 限流时间(分钟),解析重置时间失败时使用
|
||||||
|
AntigravityFallbackCooldownMinutes int `mapstructure:"antigravity_fallback_cooldown_minutes"`
|
||||||
|
|
||||||
// Scheduling: 账号调度相关配置
|
// Scheduling: 账号调度相关配置
|
||||||
Scheduling GatewaySchedulingConfig `mapstructure:"scheduling"`
|
Scheduling GatewaySchedulingConfig `mapstructure:"scheduling"`
|
||||||
|
|
||||||
|
// TLSFingerprint: TLS指纹伪装配置
|
||||||
|
TLSFingerprint TLSFingerprintConfig `mapstructure:"tls_fingerprint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintConfig TLS指纹伪装配置
|
||||||
|
// 用于模拟 Claude CLI (Node.js) 的 TLS 握手特征,避免被识别为非官方客户端
|
||||||
|
type TLSFingerprintConfig struct {
|
||||||
|
// Enabled: 是否全局启用TLS指纹功能
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
// Profiles: 预定义的TLS指纹配置模板
|
||||||
|
// key 为模板名称,如 "claude_cli_v2", "chrome_120" 等
|
||||||
|
Profiles map[string]TLSProfileConfig `mapstructure:"profiles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSProfileConfig 单个TLS指纹模板的配置
|
||||||
|
type TLSProfileConfig struct {
|
||||||
|
// Name: 模板显示名称
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
// EnableGREASE: 是否启用GREASE扩展(Chrome使用,Node.js不使用)
|
||||||
|
EnableGREASE bool `mapstructure:"enable_grease"`
|
||||||
|
// CipherSuites: TLS加密套件列表(空则使用内置默认值)
|
||||||
|
CipherSuites []uint16 `mapstructure:"cipher_suites"`
|
||||||
|
// Curves: 椭圆曲线列表(空则使用内置默认值)
|
||||||
|
Curves []uint16 `mapstructure:"curves"`
|
||||||
|
// PointFormats: 点格式列表(空则使用内置默认值)
|
||||||
|
PointFormats []uint8 `mapstructure:"point_formats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GatewaySchedulingConfig accounts scheduling configuration.
|
// GatewaySchedulingConfig accounts scheduling configuration.
|
||||||
@@ -242,11 +308,37 @@ type GatewaySchedulingConfig struct {
|
|||||||
FallbackWaitTimeout time.Duration `mapstructure:"fallback_wait_timeout"`
|
FallbackWaitTimeout time.Duration `mapstructure:"fallback_wait_timeout"`
|
||||||
FallbackMaxWaiting int `mapstructure:"fallback_max_waiting"`
|
FallbackMaxWaiting int `mapstructure:"fallback_max_waiting"`
|
||||||
|
|
||||||
|
// 兜底层账户选择策略: "last_used"(按最后使用时间排序,默认) 或 "random"(随机)
|
||||||
|
FallbackSelectionMode string `mapstructure:"fallback_selection_mode"`
|
||||||
|
|
||||||
// 负载计算
|
// 负载计算
|
||||||
LoadBatchEnabled bool `mapstructure:"load_batch_enabled"`
|
LoadBatchEnabled bool `mapstructure:"load_batch_enabled"`
|
||||||
|
|
||||||
// 过期槽位清理周期(0 表示禁用)
|
// 过期槽位清理周期(0 表示禁用)
|
||||||
SlotCleanupInterval time.Duration `mapstructure:"slot_cleanup_interval"`
|
SlotCleanupInterval time.Duration `mapstructure:"slot_cleanup_interval"`
|
||||||
|
|
||||||
|
// 受控回源配置
|
||||||
|
DbFallbackEnabled bool `mapstructure:"db_fallback_enabled"`
|
||||||
|
// 受控回源超时(秒),0 表示不额外收紧超时
|
||||||
|
DbFallbackTimeoutSeconds int `mapstructure:"db_fallback_timeout_seconds"`
|
||||||
|
// 受控回源限流(实例级 QPS),0 表示不限制
|
||||||
|
DbFallbackMaxQPS int `mapstructure:"db_fallback_max_qps"`
|
||||||
|
|
||||||
|
// Outbox 轮询与滞后阈值配置
|
||||||
|
// Outbox 轮询周期(秒)
|
||||||
|
OutboxPollIntervalSeconds int `mapstructure:"outbox_poll_interval_seconds"`
|
||||||
|
// Outbox 滞后告警阈值(秒)
|
||||||
|
OutboxLagWarnSeconds int `mapstructure:"outbox_lag_warn_seconds"`
|
||||||
|
// Outbox 触发强制重建阈值(秒)
|
||||||
|
OutboxLagRebuildSeconds int `mapstructure:"outbox_lag_rebuild_seconds"`
|
||||||
|
// Outbox 连续滞后触发次数
|
||||||
|
OutboxLagRebuildFailures int `mapstructure:"outbox_lag_rebuild_failures"`
|
||||||
|
// Outbox 积压触发重建阈值(行数)
|
||||||
|
OutboxBacklogRebuildRows int `mapstructure:"outbox_backlog_rebuild_rows"`
|
||||||
|
|
||||||
|
// 全量重建周期配置
|
||||||
|
// 全量重建周期(秒),0 表示禁用
|
||||||
|
FullRebuildIntervalSeconds int `mapstructure:"full_rebuild_interval_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerConfig) Address() string {
|
func (s *ServerConfig) Address() string {
|
||||||
@@ -323,43 +415,72 @@ type RedisConfig struct {
|
|||||||
PoolSize int `mapstructure:"pool_size"`
|
PoolSize int `mapstructure:"pool_size"`
|
||||||
// MinIdleConns: 最小空闲连接数,保持热连接减少冷启动延迟
|
// MinIdleConns: 最小空闲连接数,保持热连接减少冷启动延迟
|
||||||
MinIdleConns int `mapstructure:"min_idle_conns"`
|
MinIdleConns int `mapstructure:"min_idle_conns"`
|
||||||
|
// EnableTLS: 是否启用 TLS/SSL 连接
|
||||||
|
EnableTLS bool `mapstructure:"enable_tls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RedisConfig) Address() string {
|
func (r *RedisConfig) Address() string {
|
||||||
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpsConfig struct {
|
||||||
|
// Enabled controls whether ops features should run.
|
||||||
|
//
|
||||||
|
// NOTE: vNext still has a DB-backed feature flag (ops_monitoring_enabled) for runtime on/off.
|
||||||
|
// This config flag is the "hard switch" for deployments that want to disable ops completely.
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
|
||||||
|
// UsePreaggregatedTables prefers ops_metrics_hourly/daily for long-window dashboard queries.
|
||||||
|
UsePreaggregatedTables bool `mapstructure:"use_preaggregated_tables"`
|
||||||
|
|
||||||
|
// Cleanup controls periodic deletion of old ops data to prevent unbounded growth.
|
||||||
|
Cleanup OpsCleanupConfig `mapstructure:"cleanup"`
|
||||||
|
|
||||||
|
// MetricsCollectorCache controls Redis caching for expensive per-window collector queries.
|
||||||
|
MetricsCollectorCache OpsMetricsCollectorCacheConfig `mapstructure:"metrics_collector_cache"`
|
||||||
|
|
||||||
|
// Pre-aggregation configuration.
|
||||||
|
Aggregation OpsAggregationConfig `mapstructure:"aggregation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsCleanupConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
Schedule string `mapstructure:"schedule"`
|
||||||
|
|
||||||
|
// Retention days (0 disables that cleanup target).
|
||||||
|
//
|
||||||
|
// vNext requirement: default 30 days across ops datasets.
|
||||||
|
ErrorLogRetentionDays int `mapstructure:"error_log_retention_days"`
|
||||||
|
MinuteMetricsRetentionDays int `mapstructure:"minute_metrics_retention_days"`
|
||||||
|
HourlyMetricsRetentionDays int `mapstructure:"hourly_metrics_retention_days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsAggregationConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsMetricsCollectorCacheConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
TTL time.Duration `mapstructure:"ttl"`
|
||||||
|
}
|
||||||
|
|
||||||
type JWTConfig struct {
|
type JWTConfig struct {
|
||||||
Secret string `mapstructure:"secret"`
|
Secret string `mapstructure:"secret"`
|
||||||
ExpireHour int `mapstructure:"expire_hour"`
|
ExpireHour int `mapstructure:"expire_hour"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TurnstileConfig struct {
|
// TotpConfig TOTP 双因素认证配置
|
||||||
Required bool `mapstructure:"required"`
|
type TotpConfig struct {
|
||||||
|
// EncryptionKey 用于加密 TOTP 密钥的 AES-256 密钥(32 字节 hex 编码)
|
||||||
|
// 如果为空,将自动生成一个随机密钥(仅适用于开发环境)
|
||||||
|
EncryptionKey string `mapstructure:"encryption_key"`
|
||||||
|
// EncryptionKeyConfigured 标记加密密钥是否为手动配置(非自动生成)
|
||||||
|
// 只有手动配置了密钥才允许在管理后台启用 TOTP 功能
|
||||||
|
EncryptionKeyConfigured bool `mapstructure:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinuxDoConnectConfig 用于 LinuxDo Connect OAuth 登录(终端用户 SSO)。
|
type TurnstileConfig struct {
|
||||||
//
|
Required bool `mapstructure:"required"`
|
||||||
// 注意:这与上游账号的 OAuth(例如 OpenAI/Gemini 账号接入)不是一回事。
|
|
||||||
// 这里是用于登录 Sub2API 本身的用户体系。
|
|
||||||
type LinuxDoConnectConfig struct {
|
|
||||||
Enabled bool `mapstructure:"enabled"`
|
|
||||||
ClientID string `mapstructure:"client_id"`
|
|
||||||
ClientSecret string `mapstructure:"client_secret"`
|
|
||||||
AuthorizeURL string `mapstructure:"authorize_url"`
|
|
||||||
TokenURL string `mapstructure:"token_url"`
|
|
||||||
UserInfoURL string `mapstructure:"userinfo_url"`
|
|
||||||
Scopes string `mapstructure:"scopes"`
|
|
||||||
RedirectURL string `mapstructure:"redirect_url"` // 后端回调地址(需在提供方后台登记)
|
|
||||||
FrontendRedirectURL string `mapstructure:"frontend_redirect_url"` // 前端接收 token 的路由(默认:/auth/linuxdo/callback)
|
|
||||||
TokenAuthMethod string `mapstructure:"token_auth_method"` // client_secret_post / client_secret_basic / none
|
|
||||||
UsePKCE bool `mapstructure:"use_pkce"`
|
|
||||||
|
|
||||||
// 可选:用于从 userinfo JSON 中提取字段的 gjson 路径。
|
|
||||||
// 为空时,服务端会尝试一组常见字段名。
|
|
||||||
UserInfoEmailPath string `mapstructure:"userinfo_email_path"`
|
|
||||||
UserInfoIDPath string `mapstructure:"userinfo_id_path"`
|
|
||||||
UserInfoUsernamePath string `mapstructure:"userinfo_username_path"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultConfig struct {
|
type DefaultConfig struct {
|
||||||
@@ -375,6 +496,69 @@ type RateLimitConfig struct {
|
|||||||
OverloadCooldownMinutes int `mapstructure:"overload_cooldown_minutes"` // 529过载冷却时间(分钟)
|
OverloadCooldownMinutes int `mapstructure:"overload_cooldown_minutes"` // 529过载冷却时间(分钟)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// APIKeyAuthCacheConfig API Key 认证缓存配置
|
||||||
|
type APIKeyAuthCacheConfig struct {
|
||||||
|
L1Size int `mapstructure:"l1_size"`
|
||||||
|
L1TTLSeconds int `mapstructure:"l1_ttl_seconds"`
|
||||||
|
L2TTLSeconds int `mapstructure:"l2_ttl_seconds"`
|
||||||
|
NegativeTTLSeconds int `mapstructure:"negative_ttl_seconds"`
|
||||||
|
JitterPercent int `mapstructure:"jitter_percent"`
|
||||||
|
Singleflight bool `mapstructure:"singleflight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardCacheConfig 仪表盘统计缓存配置
|
||||||
|
type DashboardCacheConfig struct {
|
||||||
|
// Enabled: 是否启用仪表盘缓存
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
// KeyPrefix: Redis key 前缀,用于多环境隔离
|
||||||
|
KeyPrefix string `mapstructure:"key_prefix"`
|
||||||
|
// StatsFreshTTLSeconds: 缓存命中认为“新鲜”的时间窗口(秒)
|
||||||
|
StatsFreshTTLSeconds int `mapstructure:"stats_fresh_ttl_seconds"`
|
||||||
|
// StatsTTLSeconds: Redis 缓存总 TTL(秒)
|
||||||
|
StatsTTLSeconds int `mapstructure:"stats_ttl_seconds"`
|
||||||
|
// StatsRefreshTimeoutSeconds: 异步刷新超时(秒)
|
||||||
|
StatsRefreshTimeoutSeconds int `mapstructure:"stats_refresh_timeout_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardAggregationConfig 仪表盘预聚合配置
|
||||||
|
type DashboardAggregationConfig struct {
|
||||||
|
// Enabled: 是否启用预聚合作业
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
// IntervalSeconds: 聚合刷新间隔(秒)
|
||||||
|
IntervalSeconds int `mapstructure:"interval_seconds"`
|
||||||
|
// LookbackSeconds: 回看窗口(秒)
|
||||||
|
LookbackSeconds int `mapstructure:"lookback_seconds"`
|
||||||
|
// BackfillEnabled: 是否允许全量回填
|
||||||
|
BackfillEnabled bool `mapstructure:"backfill_enabled"`
|
||||||
|
// BackfillMaxDays: 回填最大跨度(天)
|
||||||
|
BackfillMaxDays int `mapstructure:"backfill_max_days"`
|
||||||
|
// Retention: 各表保留窗口(天)
|
||||||
|
Retention DashboardAggregationRetentionConfig `mapstructure:"retention"`
|
||||||
|
// RecomputeDays: 启动时重算最近 N 天
|
||||||
|
RecomputeDays int `mapstructure:"recompute_days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardAggregationRetentionConfig 预聚合保留窗口
|
||||||
|
type DashboardAggregationRetentionConfig struct {
|
||||||
|
UsageLogsDays int `mapstructure:"usage_logs_days"`
|
||||||
|
HourlyDays int `mapstructure:"hourly_days"`
|
||||||
|
DailyDays int `mapstructure:"daily_days"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageCleanupConfig 使用记录清理任务配置
|
||||||
|
type UsageCleanupConfig struct {
|
||||||
|
// Enabled: 是否启用清理任务执行器
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
// MaxRangeDays: 单次任务允许的最大时间跨度(天)
|
||||||
|
MaxRangeDays int `mapstructure:"max_range_days"`
|
||||||
|
// BatchSize: 单批删除数量
|
||||||
|
BatchSize int `mapstructure:"batch_size"`
|
||||||
|
// WorkerIntervalSeconds: 后台任务轮询间隔(秒)
|
||||||
|
WorkerIntervalSeconds int `mapstructure:"worker_interval_seconds"`
|
||||||
|
// TaskTimeoutSeconds: 单次任务最大执行时长(秒)
|
||||||
|
TaskTimeoutSeconds int `mapstructure:"task_timeout_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
func NormalizeRunMode(value string) string {
|
func NormalizeRunMode(value string) string {
|
||||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||||
switch normalized {
|
switch normalized {
|
||||||
@@ -440,6 +624,7 @@ func Load() (*Config, error) {
|
|||||||
cfg.LinuxDo.UserInfoEmailPath = strings.TrimSpace(cfg.LinuxDo.UserInfoEmailPath)
|
cfg.LinuxDo.UserInfoEmailPath = strings.TrimSpace(cfg.LinuxDo.UserInfoEmailPath)
|
||||||
cfg.LinuxDo.UserInfoIDPath = strings.TrimSpace(cfg.LinuxDo.UserInfoIDPath)
|
cfg.LinuxDo.UserInfoIDPath = strings.TrimSpace(cfg.LinuxDo.UserInfoIDPath)
|
||||||
cfg.LinuxDo.UserInfoUsernamePath = strings.TrimSpace(cfg.LinuxDo.UserInfoUsernamePath)
|
cfg.LinuxDo.UserInfoUsernamePath = strings.TrimSpace(cfg.LinuxDo.UserInfoUsernamePath)
|
||||||
|
cfg.Dashboard.KeyPrefix = strings.TrimSpace(cfg.Dashboard.KeyPrefix)
|
||||||
cfg.CORS.AllowedOrigins = normalizeStringSlice(cfg.CORS.AllowedOrigins)
|
cfg.CORS.AllowedOrigins = normalizeStringSlice(cfg.CORS.AllowedOrigins)
|
||||||
cfg.Security.ResponseHeaders.AdditionalAllowed = normalizeStringSlice(cfg.Security.ResponseHeaders.AdditionalAllowed)
|
cfg.Security.ResponseHeaders.AdditionalAllowed = normalizeStringSlice(cfg.Security.ResponseHeaders.AdditionalAllowed)
|
||||||
cfg.Security.ResponseHeaders.ForceRemove = normalizeStringSlice(cfg.Security.ResponseHeaders.ForceRemove)
|
cfg.Security.ResponseHeaders.ForceRemove = normalizeStringSlice(cfg.Security.ResponseHeaders.ForceRemove)
|
||||||
@@ -454,6 +639,20 @@ func Load() (*Config, error) {
|
|||||||
log.Println("Warning: JWT secret auto-generated. Consider setting a fixed secret for production.")
|
log.Println("Warning: JWT secret auto-generated. Consider setting a fixed secret for production.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 == "" {
|
||||||
|
key, err := generateJWTSecret(32) // Reuse the same random generation function
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("generate totp encryption key error: %w", err)
|
||||||
|
}
|
||||||
|
cfg.Totp.EncryptionKey = key
|
||||||
|
cfg.Totp.EncryptionKeyConfigured = false
|
||||||
|
log.Println("Warning: TOTP encryption key auto-generated. Consider setting a fixed key for production.")
|
||||||
|
} else {
|
||||||
|
cfg.Totp.EncryptionKeyConfigured = true
|
||||||
|
}
|
||||||
|
|
||||||
if err := cfg.Validate(); err != nil {
|
if err := cfg.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("validate config error: %w", err)
|
return nil, fmt.Errorf("validate config error: %w", err)
|
||||||
}
|
}
|
||||||
@@ -478,81 +677,6 @@ func Load() (*Config, error) {
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAbsoluteHTTPURL 校验一个绝对 http(s) URL(禁止 fragment)。
|
|
||||||
func ValidateAbsoluteHTTPURL(raw string) error {
|
|
||||||
raw = strings.TrimSpace(raw)
|
|
||||||
if raw == "" {
|
|
||||||
return fmt.Errorf("empty url")
|
|
||||||
}
|
|
||||||
u, err := url.Parse(raw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !u.IsAbs() {
|
|
||||||
return fmt.Errorf("must be absolute")
|
|
||||||
}
|
|
||||||
if !isHTTPScheme(u.Scheme) {
|
|
||||||
return fmt.Errorf("unsupported scheme: %s", u.Scheme)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(u.Host) == "" {
|
|
||||||
return fmt.Errorf("missing host")
|
|
||||||
}
|
|
||||||
if u.Fragment != "" {
|
|
||||||
return fmt.Errorf("must not include fragment")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateFrontendRedirectURL 校验前端回调地址:
|
|
||||||
// - 允许同源相对路径(以 / 开头)
|
|
||||||
// - 或绝对 http(s) URL(禁止 fragment)
|
|
||||||
func ValidateFrontendRedirectURL(raw string) error {
|
|
||||||
raw = strings.TrimSpace(raw)
|
|
||||||
if raw == "" {
|
|
||||||
return fmt.Errorf("empty url")
|
|
||||||
}
|
|
||||||
if strings.ContainsAny(raw, "\r\n") {
|
|
||||||
return fmt.Errorf("contains invalid characters")
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(raw, "/") {
|
|
||||||
if strings.HasPrefix(raw, "//") {
|
|
||||||
return fmt.Errorf("must not start with //")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
u, err := url.Parse(raw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !u.IsAbs() {
|
|
||||||
return fmt.Errorf("must be absolute http(s) url or relative path")
|
|
||||||
}
|
|
||||||
if !isHTTPScheme(u.Scheme) {
|
|
||||||
return fmt.Errorf("unsupported scheme: %s", u.Scheme)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(u.Host) == "" {
|
|
||||||
return fmt.Errorf("missing host")
|
|
||||||
}
|
|
||||||
if u.Fragment != "" {
|
|
||||||
return fmt.Errorf("must not include fragment")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHTTPScheme(scheme string) bool {
|
|
||||||
return strings.EqualFold(scheme, "http") || strings.EqualFold(scheme, "https")
|
|
||||||
}
|
|
||||||
|
|
||||||
func warnIfInsecureURL(field, raw string) {
|
|
||||||
u, err := url.Parse(strings.TrimSpace(raw))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.EqualFold(u.Scheme, "http") {
|
|
||||||
log.Printf("Warning: %s uses http scheme; use https in production to avoid token leakage.", field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDefaults() {
|
func setDefaults() {
|
||||||
viper.SetDefault("run_mode", RunModeStandard)
|
viper.SetDefault("run_mode", RunModeStandard)
|
||||||
|
|
||||||
@@ -602,7 +726,7 @@ func setDefaults() {
|
|||||||
// Turnstile
|
// Turnstile
|
||||||
viper.SetDefault("turnstile.required", false)
|
viper.SetDefault("turnstile.required", false)
|
||||||
|
|
||||||
// LinuxDo Connect OAuth 登录(终端用户 SSO)
|
// LinuxDo Connect OAuth 登录
|
||||||
viper.SetDefault("linuxdo_connect.enabled", false)
|
viper.SetDefault("linuxdo_connect.enabled", false)
|
||||||
viper.SetDefault("linuxdo_connect.client_id", "")
|
viper.SetDefault("linuxdo_connect.client_id", "")
|
||||||
viper.SetDefault("linuxdo_connect.client_secret", "")
|
viper.SetDefault("linuxdo_connect.client_secret", "")
|
||||||
@@ -640,11 +764,29 @@ func setDefaults() {
|
|||||||
viper.SetDefault("redis.write_timeout_seconds", 3)
|
viper.SetDefault("redis.write_timeout_seconds", 3)
|
||||||
viper.SetDefault("redis.pool_size", 128)
|
viper.SetDefault("redis.pool_size", 128)
|
||||||
viper.SetDefault("redis.min_idle_conns", 10)
|
viper.SetDefault("redis.min_idle_conns", 10)
|
||||||
|
viper.SetDefault("redis.enable_tls", false)
|
||||||
|
|
||||||
|
// Ops (vNext)
|
||||||
|
viper.SetDefault("ops.enabled", true)
|
||||||
|
viper.SetDefault("ops.use_preaggregated_tables", false)
|
||||||
|
viper.SetDefault("ops.cleanup.enabled", true)
|
||||||
|
viper.SetDefault("ops.cleanup.schedule", "0 2 * * *")
|
||||||
|
// Retention days: vNext defaults to 30 days across ops datasets.
|
||||||
|
viper.SetDefault("ops.cleanup.error_log_retention_days", 30)
|
||||||
|
viper.SetDefault("ops.cleanup.minute_metrics_retention_days", 30)
|
||||||
|
viper.SetDefault("ops.cleanup.hourly_metrics_retention_days", 30)
|
||||||
|
viper.SetDefault("ops.aggregation.enabled", true)
|
||||||
|
viper.SetDefault("ops.metrics_collector_cache.enabled", true)
|
||||||
|
// TTL should be slightly larger than collection interval (1m) to maximize cross-replica cache hits.
|
||||||
|
viper.SetDefault("ops.metrics_collector_cache.ttl", 65*time.Second)
|
||||||
|
|
||||||
// JWT
|
// JWT
|
||||||
viper.SetDefault("jwt.secret", "")
|
viper.SetDefault("jwt.secret", "")
|
||||||
viper.SetDefault("jwt.expire_hour", 24)
|
viper.SetDefault("jwt.expire_hour", 24)
|
||||||
|
|
||||||
|
// TOTP
|
||||||
|
viper.SetDefault("totp.encryption_key", "")
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
// Admin credentials are created via the setup flow (web wizard / CLI / AUTO_SETUP).
|
// Admin credentials are created via the setup flow (web wizard / CLI / AUTO_SETUP).
|
||||||
// Do not ship fixed defaults here to avoid insecure "known credentials" in production.
|
// Do not ship fixed defaults here to avoid insecure "known credentials" in production.
|
||||||
@@ -669,12 +811,48 @@ func setDefaults() {
|
|||||||
// Timezone (default to Asia/Shanghai for Chinese users)
|
// Timezone (default to Asia/Shanghai for Chinese users)
|
||||||
viper.SetDefault("timezone", "Asia/Shanghai")
|
viper.SetDefault("timezone", "Asia/Shanghai")
|
||||||
|
|
||||||
|
// API Key auth cache
|
||||||
|
viper.SetDefault("api_key_auth_cache.l1_size", 65535)
|
||||||
|
viper.SetDefault("api_key_auth_cache.l1_ttl_seconds", 15)
|
||||||
|
viper.SetDefault("api_key_auth_cache.l2_ttl_seconds", 300)
|
||||||
|
viper.SetDefault("api_key_auth_cache.negative_ttl_seconds", 30)
|
||||||
|
viper.SetDefault("api_key_auth_cache.jitter_percent", 10)
|
||||||
|
viper.SetDefault("api_key_auth_cache.singleflight", true)
|
||||||
|
|
||||||
|
// Dashboard cache
|
||||||
|
viper.SetDefault("dashboard_cache.enabled", true)
|
||||||
|
viper.SetDefault("dashboard_cache.key_prefix", "sub2api:")
|
||||||
|
viper.SetDefault("dashboard_cache.stats_fresh_ttl_seconds", 15)
|
||||||
|
viper.SetDefault("dashboard_cache.stats_ttl_seconds", 30)
|
||||||
|
viper.SetDefault("dashboard_cache.stats_refresh_timeout_seconds", 30)
|
||||||
|
|
||||||
|
// Dashboard aggregation
|
||||||
|
viper.SetDefault("dashboard_aggregation.enabled", true)
|
||||||
|
viper.SetDefault("dashboard_aggregation.interval_seconds", 60)
|
||||||
|
viper.SetDefault("dashboard_aggregation.lookback_seconds", 120)
|
||||||
|
viper.SetDefault("dashboard_aggregation.backfill_enabled", false)
|
||||||
|
viper.SetDefault("dashboard_aggregation.backfill_max_days", 31)
|
||||||
|
viper.SetDefault("dashboard_aggregation.retention.usage_logs_days", 90)
|
||||||
|
viper.SetDefault("dashboard_aggregation.retention.hourly_days", 180)
|
||||||
|
viper.SetDefault("dashboard_aggregation.retention.daily_days", 730)
|
||||||
|
viper.SetDefault("dashboard_aggregation.recompute_days", 2)
|
||||||
|
|
||||||
|
// Usage cleanup task
|
||||||
|
viper.SetDefault("usage_cleanup.enabled", true)
|
||||||
|
viper.SetDefault("usage_cleanup.max_range_days", 31)
|
||||||
|
viper.SetDefault("usage_cleanup.batch_size", 5000)
|
||||||
|
viper.SetDefault("usage_cleanup.worker_interval_seconds", 10)
|
||||||
|
viper.SetDefault("usage_cleanup.task_timeout_seconds", 1800)
|
||||||
|
|
||||||
// Gateway
|
// Gateway
|
||||||
viper.SetDefault("gateway.response_header_timeout", 600) // 600秒(10分钟)等待上游响应头,LLM高负载时可能排队较久
|
viper.SetDefault("gateway.response_header_timeout", 600) // 600秒(10分钟)等待上游响应头,LLM高负载时可能排队较久
|
||||||
viper.SetDefault("gateway.log_upstream_error_body", false)
|
viper.SetDefault("gateway.log_upstream_error_body", true)
|
||||||
viper.SetDefault("gateway.log_upstream_error_body_max_bytes", 2048)
|
viper.SetDefault("gateway.log_upstream_error_body_max_bytes", 2048)
|
||||||
viper.SetDefault("gateway.inject_beta_for_apikey", false)
|
viper.SetDefault("gateway.inject_beta_for_apikey", false)
|
||||||
viper.SetDefault("gateway.failover_on_400", false)
|
viper.SetDefault("gateway.failover_on_400", false)
|
||||||
|
viper.SetDefault("gateway.max_account_switches", 10)
|
||||||
|
viper.SetDefault("gateway.max_account_switches_gemini", 3)
|
||||||
|
viper.SetDefault("gateway.antigravity_fallback_cooldown_minutes", 1)
|
||||||
viper.SetDefault("gateway.max_body_size", int64(100*1024*1024))
|
viper.SetDefault("gateway.max_body_size", int64(100*1024*1024))
|
||||||
viper.SetDefault("gateway.connection_pool_isolation", ConnectionPoolIsolationAccountProxy)
|
viper.SetDefault("gateway.connection_pool_isolation", ConnectionPoolIsolationAccountProxy)
|
||||||
// HTTP 上游连接池配置(针对 5000+ 并发用户优化)
|
// HTTP 上游连接池配置(针对 5000+ 并发用户优化)
|
||||||
@@ -689,11 +867,23 @@ func setDefaults() {
|
|||||||
viper.SetDefault("gateway.stream_keepalive_interval", 10)
|
viper.SetDefault("gateway.stream_keepalive_interval", 10)
|
||||||
viper.SetDefault("gateway.max_line_size", 40*1024*1024)
|
viper.SetDefault("gateway.max_line_size", 40*1024*1024)
|
||||||
viper.SetDefault("gateway.scheduling.sticky_session_max_waiting", 3)
|
viper.SetDefault("gateway.scheduling.sticky_session_max_waiting", 3)
|
||||||
viper.SetDefault("gateway.scheduling.sticky_session_wait_timeout", 45*time.Second)
|
viper.SetDefault("gateway.scheduling.sticky_session_wait_timeout", 120*time.Second)
|
||||||
viper.SetDefault("gateway.scheduling.fallback_wait_timeout", 30*time.Second)
|
viper.SetDefault("gateway.scheduling.fallback_wait_timeout", 30*time.Second)
|
||||||
viper.SetDefault("gateway.scheduling.fallback_max_waiting", 100)
|
viper.SetDefault("gateway.scheduling.fallback_max_waiting", 100)
|
||||||
|
viper.SetDefault("gateway.scheduling.fallback_selection_mode", "last_used")
|
||||||
viper.SetDefault("gateway.scheduling.load_batch_enabled", true)
|
viper.SetDefault("gateway.scheduling.load_batch_enabled", true)
|
||||||
viper.SetDefault("gateway.scheduling.slot_cleanup_interval", 30*time.Second)
|
viper.SetDefault("gateway.scheduling.slot_cleanup_interval", 30*time.Second)
|
||||||
|
viper.SetDefault("gateway.scheduling.db_fallback_enabled", true)
|
||||||
|
viper.SetDefault("gateway.scheduling.db_fallback_timeout_seconds", 0)
|
||||||
|
viper.SetDefault("gateway.scheduling.db_fallback_max_qps", 0)
|
||||||
|
viper.SetDefault("gateway.scheduling.outbox_poll_interval_seconds", 1)
|
||||||
|
viper.SetDefault("gateway.scheduling.outbox_lag_warn_seconds", 5)
|
||||||
|
viper.SetDefault("gateway.scheduling.outbox_lag_rebuild_seconds", 10)
|
||||||
|
viper.SetDefault("gateway.scheduling.outbox_lag_rebuild_failures", 3)
|
||||||
|
viper.SetDefault("gateway.scheduling.outbox_backlog_rebuild_rows", 10000)
|
||||||
|
viper.SetDefault("gateway.scheduling.full_rebuild_interval_seconds", 300)
|
||||||
|
// TLS指纹伪装配置(默认关闭,需要账号级别单独启用)
|
||||||
|
viper.SetDefault("gateway.tls_fingerprint.enabled", true)
|
||||||
viper.SetDefault("concurrency.ping_interval", 10)
|
viper.SetDefault("concurrency.ping_interval", 10)
|
||||||
|
|
||||||
// TokenRefresh
|
// TokenRefresh
|
||||||
@@ -710,10 +900,6 @@ func setDefaults() {
|
|||||||
viper.SetDefault("gemini.oauth.client_secret", "")
|
viper.SetDefault("gemini.oauth.client_secret", "")
|
||||||
viper.SetDefault("gemini.oauth.scopes", "")
|
viper.SetDefault("gemini.oauth.scopes", "")
|
||||||
viper.SetDefault("gemini.quota.policy", "")
|
viper.SetDefault("gemini.quota.policy", "")
|
||||||
|
|
||||||
// Update - 在线更新配置
|
|
||||||
// 代理地址为空表示直连 GitHub(适用于海外服务器)
|
|
||||||
viper.SetDefault("update.proxy_url", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
@@ -754,7 +940,8 @@ func (c *Config) Validate() error {
|
|||||||
if method == "none" && !c.LinuxDo.UsePKCE {
|
if method == "none" && !c.LinuxDo.UsePKCE {
|
||||||
return fmt.Errorf("linuxdo_connect.use_pkce must be true when linuxdo_connect.token_auth_method=none")
|
return fmt.Errorf("linuxdo_connect.use_pkce must be true when linuxdo_connect.token_auth_method=none")
|
||||||
}
|
}
|
||||||
if (method == "" || method == "client_secret_post" || method == "client_secret_basic") && strings.TrimSpace(c.LinuxDo.ClientSecret) == "" {
|
if (method == "" || method == "client_secret_post" || method == "client_secret_basic") &&
|
||||||
|
strings.TrimSpace(c.LinuxDo.ClientSecret) == "" {
|
||||||
return fmt.Errorf("linuxdo_connect.client_secret is required when linuxdo_connect.enabled=true and token_auth_method is client_secret_post/client_secret_basic")
|
return fmt.Errorf("linuxdo_connect.client_secret is required when linuxdo_connect.enabled=true and token_auth_method is client_secret_post/client_secret_basic")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(c.LinuxDo.FrontendRedirectURL) == "" {
|
if strings.TrimSpace(c.LinuxDo.FrontendRedirectURL) == "" {
|
||||||
@@ -827,6 +1014,105 @@ func (c *Config) Validate() error {
|
|||||||
if c.Redis.MinIdleConns > c.Redis.PoolSize {
|
if c.Redis.MinIdleConns > c.Redis.PoolSize {
|
||||||
return fmt.Errorf("redis.min_idle_conns cannot exceed redis.pool_size")
|
return fmt.Errorf("redis.min_idle_conns cannot exceed redis.pool_size")
|
||||||
}
|
}
|
||||||
|
if c.Dashboard.Enabled {
|
||||||
|
if c.Dashboard.StatsFreshTTLSeconds <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_fresh_ttl_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.Dashboard.StatsTTLSeconds <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_ttl_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.Dashboard.StatsRefreshTimeoutSeconds <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_refresh_timeout_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.Dashboard.StatsFreshTTLSeconds > c.Dashboard.StatsTTLSeconds {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_fresh_ttl_seconds must be <= dashboard_cache.stats_ttl_seconds")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.Dashboard.StatsFreshTTLSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_fresh_ttl_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Dashboard.StatsTTLSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_ttl_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Dashboard.StatsRefreshTimeoutSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_cache.stats_refresh_timeout_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Enabled {
|
||||||
|
if c.DashboardAgg.IntervalSeconds <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.interval_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.LookbackSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.lookback_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.BackfillMaxDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.backfill_max_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.BackfillEnabled && c.DashboardAgg.BackfillMaxDays == 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.backfill_max_days must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageLogsDays <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.HourlyDays <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.DailyDays <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.daily_days must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.RecomputeDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.recompute_days must be non-negative")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.DashboardAgg.IntervalSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.interval_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.LookbackSeconds < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.lookback_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.BackfillMaxDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.backfill_max_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageLogsDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.HourlyDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.DailyDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.daily_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.RecomputeDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.recompute_days must be non-negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.Enabled {
|
||||||
|
if c.UsageCleanup.MaxRangeDays <= 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.max_range_days must be positive")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.BatchSize <= 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.batch_size must be positive")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.WorkerIntervalSeconds <= 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.worker_interval_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.TaskTimeoutSeconds <= 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.task_timeout_seconds must be positive")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.UsageCleanup.MaxRangeDays < 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.max_range_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.BatchSize < 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.batch_size must be non-negative")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.WorkerIntervalSeconds < 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.worker_interval_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.UsageCleanup.TaskTimeoutSeconds < 0 {
|
||||||
|
return fmt.Errorf("usage_cleanup.task_timeout_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
if c.Gateway.MaxBodySize <= 0 {
|
if c.Gateway.MaxBodySize <= 0 {
|
||||||
return fmt.Errorf("gateway.max_body_size must be positive")
|
return fmt.Errorf("gateway.max_body_size must be positive")
|
||||||
}
|
}
|
||||||
@@ -897,6 +1183,50 @@ func (c *Config) Validate() error {
|
|||||||
if c.Gateway.Scheduling.SlotCleanupInterval < 0 {
|
if c.Gateway.Scheduling.SlotCleanupInterval < 0 {
|
||||||
return fmt.Errorf("gateway.scheduling.slot_cleanup_interval must be non-negative")
|
return fmt.Errorf("gateway.scheduling.slot_cleanup_interval must be non-negative")
|
||||||
}
|
}
|
||||||
|
if c.Gateway.Scheduling.DbFallbackTimeoutSeconds < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.db_fallback_timeout_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.DbFallbackMaxQPS < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.db_fallback_max_qps must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxPollIntervalSeconds <= 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_poll_interval_seconds must be positive")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxLagWarnSeconds < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_lag_warn_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxLagRebuildSeconds < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_lag_rebuild_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxLagRebuildFailures <= 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_lag_rebuild_failures must be positive")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxBacklogRebuildRows < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_backlog_rebuild_rows must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.FullRebuildIntervalSeconds < 0 {
|
||||||
|
return fmt.Errorf("gateway.scheduling.full_rebuild_interval_seconds must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Gateway.Scheduling.OutboxLagWarnSeconds > 0 &&
|
||||||
|
c.Gateway.Scheduling.OutboxLagRebuildSeconds > 0 &&
|
||||||
|
c.Gateway.Scheduling.OutboxLagRebuildSeconds < c.Gateway.Scheduling.OutboxLagWarnSeconds {
|
||||||
|
return fmt.Errorf("gateway.scheduling.outbox_lag_rebuild_seconds must be >= outbox_lag_warn_seconds")
|
||||||
|
}
|
||||||
|
if c.Ops.MetricsCollectorCache.TTL < 0 {
|
||||||
|
return fmt.Errorf("ops.metrics_collector_cache.ttl must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Ops.Cleanup.ErrorLogRetentionDays < 0 {
|
||||||
|
return fmt.Errorf("ops.cleanup.error_log_retention_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Ops.Cleanup.MinuteMetricsRetentionDays < 0 {
|
||||||
|
return fmt.Errorf("ops.cleanup.minute_metrics_retention_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Ops.Cleanup.HourlyMetricsRetentionDays < 0 {
|
||||||
|
return fmt.Errorf("ops.cleanup.hourly_metrics_retention_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.Ops.Cleanup.Enabled && strings.TrimSpace(c.Ops.Cleanup.Schedule) == "" {
|
||||||
|
return fmt.Errorf("ops.cleanup.schedule is required when ops.cleanup.enabled=true")
|
||||||
|
}
|
||||||
if c.Concurrency.PingInterval < 5 || c.Concurrency.PingInterval > 30 {
|
if c.Concurrency.PingInterval < 5 || c.Concurrency.PingInterval > 30 {
|
||||||
return fmt.Errorf("concurrency.ping_interval must be between 5-30 seconds")
|
return fmt.Errorf("concurrency.ping_interval must be between 5-30 seconds")
|
||||||
}
|
}
|
||||||
@@ -973,3 +1303,77 @@ func GetServerAddress() string {
|
|||||||
port := v.GetInt("server.port")
|
port := v.GetInt("server.port")
|
||||||
return fmt.Sprintf("%s:%d", host, port)
|
return fmt.Sprintf("%s:%d", host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAbsoluteHTTPURL 验证是否为有效的绝对 HTTP(S) URL
|
||||||
|
func ValidateAbsoluteHTTPURL(raw string) error {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return fmt.Errorf("empty url")
|
||||||
|
}
|
||||||
|
u, err := url.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !u.IsAbs() {
|
||||||
|
return fmt.Errorf("must be absolute")
|
||||||
|
}
|
||||||
|
if !isHTTPScheme(u.Scheme) {
|
||||||
|
return fmt.Errorf("unsupported scheme: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(u.Host) == "" {
|
||||||
|
return fmt.Errorf("missing host")
|
||||||
|
}
|
||||||
|
if u.Fragment != "" {
|
||||||
|
return fmt.Errorf("must not include fragment")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateFrontendRedirectURL 验证前端重定向 URL(可以是绝对 URL 或相对路径)
|
||||||
|
func ValidateFrontendRedirectURL(raw string) error {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return fmt.Errorf("empty url")
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(raw, "\r\n") {
|
||||||
|
return fmt.Errorf("contains invalid characters")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(raw, "/") {
|
||||||
|
if strings.HasPrefix(raw, "//") {
|
||||||
|
return fmt.Errorf("must not start with //")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !u.IsAbs() {
|
||||||
|
return fmt.Errorf("must be absolute http(s) url or relative path")
|
||||||
|
}
|
||||||
|
if !isHTTPScheme(u.Scheme) {
|
||||||
|
return fmt.Errorf("unsupported scheme: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(u.Host) == "" {
|
||||||
|
return fmt.Errorf("missing host")
|
||||||
|
}
|
||||||
|
if u.Fragment != "" {
|
||||||
|
return fmt.Errorf("must not include fragment")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHTTPScheme 检查是否为 HTTP 或 HTTPS 协议
|
||||||
|
func isHTTPScheme(scheme string) bool {
|
||||||
|
return strings.EqualFold(scheme, "http") || strings.EqualFold(scheme, "https")
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnIfInsecureURL(field, raw string) {
|
||||||
|
u, err := url.Parse(strings.TrimSpace(raw))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.EqualFold(u.Scheme, "http") {
|
||||||
|
log.Printf("Warning: %s uses http scheme; use https in production to avoid token leakage.", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ func TestLoadDefaultSchedulingConfig(t *testing.T) {
|
|||||||
if cfg.Gateway.Scheduling.StickySessionMaxWaiting != 3 {
|
if cfg.Gateway.Scheduling.StickySessionMaxWaiting != 3 {
|
||||||
t.Fatalf("StickySessionMaxWaiting = %d, want 3", cfg.Gateway.Scheduling.StickySessionMaxWaiting)
|
t.Fatalf("StickySessionMaxWaiting = %d, want 3", cfg.Gateway.Scheduling.StickySessionMaxWaiting)
|
||||||
}
|
}
|
||||||
if cfg.Gateway.Scheduling.StickySessionWaitTimeout != 45*time.Second {
|
if cfg.Gateway.Scheduling.StickySessionWaitTimeout != 120*time.Second {
|
||||||
t.Fatalf("StickySessionWaitTimeout = %v, want 45s", cfg.Gateway.Scheduling.StickySessionWaitTimeout)
|
t.Fatalf("StickySessionWaitTimeout = %v, want 120s", cfg.Gateway.Scheduling.StickySessionWaitTimeout)
|
||||||
}
|
}
|
||||||
if cfg.Gateway.Scheduling.FallbackWaitTimeout != 30*time.Second {
|
if cfg.Gateway.Scheduling.FallbackWaitTimeout != 30*time.Second {
|
||||||
t.Fatalf("FallbackWaitTimeout = %v, want 30s", cfg.Gateway.Scheduling.FallbackWaitTimeout)
|
t.Fatalf("FallbackWaitTimeout = %v, want 30s", cfg.Gateway.Scheduling.FallbackWaitTimeout)
|
||||||
@@ -141,3 +141,712 @@ func TestValidateLinuxDoPKCERequiredForPublicClient(t *testing.T) {
|
|||||||
t.Fatalf("Validate() expected use_pkce error, got: %v", err)
|
t.Fatalf("Validate() expected use_pkce error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadDefaultDashboardCacheConfig(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.Dashboard.Enabled {
|
||||||
|
t.Fatalf("Dashboard.Enabled = false, want true")
|
||||||
|
}
|
||||||
|
if cfg.Dashboard.KeyPrefix != "sub2api:" {
|
||||||
|
t.Fatalf("Dashboard.KeyPrefix = %q, want %q", cfg.Dashboard.KeyPrefix, "sub2api:")
|
||||||
|
}
|
||||||
|
if cfg.Dashboard.StatsFreshTTLSeconds != 15 {
|
||||||
|
t.Fatalf("Dashboard.StatsFreshTTLSeconds = %d, want 15", cfg.Dashboard.StatsFreshTTLSeconds)
|
||||||
|
}
|
||||||
|
if cfg.Dashboard.StatsTTLSeconds != 30 {
|
||||||
|
t.Fatalf("Dashboard.StatsTTLSeconds = %d, want 30", cfg.Dashboard.StatsTTLSeconds)
|
||||||
|
}
|
||||||
|
if cfg.Dashboard.StatsRefreshTimeoutSeconds != 30 {
|
||||||
|
t.Fatalf("Dashboard.StatsRefreshTimeoutSeconds = %d, want 30", cfg.Dashboard.StatsRefreshTimeoutSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateDashboardCacheConfigEnabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Dashboard.Enabled = true
|
||||||
|
cfg.Dashboard.StatsFreshTTLSeconds = 10
|
||||||
|
cfg.Dashboard.StatsTTLSeconds = 5
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for stats_fresh_ttl_seconds > stats_ttl_seconds, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "dashboard_cache.stats_fresh_ttl_seconds") {
|
||||||
|
t.Fatalf("Validate() expected stats_fresh_ttl_seconds error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateDashboardCacheConfigDisabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Dashboard.Enabled = false
|
||||||
|
cfg.Dashboard.StatsTTLSeconds = -1
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for negative stats_ttl_seconds, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "dashboard_cache.stats_ttl_seconds") {
|
||||||
|
t.Fatalf("Validate() expected stats_ttl_seconds error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadDefaultDashboardAggregationConfig(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.DashboardAgg.Enabled {
|
||||||
|
t.Fatalf("DashboardAgg.Enabled = false, want true")
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.IntervalSeconds != 60 {
|
||||||
|
t.Fatalf("DashboardAgg.IntervalSeconds = %d, want 60", cfg.DashboardAgg.IntervalSeconds)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.LookbackSeconds != 120 {
|
||||||
|
t.Fatalf("DashboardAgg.LookbackSeconds = %d, want 120", cfg.DashboardAgg.LookbackSeconds)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.BackfillEnabled {
|
||||||
|
t.Fatalf("DashboardAgg.BackfillEnabled = true, want false")
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.BackfillMaxDays != 31 {
|
||||||
|
t.Fatalf("DashboardAgg.BackfillMaxDays = %d, want 31", cfg.DashboardAgg.BackfillMaxDays)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.Retention.UsageLogsDays != 90 {
|
||||||
|
t.Fatalf("DashboardAgg.Retention.UsageLogsDays = %d, want 90", cfg.DashboardAgg.Retention.UsageLogsDays)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.Retention.HourlyDays != 180 {
|
||||||
|
t.Fatalf("DashboardAgg.Retention.HourlyDays = %d, want 180", cfg.DashboardAgg.Retention.HourlyDays)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.Retention.DailyDays != 730 {
|
||||||
|
t.Fatalf("DashboardAgg.Retention.DailyDays = %d, want 730", cfg.DashboardAgg.Retention.DailyDays)
|
||||||
|
}
|
||||||
|
if cfg.DashboardAgg.RecomputeDays != 2 {
|
||||||
|
t.Fatalf("DashboardAgg.RecomputeDays = %d, want 2", cfg.DashboardAgg.RecomputeDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateDashboardAggregationConfigDisabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.DashboardAgg.Enabled = false
|
||||||
|
cfg.DashboardAgg.IntervalSeconds = -1
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for negative dashboard_aggregation.interval_seconds, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "dashboard_aggregation.interval_seconds") {
|
||||||
|
t.Fatalf("Validate() expected interval_seconds error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateDashboardAggregationBackfillMaxDays(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.DashboardAgg.BackfillEnabled = true
|
||||||
|
cfg.DashboardAgg.BackfillMaxDays = 0
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for dashboard_aggregation.backfill_max_days, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "dashboard_aggregation.backfill_max_days") {
|
||||||
|
t.Fatalf("Validate() expected backfill_max_days error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadDefaultUsageCleanupConfig(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.UsageCleanup.Enabled {
|
||||||
|
t.Fatalf("UsageCleanup.Enabled = false, want true")
|
||||||
|
}
|
||||||
|
if cfg.UsageCleanup.MaxRangeDays != 31 {
|
||||||
|
t.Fatalf("UsageCleanup.MaxRangeDays = %d, want 31", cfg.UsageCleanup.MaxRangeDays)
|
||||||
|
}
|
||||||
|
if cfg.UsageCleanup.BatchSize != 5000 {
|
||||||
|
t.Fatalf("UsageCleanup.BatchSize = %d, want 5000", cfg.UsageCleanup.BatchSize)
|
||||||
|
}
|
||||||
|
if cfg.UsageCleanup.WorkerIntervalSeconds != 10 {
|
||||||
|
t.Fatalf("UsageCleanup.WorkerIntervalSeconds = %d, want 10", cfg.UsageCleanup.WorkerIntervalSeconds)
|
||||||
|
}
|
||||||
|
if cfg.UsageCleanup.TaskTimeoutSeconds != 1800 {
|
||||||
|
t.Fatalf("UsageCleanup.TaskTimeoutSeconds = %d, want 1800", cfg.UsageCleanup.TaskTimeoutSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateUsageCleanupConfigEnabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.UsageCleanup.Enabled = true
|
||||||
|
cfg.UsageCleanup.MaxRangeDays = 0
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for usage_cleanup.max_range_days, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "usage_cleanup.max_range_days") {
|
||||||
|
t.Fatalf("Validate() expected max_range_days error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateUsageCleanupConfigDisabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.UsageCleanup.Enabled = false
|
||||||
|
cfg.UsageCleanup.BatchSize = -1
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for usage_cleanup.batch_size, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "usage_cleanup.batch_size") {
|
||||||
|
t.Fatalf("Validate() expected batch_size error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigAddressHelpers(t *testing.T) {
|
||||||
|
server := ServerConfig{Host: "127.0.0.1", Port: 9000}
|
||||||
|
if server.Address() != "127.0.0.1:9000" {
|
||||||
|
t.Fatalf("ServerConfig.Address() = %q", server.Address())
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCfg := DatabaseConfig{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 5432,
|
||||||
|
User: "postgres",
|
||||||
|
Password: "",
|
||||||
|
DBName: "sub2api",
|
||||||
|
SSLMode: "disable",
|
||||||
|
}
|
||||||
|
if !strings.Contains(dbCfg.DSN(), "password=") {
|
||||||
|
} else {
|
||||||
|
t.Fatalf("DatabaseConfig.DSN() should not include password when empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCfg.Password = "secret"
|
||||||
|
if !strings.Contains(dbCfg.DSN(), "password=secret") {
|
||||||
|
t.Fatalf("DatabaseConfig.DSN() missing password")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCfg.Password = ""
|
||||||
|
if strings.Contains(dbCfg.DSNWithTimezone("UTC"), "password=") {
|
||||||
|
t.Fatalf("DatabaseConfig.DSNWithTimezone() should omit password when empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(dbCfg.DSNWithTimezone(""), "TimeZone=Asia/Shanghai") {
|
||||||
|
t.Fatalf("DatabaseConfig.DSNWithTimezone() should use default timezone")
|
||||||
|
}
|
||||||
|
if !strings.Contains(dbCfg.DSNWithTimezone("UTC"), "TimeZone=UTC") {
|
||||||
|
t.Fatalf("DatabaseConfig.DSNWithTimezone() should use provided timezone")
|
||||||
|
}
|
||||||
|
|
||||||
|
redis := RedisConfig{Host: "redis", Port: 6379}
|
||||||
|
if redis.Address() != "redis:6379" {
|
||||||
|
t.Fatalf("RedisConfig.Address() = %q", redis.Address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeStringSlice(t *testing.T) {
|
||||||
|
values := normalizeStringSlice([]string{" a ", "", "b", " ", "c"})
|
||||||
|
if len(values) != 3 || values[0] != "a" || values[1] != "b" || values[2] != "c" {
|
||||||
|
t.Fatalf("normalizeStringSlice() unexpected result: %#v", values)
|
||||||
|
}
|
||||||
|
if normalizeStringSlice(nil) != nil {
|
||||||
|
t.Fatalf("normalizeStringSlice(nil) expected nil slice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetServerAddressFromEnv(t *testing.T) {
|
||||||
|
t.Setenv("SERVER_HOST", "127.0.0.1")
|
||||||
|
t.Setenv("SERVER_PORT", "9090")
|
||||||
|
|
||||||
|
address := GetServerAddress()
|
||||||
|
if address != "127.0.0.1:9090" {
|
||||||
|
t.Fatalf("GetServerAddress() = %q", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAbsoluteHTTPURL(t *testing.T) {
|
||||||
|
if err := ValidateAbsoluteHTTPURL("https://example.com/path"); err != nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL valid url error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ValidateAbsoluteHTTPURL(""); err == nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL should reject empty url")
|
||||||
|
}
|
||||||
|
if err := ValidateAbsoluteHTTPURL("/relative"); err == nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL should reject relative url")
|
||||||
|
}
|
||||||
|
if err := ValidateAbsoluteHTTPURL("ftp://example.com"); err == nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL should reject ftp scheme")
|
||||||
|
}
|
||||||
|
if err := ValidateAbsoluteHTTPURL("https://example.com/#frag"); err == nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL should reject fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFrontendRedirectURL(t *testing.T) {
|
||||||
|
if err := ValidateFrontendRedirectURL("/auth/callback"); err != nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL relative error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("https://example.com/auth"); err != nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL absolute error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("example.com/path"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject non-absolute url")
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("//evil.com"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject // prefix")
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("javascript:alert(1)"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject javascript scheme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarnIfInsecureURL(t *testing.T) {
|
||||||
|
warnIfInsecureURL("test", "http://example.com")
|
||||||
|
warnIfInsecureURL("test", "bad://url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJWTSecretDefaultLength(t *testing.T) {
|
||||||
|
secret, err := generateJWTSecret(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generateJWTSecret error: %v", err)
|
||||||
|
}
|
||||||
|
if len(secret) == 0 {
|
||||||
|
t.Fatalf("generateJWTSecret returned empty string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateOpsCleanupScheduleRequired(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
cfg.Ops.Cleanup.Enabled = true
|
||||||
|
cfg.Ops.Cleanup.Schedule = ""
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for ops.cleanup.schedule")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "ops.cleanup.schedule") {
|
||||||
|
t.Fatalf("Validate() expected ops.cleanup.schedule error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateConcurrencyPingInterval(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
cfg.Concurrency.PingInterval = 3
|
||||||
|
err = cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Validate() expected error for concurrency.ping_interval")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "concurrency.ping_interval") {
|
||||||
|
t.Fatalf("Validate() expected concurrency.ping_interval error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvideConfig(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
if _, err := ProvideConfig(); err != nil {
|
||||||
|
t.Fatalf("ProvideConfig() error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateConfigWithLinuxDoEnabled(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Security.CSP.Enabled = true
|
||||||
|
cfg.Security.CSP.Policy = "default-src 'self'"
|
||||||
|
|
||||||
|
cfg.LinuxDo.Enabled = true
|
||||||
|
cfg.LinuxDo.ClientID = "client"
|
||||||
|
cfg.LinuxDo.ClientSecret = "secret"
|
||||||
|
cfg.LinuxDo.AuthorizeURL = "https://example.com/oauth2/authorize"
|
||||||
|
cfg.LinuxDo.TokenURL = "https://example.com/oauth2/token"
|
||||||
|
cfg.LinuxDo.UserInfoURL = "https://example.com/oauth2/userinfo"
|
||||||
|
cfg.LinuxDo.RedirectURL = "https://example.com/api/v1/auth/oauth/linuxdo/callback"
|
||||||
|
cfg.LinuxDo.FrontendRedirectURL = "/auth/linuxdo/callback"
|
||||||
|
cfg.LinuxDo.TokenAuthMethod = "client_secret_post"
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
t.Fatalf("Validate() unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateJWTSecretStrength(t *testing.T) {
|
||||||
|
if !isWeakJWTSecret("change-me-in-production") {
|
||||||
|
t.Fatalf("isWeakJWTSecret should detect weak secret")
|
||||||
|
}
|
||||||
|
if isWeakJWTSecret("StrongSecretValue") {
|
||||||
|
t.Fatalf("isWeakJWTSecret should accept strong secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJWTSecretWithLength(t *testing.T) {
|
||||||
|
secret, err := generateJWTSecret(16)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("generateJWTSecret error: %v", err)
|
||||||
|
}
|
||||||
|
if len(secret) == 0 {
|
||||||
|
t.Fatalf("generateJWTSecret returned empty string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAbsoluteHTTPURLMissingHost(t *testing.T) {
|
||||||
|
if err := ValidateAbsoluteHTTPURL("https://"); err == nil {
|
||||||
|
t.Fatalf("ValidateAbsoluteHTTPURL should reject missing host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFrontendRedirectURLInvalidChars(t *testing.T) {
|
||||||
|
if err := ValidateFrontendRedirectURL("/auth/\ncallback"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject invalid chars")
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("http://"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject missing host")
|
||||||
|
}
|
||||||
|
if err := ValidateFrontendRedirectURL("mailto:user@example.com"); err == nil {
|
||||||
|
t.Fatalf("ValidateFrontendRedirectURL should reject mailto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarnIfInsecureURLHTTPS(t *testing.T) {
|
||||||
|
warnIfInsecureURL("secure", "https://example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateConfigErrors(t *testing.T) {
|
||||||
|
buildValid := func(t *testing.T) *Config {
|
||||||
|
t.Helper()
|
||||||
|
viper.Reset()
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error: %v", err)
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mutate func(*Config)
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "jwt expire hour positive",
|
||||||
|
mutate: func(c *Config) { c.JWT.ExpireHour = 0 },
|
||||||
|
wantErr: "jwt.expire_hour must be positive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "jwt expire hour max",
|
||||||
|
mutate: func(c *Config) { c.JWT.ExpireHour = 200 },
|
||||||
|
wantErr: "jwt.expire_hour must be <= 168",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "csp policy required",
|
||||||
|
mutate: func(c *Config) { c.Security.CSP.Enabled = true; c.Security.CSP.Policy = "" },
|
||||||
|
wantErr: "security.csp.policy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "linuxdo client id required",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.LinuxDo.Enabled = true
|
||||||
|
c.LinuxDo.ClientID = ""
|
||||||
|
},
|
||||||
|
wantErr: "linuxdo_connect.client_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "linuxdo token auth method",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.LinuxDo.Enabled = true
|
||||||
|
c.LinuxDo.ClientID = "client"
|
||||||
|
c.LinuxDo.ClientSecret = "secret"
|
||||||
|
c.LinuxDo.AuthorizeURL = "https://example.com/authorize"
|
||||||
|
c.LinuxDo.TokenURL = "https://example.com/token"
|
||||||
|
c.LinuxDo.UserInfoURL = "https://example.com/userinfo"
|
||||||
|
c.LinuxDo.RedirectURL = "https://example.com/callback"
|
||||||
|
c.LinuxDo.FrontendRedirectURL = "/auth/callback"
|
||||||
|
c.LinuxDo.TokenAuthMethod = "invalid"
|
||||||
|
},
|
||||||
|
wantErr: "linuxdo_connect.token_auth_method",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "billing circuit breaker threshold",
|
||||||
|
mutate: func(c *Config) { c.Billing.CircuitBreaker.FailureThreshold = 0 },
|
||||||
|
wantErr: "billing.circuit_breaker.failure_threshold",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "billing circuit breaker reset",
|
||||||
|
mutate: func(c *Config) { c.Billing.CircuitBreaker.ResetTimeoutSeconds = 0 },
|
||||||
|
wantErr: "billing.circuit_breaker.reset_timeout_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "billing circuit breaker half open",
|
||||||
|
mutate: func(c *Config) { c.Billing.CircuitBreaker.HalfOpenRequests = 0 },
|
||||||
|
wantErr: "billing.circuit_breaker.half_open_requests",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "database max open conns",
|
||||||
|
mutate: func(c *Config) { c.Database.MaxOpenConns = 0 },
|
||||||
|
wantErr: "database.max_open_conns",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "database max lifetime",
|
||||||
|
mutate: func(c *Config) { c.Database.ConnMaxLifetimeMinutes = -1 },
|
||||||
|
wantErr: "database.conn_max_lifetime_minutes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "database idle exceeds open",
|
||||||
|
mutate: func(c *Config) { c.Database.MaxIdleConns = c.Database.MaxOpenConns + 1 },
|
||||||
|
wantErr: "database.max_idle_conns cannot exceed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis dial timeout",
|
||||||
|
mutate: func(c *Config) { c.Redis.DialTimeoutSeconds = 0 },
|
||||||
|
wantErr: "redis.dial_timeout_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis read timeout",
|
||||||
|
mutate: func(c *Config) { c.Redis.ReadTimeoutSeconds = 0 },
|
||||||
|
wantErr: "redis.read_timeout_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis write timeout",
|
||||||
|
mutate: func(c *Config) { c.Redis.WriteTimeoutSeconds = 0 },
|
||||||
|
wantErr: "redis.write_timeout_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis pool size",
|
||||||
|
mutate: func(c *Config) { c.Redis.PoolSize = 0 },
|
||||||
|
wantErr: "redis.pool_size",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redis idle exceeds pool",
|
||||||
|
mutate: func(c *Config) { c.Redis.MinIdleConns = c.Redis.PoolSize + 1 },
|
||||||
|
wantErr: "redis.min_idle_conns cannot exceed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard cache disabled negative",
|
||||||
|
mutate: func(c *Config) { c.Dashboard.Enabled = false; c.Dashboard.StatsTTLSeconds = -1 },
|
||||||
|
wantErr: "dashboard_cache.stats_ttl_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard cache fresh ttl positive",
|
||||||
|
mutate: func(c *Config) { c.Dashboard.Enabled = true; c.Dashboard.StatsFreshTTLSeconds = 0 },
|
||||||
|
wantErr: "dashboard_cache.stats_fresh_ttl_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation enabled interval",
|
||||||
|
mutate: func(c *Config) { c.DashboardAgg.Enabled = true; c.DashboardAgg.IntervalSeconds = 0 },
|
||||||
|
wantErr: "dashboard_aggregation.interval_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation backfill positive",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.DashboardAgg.Enabled = true
|
||||||
|
c.DashboardAgg.BackfillEnabled = true
|
||||||
|
c.DashboardAgg.BackfillMaxDays = 0
|
||||||
|
},
|
||||||
|
wantErr: "dashboard_aggregation.backfill_max_days",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation retention",
|
||||||
|
mutate: func(c *Config) { c.DashboardAgg.Enabled = true; c.DashboardAgg.Retention.UsageLogsDays = 0 },
|
||||||
|
wantErr: "dashboard_aggregation.retention.usage_logs_days",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation disabled interval",
|
||||||
|
mutate: func(c *Config) { c.DashboardAgg.Enabled = false; c.DashboardAgg.IntervalSeconds = -1 },
|
||||||
|
wantErr: "dashboard_aggregation.interval_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "usage cleanup max range",
|
||||||
|
mutate: func(c *Config) { c.UsageCleanup.Enabled = true; c.UsageCleanup.MaxRangeDays = 0 },
|
||||||
|
wantErr: "usage_cleanup.max_range_days",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "usage cleanup worker interval",
|
||||||
|
mutate: func(c *Config) { c.UsageCleanup.Enabled = true; c.UsageCleanup.WorkerIntervalSeconds = 0 },
|
||||||
|
wantErr: "usage_cleanup.worker_interval_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "usage cleanup batch size",
|
||||||
|
mutate: func(c *Config) { c.UsageCleanup.Enabled = true; c.UsageCleanup.BatchSize = 0 },
|
||||||
|
wantErr: "usage_cleanup.batch_size",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "usage cleanup disabled negative",
|
||||||
|
mutate: func(c *Config) { c.UsageCleanup.Enabled = false; c.UsageCleanup.BatchSize = -1 },
|
||||||
|
wantErr: "usage_cleanup.batch_size",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max body size",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxBodySize = 0 },
|
||||||
|
wantErr: "gateway.max_body_size",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max idle conns",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxIdleConns = 0 },
|
||||||
|
wantErr: "gateway.max_idle_conns",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max idle conns per host",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxIdleConnsPerHost = 0 },
|
||||||
|
wantErr: "gateway.max_idle_conns_per_host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway idle timeout",
|
||||||
|
mutate: func(c *Config) { c.Gateway.IdleConnTimeoutSeconds = 0 },
|
||||||
|
wantErr: "gateway.idle_conn_timeout_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max upstream clients",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxUpstreamClients = 0 },
|
||||||
|
wantErr: "gateway.max_upstream_clients",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway client idle ttl",
|
||||||
|
mutate: func(c *Config) { c.Gateway.ClientIdleTTLSeconds = 0 },
|
||||||
|
wantErr: "gateway.client_idle_ttl_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway concurrency slot ttl",
|
||||||
|
mutate: func(c *Config) { c.Gateway.ConcurrencySlotTTLMinutes = 0 },
|
||||||
|
wantErr: "gateway.concurrency_slot_ttl_minutes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max conns per host",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxConnsPerHost = -1 },
|
||||||
|
wantErr: "gateway.max_conns_per_host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway connection isolation",
|
||||||
|
mutate: func(c *Config) { c.Gateway.ConnectionPoolIsolation = "invalid" },
|
||||||
|
wantErr: "gateway.connection_pool_isolation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway stream keepalive range",
|
||||||
|
mutate: func(c *Config) { c.Gateway.StreamKeepaliveInterval = 4 },
|
||||||
|
wantErr: "gateway.stream_keepalive_interval",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway stream data interval range",
|
||||||
|
mutate: func(c *Config) { c.Gateway.StreamDataIntervalTimeout = 5 },
|
||||||
|
wantErr: "gateway.stream_data_interval_timeout",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway stream data interval negative",
|
||||||
|
mutate: func(c *Config) { c.Gateway.StreamDataIntervalTimeout = -1 },
|
||||||
|
wantErr: "gateway.stream_data_interval_timeout must be non-negative",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max line size",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxLineSize = 1024 },
|
||||||
|
wantErr: "gateway.max_line_size must be at least",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway max line size negative",
|
||||||
|
mutate: func(c *Config) { c.Gateway.MaxLineSize = -1 },
|
||||||
|
wantErr: "gateway.max_line_size must be non-negative",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway scheduling sticky waiting",
|
||||||
|
mutate: func(c *Config) { c.Gateway.Scheduling.StickySessionMaxWaiting = 0 },
|
||||||
|
wantErr: "gateway.scheduling.sticky_session_max_waiting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway scheduling outbox poll",
|
||||||
|
mutate: func(c *Config) { c.Gateway.Scheduling.OutboxPollIntervalSeconds = 0 },
|
||||||
|
wantErr: "gateway.scheduling.outbox_poll_interval_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway scheduling outbox failures",
|
||||||
|
mutate: func(c *Config) { c.Gateway.Scheduling.OutboxLagRebuildFailures = 0 },
|
||||||
|
wantErr: "gateway.scheduling.outbox_lag_rebuild_failures",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gateway outbox lag rebuild",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.Gateway.Scheduling.OutboxLagWarnSeconds = 10
|
||||||
|
c.Gateway.Scheduling.OutboxLagRebuildSeconds = 5
|
||||||
|
},
|
||||||
|
wantErr: "gateway.scheduling.outbox_lag_rebuild_seconds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ops metrics collector ttl",
|
||||||
|
mutate: func(c *Config) { c.Ops.MetricsCollectorCache.TTL = -1 },
|
||||||
|
wantErr: "ops.metrics_collector_cache.ttl",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ops cleanup retention",
|
||||||
|
mutate: func(c *Config) { c.Ops.Cleanup.ErrorLogRetentionDays = -1 },
|
||||||
|
wantErr: "ops.cleanup.error_log_retention_days",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ops cleanup minute retention",
|
||||||
|
mutate: func(c *Config) { c.Ops.Cleanup.MinuteMetricsRetentionDays = -1 },
|
||||||
|
wantErr: "ops.cleanup.minute_metrics_retention_days",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := buildValid(t)
|
||||||
|
tt.mutate(cfg)
|
||||||
|
err := cfg.Validate()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
|
t.Fatalf("Validate() error = %v, want %q", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
226
backend/internal/domain/announcement.go
Normal file
226
backend/internal/domain/announcement.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnouncementStatusDraft = "draft"
|
||||||
|
AnnouncementStatusActive = "active"
|
||||||
|
AnnouncementStatusArchived = "archived"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnouncementConditionTypeSubscription = "subscription"
|
||||||
|
AnnouncementConditionTypeBalance = "balance"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnouncementOperatorIn = "in"
|
||||||
|
AnnouncementOperatorGT = "gt"
|
||||||
|
AnnouncementOperatorGTE = "gte"
|
||||||
|
AnnouncementOperatorLT = "lt"
|
||||||
|
AnnouncementOperatorLTE = "lte"
|
||||||
|
AnnouncementOperatorEQ = "eq"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAnnouncementNotFound = infraerrors.NotFound("ANNOUNCEMENT_NOT_FOUND", "announcement not found")
|
||||||
|
ErrAnnouncementInvalidTarget = infraerrors.BadRequest("ANNOUNCEMENT_INVALID_TARGET", "invalid announcement targeting rules")
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnnouncementTargeting struct {
|
||||||
|
// AnyOf 表示 OR:任意一个条件组满足即可展示。
|
||||||
|
AnyOf []AnnouncementConditionGroup `json:"any_of,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnouncementConditionGroup struct {
|
||||||
|
// AllOf 表示 AND:组内所有条件都满足才算命中该组。
|
||||||
|
AllOf []AnnouncementCondition `json:"all_of,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnouncementCondition struct {
|
||||||
|
// Type: subscription | balance
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Operator:
|
||||||
|
// - subscription: in
|
||||||
|
// - balance: gt/gte/lt/lte/eq
|
||||||
|
Operator string `json:"operator"`
|
||||||
|
|
||||||
|
// subscription 条件:匹配的订阅套餐(group_id)
|
||||||
|
GroupIDs []int64 `json:"group_ids,omitempty"`
|
||||||
|
|
||||||
|
// balance 条件:比较阈值
|
||||||
|
Value float64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AnnouncementTargeting) Matches(balance float64, activeSubscriptionGroupIDs map[int64]struct{}) bool {
|
||||||
|
// 空规则:展示给所有用户
|
||||||
|
if len(t.AnyOf) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range t.AnyOf {
|
||||||
|
if len(group.AllOf) == 0 {
|
||||||
|
// 空条件组不命中(避免 OR 中出现无条件 “全命中”)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allMatched := true
|
||||||
|
for _, cond := range group.AllOf {
|
||||||
|
if !cond.Matches(balance, activeSubscriptionGroupIDs) {
|
||||||
|
allMatched = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allMatched {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c AnnouncementCondition) Matches(balance float64, activeSubscriptionGroupIDs map[int64]struct{}) bool {
|
||||||
|
switch c.Type {
|
||||||
|
case AnnouncementConditionTypeSubscription:
|
||||||
|
if c.Operator != AnnouncementOperatorIn {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(c.GroupIDs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(activeSubscriptionGroupIDs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, gid := range c.GroupIDs {
|
||||||
|
if _, ok := activeSubscriptionGroupIDs[gid]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
case AnnouncementConditionTypeBalance:
|
||||||
|
switch c.Operator {
|
||||||
|
case AnnouncementOperatorGT:
|
||||||
|
return balance > c.Value
|
||||||
|
case AnnouncementOperatorGTE:
|
||||||
|
return balance >= c.Value
|
||||||
|
case AnnouncementOperatorLT:
|
||||||
|
return balance < c.Value
|
||||||
|
case AnnouncementOperatorLTE:
|
||||||
|
return balance <= c.Value
|
||||||
|
case AnnouncementOperatorEQ:
|
||||||
|
return balance == c.Value
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AnnouncementTargeting) NormalizeAndValidate() (AnnouncementTargeting, error) {
|
||||||
|
normalized := AnnouncementTargeting{AnyOf: make([]AnnouncementConditionGroup, 0, len(t.AnyOf))}
|
||||||
|
|
||||||
|
// 允许空 targeting(展示给所有用户)
|
||||||
|
if len(t.AnyOf) == 0 {
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.AnyOf) > 50 {
|
||||||
|
return AnnouncementTargeting{}, ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range t.AnyOf {
|
||||||
|
if len(g.AllOf) == 0 {
|
||||||
|
return AnnouncementTargeting{}, ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
if len(g.AllOf) > 50 {
|
||||||
|
return AnnouncementTargeting{}, ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
group := AnnouncementConditionGroup{AllOf: make([]AnnouncementCondition, 0, len(g.AllOf))}
|
||||||
|
for _, c := range g.AllOf {
|
||||||
|
cond := AnnouncementCondition{
|
||||||
|
Type: strings.TrimSpace(c.Type),
|
||||||
|
Operator: strings.TrimSpace(c.Operator),
|
||||||
|
Value: c.Value,
|
||||||
|
}
|
||||||
|
for _, gid := range c.GroupIDs {
|
||||||
|
if gid <= 0 {
|
||||||
|
return AnnouncementTargeting{}, ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
cond.GroupIDs = append(cond.GroupIDs, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cond.validate(); err != nil {
|
||||||
|
return AnnouncementTargeting{}, err
|
||||||
|
}
|
||||||
|
group.AllOf = append(group.AllOf, cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.AnyOf = append(normalized.AnyOf, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c AnnouncementCondition) validate() error {
|
||||||
|
switch c.Type {
|
||||||
|
case AnnouncementConditionTypeSubscription:
|
||||||
|
if c.Operator != AnnouncementOperatorIn {
|
||||||
|
return ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
if len(c.GroupIDs) == 0 {
|
||||||
|
return ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case AnnouncementConditionTypeBalance:
|
||||||
|
switch c.Operator {
|
||||||
|
case AnnouncementOperatorGT, AnnouncementOperatorGTE, AnnouncementOperatorLT, AnnouncementOperatorLTE, AnnouncementOperatorEQ:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrAnnouncementInvalidTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Announcement struct {
|
||||||
|
ID int64
|
||||||
|
Title string
|
||||||
|
Content string
|
||||||
|
Status string
|
||||||
|
Targeting AnnouncementTargeting
|
||||||
|
StartsAt *time.Time
|
||||||
|
EndsAt *time.Time
|
||||||
|
CreatedBy *int64
|
||||||
|
UpdatedBy *int64
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Announcement) IsActiveAt(now time.Time) bool {
|
||||||
|
if a == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.Status != AnnouncementStatusActive {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.StartsAt != nil && now.Before(*a.StartsAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.EndsAt != nil && !now.Before(*a.EndsAt) {
|
||||||
|
// ends_at 语义:到点即下线
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
64
backend/internal/domain/constants.go
Normal file
64
backend/internal/domain/constants.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
// Status constants
|
||||||
|
const (
|
||||||
|
StatusActive = "active"
|
||||||
|
StatusDisabled = "disabled"
|
||||||
|
StatusError = "error"
|
||||||
|
StatusUnused = "unused"
|
||||||
|
StatusUsed = "used"
|
||||||
|
StatusExpired = "expired"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Role constants
|
||||||
|
const (
|
||||||
|
RoleAdmin = "admin"
|
||||||
|
RoleUser = "user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Platform constants
|
||||||
|
const (
|
||||||
|
PlatformAnthropic = "anthropic"
|
||||||
|
PlatformOpenAI = "openai"
|
||||||
|
PlatformGemini = "gemini"
|
||||||
|
PlatformAntigravity = "antigravity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account type constants
|
||||||
|
const (
|
||||||
|
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
|
||||||
|
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
||||||
|
AccountTypeAPIKey = "apikey" // API Key类型账号
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redeem type constants
|
||||||
|
const (
|
||||||
|
RedeemTypeBalance = "balance"
|
||||||
|
RedeemTypeConcurrency = "concurrency"
|
||||||
|
RedeemTypeSubscription = "subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PromoCode status constants
|
||||||
|
const (
|
||||||
|
PromoCodeStatusActive = "active"
|
||||||
|
PromoCodeStatusDisabled = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Admin adjustment type constants
|
||||||
|
const (
|
||||||
|
AdjustmentTypeAdminBalance = "admin_balance" // 管理员调整余额
|
||||||
|
AdjustmentTypeAdminConcurrency = "admin_concurrency" // 管理员调整并发数
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group subscription type constants
|
||||||
|
const (
|
||||||
|
SubscriptionTypeStandard = "standard" // 标准计费模式(按余额扣费)
|
||||||
|
SubscriptionTypeSubscription = "subscription" // 订阅模式(按限额控制)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subscription status constants
|
||||||
|
const (
|
||||||
|
SubscriptionStatusActive = "active"
|
||||||
|
SubscriptionStatusExpired = "expired"
|
||||||
|
SubscriptionStatusSuspended = "suspended"
|
||||||
|
)
|
||||||
@@ -44,6 +44,8 @@ type AccountHandler struct {
|
|||||||
accountTestService *service.AccountTestService
|
accountTestService *service.AccountTestService
|
||||||
concurrencyService *service.ConcurrencyService
|
concurrencyService *service.ConcurrencyService
|
||||||
crsSyncService *service.CRSSyncService
|
crsSyncService *service.CRSSyncService
|
||||||
|
sessionLimitCache service.SessionLimitCache
|
||||||
|
tokenCacheInvalidator service.TokenCacheInvalidator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountHandler creates a new admin account handler
|
// NewAccountHandler creates a new admin account handler
|
||||||
@@ -58,6 +60,8 @@ func NewAccountHandler(
|
|||||||
accountTestService *service.AccountTestService,
|
accountTestService *service.AccountTestService,
|
||||||
concurrencyService *service.ConcurrencyService,
|
concurrencyService *service.ConcurrencyService,
|
||||||
crsSyncService *service.CRSSyncService,
|
crsSyncService *service.CRSSyncService,
|
||||||
|
sessionLimitCache service.SessionLimitCache,
|
||||||
|
tokenCacheInvalidator service.TokenCacheInvalidator,
|
||||||
) *AccountHandler {
|
) *AccountHandler {
|
||||||
return &AccountHandler{
|
return &AccountHandler{
|
||||||
adminService: adminService,
|
adminService: adminService,
|
||||||
@@ -70,6 +74,8 @@ func NewAccountHandler(
|
|||||||
accountTestService: accountTestService,
|
accountTestService: accountTestService,
|
||||||
concurrencyService: concurrencyService,
|
concurrencyService: concurrencyService,
|
||||||
crsSyncService: crsSyncService,
|
crsSyncService: crsSyncService,
|
||||||
|
sessionLimitCache: sessionLimitCache,
|
||||||
|
tokenCacheInvalidator: tokenCacheInvalidator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +90,7 @@ type CreateAccountRequest struct {
|
|||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
Concurrency int `json:"concurrency"`
|
Concurrency int `json:"concurrency"`
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
GroupIDs []int64 `json:"group_ids"`
|
GroupIDs []int64 `json:"group_ids"`
|
||||||
ExpiresAt *int64 `json:"expires_at"`
|
ExpiresAt *int64 `json:"expires_at"`
|
||||||
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
|
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
|
||||||
@@ -101,6 +108,7 @@ type UpdateAccountRequest struct {
|
|||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
Concurrency *int `json:"concurrency"`
|
Concurrency *int `json:"concurrency"`
|
||||||
Priority *int `json:"priority"`
|
Priority *int `json:"priority"`
|
||||||
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||||
GroupIDs *[]int64 `json:"group_ids"`
|
GroupIDs *[]int64 `json:"group_ids"`
|
||||||
ExpiresAt *int64 `json:"expires_at"`
|
ExpiresAt *int64 `json:"expires_at"`
|
||||||
@@ -115,6 +123,7 @@ type BulkUpdateAccountsRequest struct {
|
|||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
Concurrency *int `json:"concurrency"`
|
Concurrency *int `json:"concurrency"`
|
||||||
Priority *int `json:"priority"`
|
Priority *int `json:"priority"`
|
||||||
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
Status string `json:"status" binding:"omitempty,oneof=active inactive error"`
|
Status string `json:"status" binding:"omitempty,oneof=active inactive error"`
|
||||||
Schedulable *bool `json:"schedulable"`
|
Schedulable *bool `json:"schedulable"`
|
||||||
GroupIDs *[]int64 `json:"group_ids"`
|
GroupIDs *[]int64 `json:"group_ids"`
|
||||||
@@ -127,6 +136,9 @@ type BulkUpdateAccountsRequest struct {
|
|||||||
type AccountWithConcurrency struct {
|
type AccountWithConcurrency struct {
|
||||||
*dto.Account
|
*dto.Account
|
||||||
CurrentConcurrency int `json:"current_concurrency"`
|
CurrentConcurrency int `json:"current_concurrency"`
|
||||||
|
// 以下字段仅对 Anthropic OAuth/SetupToken 账号有效,且仅在启用相应功能时返回
|
||||||
|
CurrentWindowCost *float64 `json:"current_window_cost,omitempty"` // 当前窗口费用
|
||||||
|
ActiveSessions *int `json:"active_sessions,omitempty"` // 当前活跃会话数
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handles listing all accounts with pagination
|
// List handles listing all accounts with pagination
|
||||||
@@ -161,13 +173,87 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
concurrencyCounts = make(map[int64]int)
|
concurrencyCounts = make(map[int64]int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 识别需要查询窗口费用和会话数的账号(Anthropic OAuth/SetupToken 且启用了相应功能)
|
||||||
|
windowCostAccountIDs := make([]int64, 0)
|
||||||
|
sessionLimitAccountIDs := make([]int64, 0)
|
||||||
|
sessionIdleTimeouts := make(map[int64]time.Duration) // 各账号的会话空闲超时配置
|
||||||
|
for i := range accounts {
|
||||||
|
acc := &accounts[i]
|
||||||
|
if acc.IsAnthropicOAuthOrSetupToken() {
|
||||||
|
if acc.GetWindowCostLimit() > 0 {
|
||||||
|
windowCostAccountIDs = append(windowCostAccountIDs, acc.ID)
|
||||||
|
}
|
||||||
|
if acc.GetMaxSessions() > 0 {
|
||||||
|
sessionLimitAccountIDs = append(sessionLimitAccountIDs, acc.ID)
|
||||||
|
sessionIdleTimeouts[acc.ID] = time.Duration(acc.GetSessionIdleTimeoutMinutes()) * time.Minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 并行获取窗口费用和活跃会话数
|
||||||
|
var windowCosts map[int64]float64
|
||||||
|
var activeSessions map[int64]int
|
||||||
|
|
||||||
|
// 获取活跃会话数(批量查询,传入各账号的 idleTimeout 配置)
|
||||||
|
if len(sessionLimitAccountIDs) > 0 && h.sessionLimitCache != nil {
|
||||||
|
activeSessions, _ = h.sessionLimitCache.GetActiveSessionCountBatch(c.Request.Context(), sessionLimitAccountIDs, sessionIdleTimeouts)
|
||||||
|
if activeSessions == nil {
|
||||||
|
activeSessions = make(map[int64]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取窗口费用(并行查询)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
return nil // 不返回错误,允许部分失败
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ = g.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// Build response with concurrency info
|
// Build response with concurrency info
|
||||||
result := make([]AccountWithConcurrency, len(accounts))
|
result := make([]AccountWithConcurrency, len(accounts))
|
||||||
for i := range accounts {
|
for i := range accounts {
|
||||||
result[i] = AccountWithConcurrency{
|
acc := &accounts[i]
|
||||||
Account: dto.AccountFromService(&accounts[i]),
|
item := AccountWithConcurrency{
|
||||||
CurrentConcurrency: concurrencyCounts[accounts[i].ID],
|
Account: dto.AccountFromService(acc),
|
||||||
|
CurrentConcurrency: concurrencyCounts[acc.ID],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加窗口费用(仅当启用时)
|
||||||
|
if windowCosts != nil {
|
||||||
|
if cost, ok := windowCosts[acc.ID]; ok {
|
||||||
|
item.CurrentWindowCost = &cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加活跃会话数(仅当启用时)
|
||||||
|
if activeSessions != nil {
|
||||||
|
if count, ok := activeSessions[acc.ID]; ok {
|
||||||
|
item.ActiveSessions = &count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Paginated(c, result, total, page, pageSize)
|
response.Paginated(c, result, total, page, pageSize)
|
||||||
@@ -199,6 +285,10 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
|||||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.RateMultiplier != nil && *req.RateMultiplier < 0 {
|
||||||
|
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 确定是否跳过混合渠道检查
|
// 确定是否跳过混合渠道检查
|
||||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||||
@@ -213,6 +303,7 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
|||||||
ProxyID: req.ProxyID,
|
ProxyID: req.ProxyID,
|
||||||
Concurrency: req.Concurrency,
|
Concurrency: req.Concurrency,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
|
RateMultiplier: req.RateMultiplier,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
ExpiresAt: req.ExpiresAt,
|
ExpiresAt: req.ExpiresAt,
|
||||||
AutoPauseOnExpired: req.AutoPauseOnExpired,
|
AutoPauseOnExpired: req.AutoPauseOnExpired,
|
||||||
@@ -258,6 +349,10 @@ func (h *AccountHandler) Update(c *gin.Context) {
|
|||||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.RateMultiplier != nil && *req.RateMultiplier < 0 {
|
||||||
|
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 确定是否跳过混合渠道检查
|
// 确定是否跳过混合渠道检查
|
||||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||||
@@ -271,6 +366,7 @@ func (h *AccountHandler) Update(c *gin.Context) {
|
|||||||
ProxyID: req.ProxyID,
|
ProxyID: req.ProxyID,
|
||||||
Concurrency: req.Concurrency, // 指针类型,nil 表示未提供
|
Concurrency: req.Concurrency, // 指针类型,nil 表示未提供
|
||||||
Priority: req.Priority, // 指针类型,nil 表示未提供
|
Priority: req.Priority, // 指针类型,nil 表示未提供
|
||||||
|
RateMultiplier: req.RateMultiplier,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
ExpiresAt: req.ExpiresAt,
|
ExpiresAt: req.ExpiresAt,
|
||||||
@@ -450,6 +546,41 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
|
|||||||
newCredentials[k] = v
|
newCredentials[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 特殊处理 project_id:如果新值为空但旧值非空,保留旧值
|
||||||
|
// 这确保了即使 LoadCodeAssist 失败,project_id 也不会丢失
|
||||||
|
if newProjectID, _ := newCredentials["project_id"].(string); newProjectID == "" {
|
||||||
|
if oldProjectID := strings.TrimSpace(account.GetCredential("project_id")); oldProjectID != "" {
|
||||||
|
newCredentials["project_id"] = oldProjectID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 project_id 获取失败,更新凭证但不标记为 error
|
||||||
|
// LoadCodeAssist 失败可能是临时网络问题,给它机会在下次自动刷新时重试
|
||||||
|
if tokenInfo.ProjectIDMissing {
|
||||||
|
// 先更新凭证(token 本身刷新成功了)
|
||||||
|
_, updateErr := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{
|
||||||
|
Credentials: newCredentials,
|
||||||
|
})
|
||||||
|
if updateErr != nil {
|
||||||
|
response.InternalError(c, "Failed to update credentials: "+updateErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 不标记为 error,只返回警告信息
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"message": "Token refreshed successfully, but project_id could not be retrieved (will retry automatically)",
|
||||||
|
"warning": "missing_project_id_temporary",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功获取到 project_id,如果之前是 missing_project_id 错误则清除
|
||||||
|
if account.Status == service.StatusError && strings.Contains(account.ErrorMessage, "missing_project_id:") {
|
||||||
|
if _, clearErr := h.adminService.ClearAccountError(c.Request.Context(), accountID); clearErr != nil {
|
||||||
|
response.InternalError(c, "Failed to clear account error: "+clearErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use Anthropic/Claude OAuth service to refresh token
|
// Use Anthropic/Claude OAuth service to refresh token
|
||||||
tokenInfo, err := h.oauthService.RefreshAccountToken(c.Request.Context(), account)
|
tokenInfo, err := h.oauthService.RefreshAccountToken(c.Request.Context(), account)
|
||||||
@@ -485,6 +616,14 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新成功后,清除 token 缓存,确保下次请求使用新 token
|
||||||
|
if h.tokenCacheInvalidator != nil {
|
||||||
|
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(c.Request.Context(), updatedAccount); invalidateErr != nil {
|
||||||
|
// 缓存失效失败只记录日志,不影响主流程
|
||||||
|
_ = c.Error(invalidateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.Success(c, dto.AccountFromService(updatedAccount))
|
response.Success(c, dto.AccountFromService(updatedAccount))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,6 +673,15 @@ func (h *AccountHandler) ClearError(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除错误后,同时清除 token 缓存,确保下次请求会获取最新的 token(触发刷新或从 DB 读取)
|
||||||
|
// 这解决了管理员重置账号状态后,旧的失效 token 仍在缓存中导致立即再次 401 的问题
|
||||||
|
if h.tokenCacheInvalidator != nil && account.IsOAuth() {
|
||||||
|
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(c.Request.Context(), account); invalidateErr != nil {
|
||||||
|
// 缓存失效失败只记录日志,不影响主流程
|
||||||
|
_ = c.Error(invalidateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.Success(c, dto.AccountFromService(account))
|
response.Success(c, dto.AccountFromService(account))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,6 +800,10 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
|||||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.RateMultiplier != nil && *req.RateMultiplier < 0 {
|
||||||
|
response.BadRequest(c, "rate_multiplier must be >= 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 确定是否跳过混合渠道检查
|
// 确定是否跳过混合渠道检查
|
||||||
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
skipCheck := req.ConfirmMixedChannelRisk != nil && *req.ConfirmMixedChannelRisk
|
||||||
@@ -660,6 +812,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
|||||||
req.ProxyID != nil ||
|
req.ProxyID != nil ||
|
||||||
req.Concurrency != nil ||
|
req.Concurrency != nil ||
|
||||||
req.Priority != nil ||
|
req.Priority != nil ||
|
||||||
|
req.RateMultiplier != nil ||
|
||||||
req.Status != "" ||
|
req.Status != "" ||
|
||||||
req.Schedulable != nil ||
|
req.Schedulable != nil ||
|
||||||
req.GroupIDs != nil ||
|
req.GroupIDs != nil ||
|
||||||
@@ -677,6 +830,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
|||||||
ProxyID: req.ProxyID,
|
ProxyID: req.ProxyID,
|
||||||
Concurrency: req.Concurrency,
|
Concurrency: req.Concurrency,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
|
RateMultiplier: req.RateMultiplier,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
Schedulable: req.Schedulable,
|
Schedulable: req.Schedulable,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
|
|||||||
262
backend/internal/handler/admin/admin_basic_handlers_test.go
Normal file
262
backend/internal/handler/admin/admin_basic_handlers_test.go
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupAdminRouter() (*gin.Engine, *stubAdminService) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
adminSvc := newStubAdminService()
|
||||||
|
|
||||||
|
userHandler := NewUserHandler(adminSvc)
|
||||||
|
groupHandler := NewGroupHandler(adminSvc)
|
||||||
|
proxyHandler := NewProxyHandler(adminSvc)
|
||||||
|
redeemHandler := NewRedeemHandler(adminSvc)
|
||||||
|
|
||||||
|
router.GET("/api/v1/admin/users", userHandler.List)
|
||||||
|
router.GET("/api/v1/admin/users/:id", userHandler.GetByID)
|
||||||
|
router.POST("/api/v1/admin/users", userHandler.Create)
|
||||||
|
router.PUT("/api/v1/admin/users/:id", userHandler.Update)
|
||||||
|
router.DELETE("/api/v1/admin/users/:id", userHandler.Delete)
|
||||||
|
router.POST("/api/v1/admin/users/:id/balance", userHandler.UpdateBalance)
|
||||||
|
router.GET("/api/v1/admin/users/:id/api-keys", userHandler.GetUserAPIKeys)
|
||||||
|
router.GET("/api/v1/admin/users/:id/usage", userHandler.GetUserUsage)
|
||||||
|
|
||||||
|
router.GET("/api/v1/admin/groups", groupHandler.List)
|
||||||
|
router.GET("/api/v1/admin/groups/all", groupHandler.GetAll)
|
||||||
|
router.GET("/api/v1/admin/groups/:id", groupHandler.GetByID)
|
||||||
|
router.POST("/api/v1/admin/groups", groupHandler.Create)
|
||||||
|
router.PUT("/api/v1/admin/groups/:id", groupHandler.Update)
|
||||||
|
router.DELETE("/api/v1/admin/groups/:id", groupHandler.Delete)
|
||||||
|
router.GET("/api/v1/admin/groups/:id/stats", groupHandler.GetStats)
|
||||||
|
router.GET("/api/v1/admin/groups/:id/api-keys", groupHandler.GetGroupAPIKeys)
|
||||||
|
|
||||||
|
router.GET("/api/v1/admin/proxies", proxyHandler.List)
|
||||||
|
router.GET("/api/v1/admin/proxies/all", proxyHandler.GetAll)
|
||||||
|
router.GET("/api/v1/admin/proxies/:id", proxyHandler.GetByID)
|
||||||
|
router.POST("/api/v1/admin/proxies", proxyHandler.Create)
|
||||||
|
router.PUT("/api/v1/admin/proxies/:id", proxyHandler.Update)
|
||||||
|
router.DELETE("/api/v1/admin/proxies/:id", proxyHandler.Delete)
|
||||||
|
router.POST("/api/v1/admin/proxies/batch-delete", proxyHandler.BatchDelete)
|
||||||
|
router.POST("/api/v1/admin/proxies/:id/test", proxyHandler.Test)
|
||||||
|
router.GET("/api/v1/admin/proxies/:id/stats", proxyHandler.GetStats)
|
||||||
|
router.GET("/api/v1/admin/proxies/:id/accounts", proxyHandler.GetProxyAccounts)
|
||||||
|
|
||||||
|
router.GET("/api/v1/admin/redeem-codes", redeemHandler.List)
|
||||||
|
router.GET("/api/v1/admin/redeem-codes/:id", redeemHandler.GetByID)
|
||||||
|
router.POST("/api/v1/admin/redeem-codes", redeemHandler.Generate)
|
||||||
|
router.DELETE("/api/v1/admin/redeem-codes/:id", redeemHandler.Delete)
|
||||||
|
router.POST("/api/v1/admin/redeem-codes/batch-delete", redeemHandler.BatchDelete)
|
||||||
|
router.POST("/api/v1/admin/redeem-codes/:id/expire", redeemHandler.Expire)
|
||||||
|
router.GET("/api/v1/admin/redeem-codes/:id/stats", redeemHandler.GetStats)
|
||||||
|
|
||||||
|
return router, adminSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserHandlerEndpoints(t *testing.T) {
|
||||||
|
router, _ := setupAdminRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/users?page=1&page_size=20", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/1", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
createBody := map[string]any{"email": "new@example.com", "password": "pass123", "balance": 1, "concurrency": 2}
|
||||||
|
body, _ := json.Marshal(createBody)
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
updateBody := map[string]any{"email": "updated@example.com"}
|
||||||
|
body, _ = json.Marshal(updateBody)
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/api/v1/admin/users/1", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodDelete, "/api/v1/admin/users/1", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/users/1/balance", bytes.NewBufferString(`{"balance":1,"operation":"add"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/1/api-keys", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/1/usage?period=today", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupHandlerEndpoints(t *testing.T) {
|
||||||
|
router, _ := setupAdminRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups/all", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups/2", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]any{"name": "new", "platform": "anthropic", "subscription_type": "standard"})
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/groups", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
body, _ = json.Marshal(map[string]any{"name": "update"})
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/api/v1/admin/groups/2", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodDelete, "/api/v1/admin/groups/2", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups/2/stats", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups/2/api-keys", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyHandlerEndpoints(t *testing.T) {
|
||||||
|
router, _ := setupAdminRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/all", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/4", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]any{"name": "proxy", "protocol": "http", "host": "localhost", "port": 8080})
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/proxies", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
body, _ = json.Marshal(map[string]any{"name": "proxy2"})
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/api/v1/admin/proxies/4", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodDelete, "/api/v1/admin/proxies/4", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/proxies/batch-delete", bytes.NewBufferString(`{"ids":[1,2]}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/proxies/4/test", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/4/stats", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/4/accounts", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedeemHandlerEndpoints(t *testing.T) {
|
||||||
|
router, _ := setupAdminRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/5", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]any{"count": 1, "type": "balance", "value": 10})
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/redeem-codes", bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodDelete, "/api/v1/admin/redeem-codes/5", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/redeem-codes/batch-delete", bytes.NewBufferString(`{"ids":[1,2]}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/redeem-codes/5/expire", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/5/stats", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
134
backend/internal/handler/admin/admin_helpers_test.go
Normal file
134
backend/internal/handler/admin/admin_helpers_test.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseTimeRange(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/?start_date=2024-01-01&end_date=2024-01-02&timezone=UTC", nil)
|
||||||
|
c.Request = req
|
||||||
|
|
||||||
|
start, end := parseTimeRange(c)
|
||||||
|
require.Equal(t, time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), start)
|
||||||
|
require.Equal(t, time.Date(2024, 1, 3, 0, 0, 0, 0, time.UTC), end)
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/?start_date=bad&timezone=UTC", nil)
|
||||||
|
c.Request = req
|
||||||
|
start, end = parseTimeRange(c)
|
||||||
|
require.False(t, start.IsZero())
|
||||||
|
require.False(t, end.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOpsViewParam(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodGet, "/?view=excluded", nil)
|
||||||
|
require.Equal(t, opsListViewExcluded, parseOpsViewParam(c))
|
||||||
|
|
||||||
|
c2, _ := gin.CreateTestContext(w)
|
||||||
|
c2.Request = httptest.NewRequest(http.MethodGet, "/?view=all", nil)
|
||||||
|
require.Equal(t, opsListViewAll, parseOpsViewParam(c2))
|
||||||
|
|
||||||
|
c3, _ := gin.CreateTestContext(w)
|
||||||
|
c3.Request = httptest.NewRequest(http.MethodGet, "/?view=unknown", nil)
|
||||||
|
require.Equal(t, opsListViewErrors, parseOpsViewParam(c3))
|
||||||
|
|
||||||
|
require.Equal(t, "", parseOpsViewParam(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOpsDuration(t *testing.T) {
|
||||||
|
dur, ok := parseOpsDuration("1h")
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, time.Hour, dur)
|
||||||
|
|
||||||
|
_, ok = parseOpsDuration("invalid")
|
||||||
|
require.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOpsTimeRange(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
now := time.Now().UTC()
|
||||||
|
startStr := now.Add(-time.Hour).Format(time.RFC3339)
|
||||||
|
endStr := now.Format(time.RFC3339)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodGet, "/?start_time="+startStr+"&end_time="+endStr, nil)
|
||||||
|
start, end, err := parseOpsTimeRange(c, "1h")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, start.Before(end))
|
||||||
|
|
||||||
|
c2, _ := gin.CreateTestContext(w)
|
||||||
|
c2.Request = httptest.NewRequest(http.MethodGet, "/?start_time=bad", nil)
|
||||||
|
_, _, err = parseOpsTimeRange(c2, "1h")
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOpsRealtimeWindow(t *testing.T) {
|
||||||
|
dur, label, ok := parseOpsRealtimeWindow("5m")
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, 5*time.Minute, dur)
|
||||||
|
require.Equal(t, "5min", label)
|
||||||
|
|
||||||
|
_, _, ok = parseOpsRealtimeWindow("invalid")
|
||||||
|
require.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPickThroughputBucketSeconds(t *testing.T) {
|
||||||
|
require.Equal(t, 60, pickThroughputBucketSeconds(30*time.Minute))
|
||||||
|
require.Equal(t, 300, pickThroughputBucketSeconds(6*time.Hour))
|
||||||
|
require.Equal(t, 3600, pickThroughputBucketSeconds(48*time.Hour))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOpsQueryMode(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodGet, "/?mode=raw", nil)
|
||||||
|
require.Equal(t, service.ParseOpsQueryMode("raw"), parseOpsQueryMode(c))
|
||||||
|
require.Equal(t, service.OpsQueryMode(""), parseOpsQueryMode(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpsAlertRuleValidation(t *testing.T) {
|
||||||
|
raw := map[string]json.RawMessage{
|
||||||
|
"name": json.RawMessage(`"High error rate"`),
|
||||||
|
"metric_type": json.RawMessage(`"error_rate"`),
|
||||||
|
"operator": json.RawMessage(`">"`),
|
||||||
|
"threshold": json.RawMessage(`90`),
|
||||||
|
}
|
||||||
|
|
||||||
|
validated, err := validateOpsAlertRulePayload(raw)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "High error rate", validated.Name)
|
||||||
|
|
||||||
|
_, err = validateOpsAlertRulePayload(map[string]json.RawMessage{})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
require.True(t, isPercentOrRateMetric("error_rate"))
|
||||||
|
require.False(t, isPercentOrRateMetric("concurrency_queue_depth"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpsWSHelpers(t *testing.T) {
|
||||||
|
prefixes, invalid := parseTrustedProxyList("10.0.0.0/8,invalid")
|
||||||
|
require.Len(t, prefixes, 1)
|
||||||
|
require.Len(t, invalid, 1)
|
||||||
|
|
||||||
|
host := hostWithoutPort("example.com:443")
|
||||||
|
require.Equal(t, "example.com", host)
|
||||||
|
|
||||||
|
addr := netip.MustParseAddr("10.0.0.1")
|
||||||
|
require.True(t, isAddrInTrustedProxies(addr, prefixes))
|
||||||
|
require.False(t, isAddrInTrustedProxies(netip.MustParseAddr("192.168.0.1"), prefixes))
|
||||||
|
}
|
||||||
294
backend/internal/handler/admin/admin_service_stub_test.go
Normal file
294
backend/internal/handler/admin/admin_service_stub_test.go
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubAdminService struct {
|
||||||
|
users []service.User
|
||||||
|
apiKeys []service.APIKey
|
||||||
|
groups []service.Group
|
||||||
|
accounts []service.Account
|
||||||
|
proxies []service.Proxy
|
||||||
|
proxyCounts []service.ProxyWithAccountCount
|
||||||
|
redeems []service.RedeemCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStubAdminService() *stubAdminService {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
user := service.User{
|
||||||
|
ID: 1,
|
||||||
|
Email: "user@example.com",
|
||||||
|
Role: service.RoleUser,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
apiKey := service.APIKey{
|
||||||
|
ID: 10,
|
||||||
|
UserID: user.ID,
|
||||||
|
Key: "sk-test",
|
||||||
|
Name: "test",
|
||||||
|
Status: service.StatusActive,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
group := service.Group{
|
||||||
|
ID: 2,
|
||||||
|
Name: "group",
|
||||||
|
Platform: service.PlatformAnthropic,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
account := service.Account{
|
||||||
|
ID: 3,
|
||||||
|
Name: "account",
|
||||||
|
Platform: service.PlatformAnthropic,
|
||||||
|
Type: service.AccountTypeOAuth,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
proxy := service.Proxy{
|
||||||
|
ID: 4,
|
||||||
|
Name: "proxy",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
redeem := service.RedeemCode{
|
||||||
|
ID: 5,
|
||||||
|
Code: "R-TEST",
|
||||||
|
Type: service.RedeemTypeBalance,
|
||||||
|
Value: 10,
|
||||||
|
Status: service.StatusUnused,
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
return &stubAdminService{
|
||||||
|
users: []service.User{user},
|
||||||
|
apiKeys: []service.APIKey{apiKey},
|
||||||
|
groups: []service.Group{group},
|
||||||
|
accounts: []service.Account{account},
|
||||||
|
proxies: []service.Proxy{proxy},
|
||||||
|
proxyCounts: []service.ProxyWithAccountCount{{Proxy: proxy, AccountCount: 1}},
|
||||||
|
redeems: []service.RedeemCode{redeem},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters) ([]service.User, int64, error) {
|
||||||
|
return s.users, int64(len(s.users)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetUser(ctx context.Context, id int64) (*service.User, error) {
|
||||||
|
for i := range s.users {
|
||||||
|
if s.users[i].ID == id {
|
||||||
|
return &s.users[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user := service.User{ID: id, Email: "user@example.com", Status: service.StatusActive}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) CreateUser(ctx context.Context, input *service.CreateUserInput) (*service.User, error) {
|
||||||
|
user := service.User{ID: 100, Email: input.Email, Status: service.StatusActive}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) UpdateUser(ctx context.Context, id int64, input *service.UpdateUserInput) (*service.User, error) {
|
||||||
|
user := service.User{ID: id, Email: "updated@example.com", Status: service.StatusActive}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) DeleteUser(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*service.User, error) {
|
||||||
|
user := service.User{ID: userID, Balance: balance, Status: service.StatusActive}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]service.APIKey, int64, error) {
|
||||||
|
return s.apiKeys, int64(len(s.apiKeys)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error) {
|
||||||
|
return map[string]any{"user_id": userID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]service.Group, int64, error) {
|
||||||
|
return s.groups, int64(len(s.groups)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAllGroups(ctx context.Context) ([]service.Group, error) {
|
||||||
|
return s.groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAllGroupsByPlatform(ctx context.Context, platform string) ([]service.Group, error) {
|
||||||
|
return s.groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetGroup(ctx context.Context, id int64) (*service.Group, error) {
|
||||||
|
group := service.Group{ID: id, Name: "group", Status: service.StatusActive}
|
||||||
|
return &group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) CreateGroup(ctx context.Context, input *service.CreateGroupInput) (*service.Group, error) {
|
||||||
|
group := service.Group{ID: 200, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) UpdateGroup(ctx context.Context, id int64, input *service.UpdateGroupInput) (*service.Group, error) {
|
||||||
|
group := service.Group{ID: id, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) DeleteGroup(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetGroupAPIKeys(ctx context.Context, groupID int64, page, pageSize int) ([]service.APIKey, int64, error) {
|
||||||
|
return s.apiKeys, int64(len(s.apiKeys)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string) ([]service.Account, int64, error) {
|
||||||
|
return s.accounts, int64(len(s.accounts)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAccountsByIDs(ctx context.Context, ids []int64) ([]*service.Account, error) {
|
||||||
|
out := make([]*service.Account, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
|
||||||
|
out = append(out, &account)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) CreateAccount(ctx context.Context, input *service.CreateAccountInput) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: 300, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) UpdateAccount(ctx context.Context, id int64, input *service.UpdateAccountInput) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: id, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) DeleteAccount(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) RefreshAccountCredentials(ctx context.Context, id int64) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ClearAccountError(ctx context.Context, id int64) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) SetAccountError(ctx context.Context, id int64, errorMsg string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*service.Account, error) {
|
||||||
|
account := service.Account{ID: id, Name: "account", Status: service.StatusActive, Schedulable: schedulable}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) BulkUpdateAccounts(ctx context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
|
||||||
|
return &service.BulkUpdateAccountsResult{Success: 1, Failed: 0, SuccessIDs: []int64{1}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.Proxy, int64, error) {
|
||||||
|
return s.proxies, int64(len(s.proxies)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.ProxyWithAccountCount, int64, error) {
|
||||||
|
return s.proxyCounts, int64(len(s.proxyCounts)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAllProxies(ctx context.Context) ([]service.Proxy, error) {
|
||||||
|
return s.proxies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAllProxiesWithAccountCount(ctx context.Context) ([]service.ProxyWithAccountCount, error) {
|
||||||
|
return s.proxyCounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetProxy(ctx context.Context, id int64) (*service.Proxy, error) {
|
||||||
|
proxy := service.Proxy{ID: id, Name: "proxy", Status: service.StatusActive}
|
||||||
|
return &proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) CreateProxy(ctx context.Context, input *service.CreateProxyInput) (*service.Proxy, error) {
|
||||||
|
proxy := service.Proxy{ID: 400, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) UpdateProxy(ctx context.Context, id int64, input *service.UpdateProxyInput) (*service.Proxy, error) {
|
||||||
|
proxy := service.Proxy{ID: id, Name: input.Name, Status: service.StatusActive}
|
||||||
|
return &proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) DeleteProxy(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) BatchDeleteProxies(ctx context.Context, ids []int64) (*service.ProxyBatchDeleteResult, error) {
|
||||||
|
return &service.ProxyBatchDeleteResult{DeletedIDs: ids}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetProxyAccounts(ctx context.Context, proxyID int64) ([]service.ProxyAccountSummary, error) {
|
||||||
|
return []service.ProxyAccountSummary{{ID: 1, Name: "account"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) CheckProxyExists(ctx context.Context, host string, port int, username, password string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) TestProxy(ctx context.Context, id int64) (*service.ProxyTestResult, error) {
|
||||||
|
return &service.ProxyTestResult{Success: true, Message: "ok"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]service.RedeemCode, int64, error) {
|
||||||
|
return s.redeems, int64(len(s.redeems)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
|
||||||
|
code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUnused}
|
||||||
|
return &code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GenerateRedeemCodes(ctx context.Context, input *service.GenerateRedeemCodesInput) ([]service.RedeemCode, error) {
|
||||||
|
return s.redeems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) DeleteRedeemCode(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) BatchDeleteRedeemCodes(ctx context.Context, ids []int64) (int64, error) {
|
||||||
|
return int64(len(ids)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ExpireRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
|
||||||
|
code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUsed}
|
||||||
|
return &code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure stub implements interface.
|
||||||
|
var _ service.AdminService = (*stubAdminService)(nil)
|
||||||
246
backend/internal/handler/admin/announcement_handler.go
Normal file
246
backend/internal/handler/admin/announcement_handler.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnouncementHandler handles admin announcement management
|
||||||
|
type AnnouncementHandler struct {
|
||||||
|
announcementService *service.AnnouncementService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnnouncementHandler creates a new admin announcement handler
|
||||||
|
func NewAnnouncementHandler(announcementService *service.AnnouncementService) *AnnouncementHandler {
|
||||||
|
return &AnnouncementHandler{
|
||||||
|
announcementService: announcementService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAnnouncementRequest struct {
|
||||||
|
Title string `json:"title" binding:"required"`
|
||||||
|
Content string `json:"content" binding:"required"`
|
||||||
|
Status string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
||||||
|
Targeting service.AnnouncementTargeting `json:"targeting"`
|
||||||
|
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0/empty = immediate
|
||||||
|
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0/empty = never
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAnnouncementRequest struct {
|
||||||
|
Title *string `json:"title"`
|
||||||
|
Content *string `json:"content"`
|
||||||
|
Status *string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
||||||
|
Targeting *service.AnnouncementTargeting `json:"targeting"`
|
||||||
|
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0 = clear
|
||||||
|
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0 = clear
|
||||||
|
}
|
||||||
|
|
||||||
|
// List handles listing announcements with filters
|
||||||
|
// GET /api/v1/admin/announcements
|
||||||
|
func (h *AnnouncementHandler) List(c *gin.Context) {
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
status := strings.TrimSpace(c.Query("status"))
|
||||||
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
if len(search) > 200 {
|
||||||
|
search = search[:200]
|
||||||
|
}
|
||||||
|
|
||||||
|
params := pagination.PaginationParams{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
items, paginationResult, err := h.announcementService.List(
|
||||||
|
c.Request.Context(),
|
||||||
|
params,
|
||||||
|
service.AnnouncementListFilters{Status: status, Search: search},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]dto.Announcement, 0, len(items))
|
||||||
|
for i := range items {
|
||||||
|
out = append(out, *dto.AnnouncementFromService(&items[i]))
|
||||||
|
}
|
||||||
|
response.Paginated(c, out, paginationResult.Total, page, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID handles getting an announcement by ID
|
||||||
|
// GET /api/v1/admin/announcements/:id
|
||||||
|
func (h *AnnouncementHandler) GetByID(c *gin.Context) {
|
||||||
|
announcementID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || announcementID <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid announcement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := h.announcementService.GetByID(c.Request.Context(), announcementID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.AnnouncementFromService(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handles creating a new announcement
|
||||||
|
// POST /api/v1/admin/announcements
|
||||||
|
func (h *AnnouncementHandler) Create(c *gin.Context) {
|
||||||
|
var req CreateAnnouncementRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware2.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok {
|
||||||
|
response.Unauthorized(c, "User not found in context")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &service.CreateAnnouncementInput{
|
||||||
|
Title: req.Title,
|
||||||
|
Content: req.Content,
|
||||||
|
Status: req.Status,
|
||||||
|
Targeting: req.Targeting,
|
||||||
|
ActorID: &subject.UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.StartsAt != nil && *req.StartsAt > 0 {
|
||||||
|
t := time.Unix(*req.StartsAt, 0)
|
||||||
|
input.StartsAt = &t
|
||||||
|
}
|
||||||
|
if req.EndsAt != nil && *req.EndsAt > 0 {
|
||||||
|
t := time.Unix(*req.EndsAt, 0)
|
||||||
|
input.EndsAt = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := h.announcementService.Create(c.Request.Context(), input)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.AnnouncementFromService(created))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update handles updating an announcement
|
||||||
|
// PUT /api/v1/admin/announcements/:id
|
||||||
|
func (h *AnnouncementHandler) Update(c *gin.Context) {
|
||||||
|
announcementID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || announcementID <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid announcement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req UpdateAnnouncementRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware2.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok {
|
||||||
|
response.Unauthorized(c, "User not found in context")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &service.UpdateAnnouncementInput{
|
||||||
|
Title: req.Title,
|
||||||
|
Content: req.Content,
|
||||||
|
Status: req.Status,
|
||||||
|
Targeting: req.Targeting,
|
||||||
|
ActorID: &subject.UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.StartsAt != nil {
|
||||||
|
if *req.StartsAt == 0 {
|
||||||
|
var cleared *time.Time = nil
|
||||||
|
input.StartsAt = &cleared
|
||||||
|
} else {
|
||||||
|
t := time.Unix(*req.StartsAt, 0)
|
||||||
|
ptr := &t
|
||||||
|
input.StartsAt = &ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.EndsAt != nil {
|
||||||
|
if *req.EndsAt == 0 {
|
||||||
|
var cleared *time.Time = nil
|
||||||
|
input.EndsAt = &cleared
|
||||||
|
} else {
|
||||||
|
t := time.Unix(*req.EndsAt, 0)
|
||||||
|
ptr := &t
|
||||||
|
input.EndsAt = &ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.announcementService.Update(c.Request.Context(), announcementID, input)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.AnnouncementFromService(updated))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete handles deleting an announcement
|
||||||
|
// DELETE /api/v1/admin/announcements/:id
|
||||||
|
func (h *AnnouncementHandler) Delete(c *gin.Context) {
|
||||||
|
announcementID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || announcementID <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid announcement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.announcementService.Delete(c.Request.Context(), announcementID); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "Announcement deleted successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReadStatus handles listing users read status for an announcement
|
||||||
|
// GET /api/v1/admin/announcements/:id/read-status
|
||||||
|
func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) {
|
||||||
|
announcementID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || announcementID <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid announcement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
params := pagination.PaginationParams{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
}
|
||||||
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
if len(search) > 200 {
|
||||||
|
search = search[:200]
|
||||||
|
}
|
||||||
|
|
||||||
|
items, paginationResult, err := h.announcementService.ListUserReadStatus(
|
||||||
|
c.Request.Context(),
|
||||||
|
announcementID,
|
||||||
|
params,
|
||||||
|
search,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Paginated(c, items, paginationResult.Total, page, pageSize)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,15 +14,17 @@ import (
|
|||||||
|
|
||||||
// DashboardHandler handles admin dashboard statistics
|
// DashboardHandler handles admin dashboard statistics
|
||||||
type DashboardHandler struct {
|
type DashboardHandler struct {
|
||||||
dashboardService *service.DashboardService
|
dashboardService *service.DashboardService
|
||||||
startTime time.Time // Server start time for uptime calculation
|
aggregationService *service.DashboardAggregationService
|
||||||
|
startTime time.Time // Server start time for uptime calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDashboardHandler creates a new admin dashboard handler
|
// NewDashboardHandler creates a new admin dashboard handler
|
||||||
func NewDashboardHandler(dashboardService *service.DashboardService) *DashboardHandler {
|
func NewDashboardHandler(dashboardService *service.DashboardService, aggregationService *service.DashboardAggregationService) *DashboardHandler {
|
||||||
return &DashboardHandler{
|
return &DashboardHandler{
|
||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
startTime: time.Now(),
|
aggregationService: aggregationService,
|
||||||
|
startTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +117,58 @@ func (h *DashboardHandler) GetStats(c *gin.Context) {
|
|||||||
// 性能指标
|
// 性能指标
|
||||||
"rpm": stats.Rpm,
|
"rpm": stats.Rpm,
|
||||||
"tpm": stats.Tpm,
|
"tpm": stats.Tpm,
|
||||||
|
|
||||||
|
// 预聚合新鲜度
|
||||||
|
"hourly_active_users": stats.HourlyActiveUsers,
|
||||||
|
"stats_updated_at": stats.StatsUpdatedAt,
|
||||||
|
"stats_stale": stats.StatsStale,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardAggregationBackfillRequest struct {
|
||||||
|
Start string `json:"start"`
|
||||||
|
End string `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackfillAggregation handles triggering aggregation backfill
|
||||||
|
// POST /api/v1/admin/dashboard/aggregation/backfill
|
||||||
|
func (h *DashboardHandler) BackfillAggregation(c *gin.Context) {
|
||||||
|
if h.aggregationService == nil {
|
||||||
|
response.InternalError(c, "Aggregation service not available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req DashboardAggregationBackfillRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start, err := time.Parse(time.RFC3339, req.Start)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid start time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end, err := time.Parse(time.RFC3339, req.End)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid end time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.aggregationService.TriggerBackfill(start, end); err != nil {
|
||||||
|
if errors.Is(err, service.ErrDashboardBackfillDisabled) {
|
||||||
|
response.Forbidden(c, "Backfill is disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, service.ErrDashboardBackfillTooLarge) {
|
||||||
|
response.BadRequest(c, "Backfill range too large")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.InternalError(c, "Failed to trigger backfill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"status": "accepted",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +186,17 @@ func (h *DashboardHandler) GetRealtimeMetrics(c *gin.Context) {
|
|||||||
|
|
||||||
// GetUsageTrend handles getting usage trend data
|
// GetUsageTrend handles getting usage trend data
|
||||||
// GET /api/v1/admin/dashboard/trend
|
// GET /api/v1/admin/dashboard/trend
|
||||||
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), user_id, api_key_id
|
// 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
|
||||||
func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
||||||
startTime, endTime := parseTimeRange(c)
|
startTime, endTime := parseTimeRange(c)
|
||||||
granularity := c.DefaultQuery("granularity", "day")
|
granularity := c.DefaultQuery("granularity", "day")
|
||||||
|
|
||||||
// Parse optional filter params
|
// Parse optional filter params
|
||||||
var userID, apiKeyID int64
|
var userID, apiKeyID, accountID, groupID int64
|
||||||
|
var model string
|
||||||
|
var stream *bool
|
||||||
|
var billingType *int8
|
||||||
|
|
||||||
if userIDStr := c.Query("user_id"); userIDStr != "" {
|
if userIDStr := c.Query("user_id"); userIDStr != "" {
|
||||||
if id, err := strconv.ParseInt(userIDStr, 10, 64); err == nil {
|
if id, err := strconv.ParseInt(userIDStr, 10, 64); err == nil {
|
||||||
userID = id
|
userID = id
|
||||||
@@ -148,8 +207,35 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
|||||||
apiKeyID = id
|
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 modelStr := c.Query("model"); modelStr != "" {
|
||||||
|
model = modelStr
|
||||||
|
}
|
||||||
|
if streamStr := c.Query("stream"); streamStr != "" {
|
||||||
|
if streamVal, err := strconv.ParseBool(streamStr); err == nil {
|
||||||
|
stream = &streamVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID)
|
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, stream, billingType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get usage trend")
|
response.Error(c, 500, "Failed to get usage trend")
|
||||||
return
|
return
|
||||||
@@ -165,12 +251,15 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
|||||||
|
|
||||||
// GetModelStats handles getting model usage statistics
|
// GetModelStats handles getting model usage statistics
|
||||||
// GET /api/v1/admin/dashboard/models
|
// GET /api/v1/admin/dashboard/models
|
||||||
// Query params: start_date, end_date (YYYY-MM-DD), user_id, api_key_id
|
// Query params: start_date, end_date (YYYY-MM-DD), user_id, api_key_id, account_id, group_id, stream, billing_type
|
||||||
func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
||||||
startTime, endTime := parseTimeRange(c)
|
startTime, endTime := parseTimeRange(c)
|
||||||
|
|
||||||
// Parse optional filter params
|
// Parse optional filter params
|
||||||
var userID, apiKeyID int64
|
var userID, apiKeyID, accountID, groupID int64
|
||||||
|
var stream *bool
|
||||||
|
var billingType *int8
|
||||||
|
|
||||||
if userIDStr := c.Query("user_id"); userIDStr != "" {
|
if userIDStr := c.Query("user_id"); userIDStr != "" {
|
||||||
if id, err := strconv.ParseInt(userIDStr, 10, 64); err == nil {
|
if id, err := strconv.ParseInt(userIDStr, 10, 64); err == nil {
|
||||||
userID = id
|
userID = id
|
||||||
@@ -181,8 +270,32 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
apiKeyID = id
|
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 streamStr := c.Query("stream"); streamStr != "" {
|
||||||
|
if streamVal, err := strconv.ParseBool(streamStr); err == nil {
|
||||||
|
stream = &streamVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID)
|
stats, err := h.dashboardService.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, stream, billingType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get model statistics")
|
response.Error(c, 500, "Failed to get model statistics")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ type CreateGroupRequest struct {
|
|||||||
ImagePrice4K *float64 `json:"image_price_4k"`
|
ImagePrice4K *float64 `json:"image_price_4k"`
|
||||||
ClaudeCodeOnly bool `json:"claude_code_only"`
|
ClaudeCodeOnly bool `json:"claude_code_only"`
|
||||||
FallbackGroupID *int64 `json:"fallback_group_id"`
|
FallbackGroupID *int64 `json:"fallback_group_id"`
|
||||||
|
// 模型路由配置(仅 anthropic 平台使用)
|
||||||
|
ModelRouting map[string][]int64 `json:"model_routing"`
|
||||||
|
ModelRoutingEnabled bool `json:"model_routing_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGroupRequest represents update group request
|
// UpdateGroupRequest represents update group request
|
||||||
@@ -60,6 +63,9 @@ type UpdateGroupRequest struct {
|
|||||||
ImagePrice4K *float64 `json:"image_price_4k"`
|
ImagePrice4K *float64 `json:"image_price_4k"`
|
||||||
ClaudeCodeOnly *bool `json:"claude_code_only"`
|
ClaudeCodeOnly *bool `json:"claude_code_only"`
|
||||||
FallbackGroupID *int64 `json:"fallback_group_id"`
|
FallbackGroupID *int64 `json:"fallback_group_id"`
|
||||||
|
// 模型路由配置(仅 anthropic 平台使用)
|
||||||
|
ModelRouting map[string][]int64 `json:"model_routing"`
|
||||||
|
ModelRoutingEnabled *bool `json:"model_routing_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handles listing all groups with pagination
|
// List handles listing all groups with pagination
|
||||||
@@ -88,9 +94,9 @@ func (h *GroupHandler) List(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outGroups := make([]dto.Group, 0, len(groups))
|
outGroups := make([]dto.AdminGroup, 0, len(groups))
|
||||||
for i := range groups {
|
for i := range groups {
|
||||||
outGroups = append(outGroups, *dto.GroupFromService(&groups[i]))
|
outGroups = append(outGroups, *dto.GroupFromServiceAdmin(&groups[i]))
|
||||||
}
|
}
|
||||||
response.Paginated(c, outGroups, total, page, pageSize)
|
response.Paginated(c, outGroups, total, page, pageSize)
|
||||||
}
|
}
|
||||||
@@ -114,9 +120,9 @@ func (h *GroupHandler) GetAll(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outGroups := make([]dto.Group, 0, len(groups))
|
outGroups := make([]dto.AdminGroup, 0, len(groups))
|
||||||
for i := range groups {
|
for i := range groups {
|
||||||
outGroups = append(outGroups, *dto.GroupFromService(&groups[i]))
|
outGroups = append(outGroups, *dto.GroupFromServiceAdmin(&groups[i]))
|
||||||
}
|
}
|
||||||
response.Success(c, outGroups)
|
response.Success(c, outGroups)
|
||||||
}
|
}
|
||||||
@@ -136,7 +142,7 @@ func (h *GroupHandler) GetByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.GroupFromService(group))
|
response.Success(c, dto.GroupFromServiceAdmin(group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create handles creating a new group
|
// Create handles creating a new group
|
||||||
@@ -149,27 +155,29 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group, err := h.adminService.CreateGroup(c.Request.Context(), &service.CreateGroupInput{
|
group, err := h.adminService.CreateGroup(c.Request.Context(), &service.CreateGroupInput{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Platform: req.Platform,
|
Platform: req.Platform,
|
||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
IsExclusive: req.IsExclusive,
|
IsExclusive: req.IsExclusive,
|
||||||
SubscriptionType: req.SubscriptionType,
|
SubscriptionType: req.SubscriptionType,
|
||||||
DailyLimitUSD: req.DailyLimitUSD,
|
DailyLimitUSD: req.DailyLimitUSD,
|
||||||
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
||||||
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
||||||
ImagePrice1K: req.ImagePrice1K,
|
ImagePrice1K: req.ImagePrice1K,
|
||||||
ImagePrice2K: req.ImagePrice2K,
|
ImagePrice2K: req.ImagePrice2K,
|
||||||
ImagePrice4K: req.ImagePrice4K,
|
ImagePrice4K: req.ImagePrice4K,
|
||||||
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
||||||
FallbackGroupID: req.FallbackGroupID,
|
FallbackGroupID: req.FallbackGroupID,
|
||||||
|
ModelRouting: req.ModelRouting,
|
||||||
|
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.GroupFromService(group))
|
response.Success(c, dto.GroupFromServiceAdmin(group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update handles updating a group
|
// Update handles updating a group
|
||||||
@@ -188,28 +196,30 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group, err := h.adminService.UpdateGroup(c.Request.Context(), groupID, &service.UpdateGroupInput{
|
group, err := h.adminService.UpdateGroup(c.Request.Context(), groupID, &service.UpdateGroupInput{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Platform: req.Platform,
|
Platform: req.Platform,
|
||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
IsExclusive: req.IsExclusive,
|
IsExclusive: req.IsExclusive,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
SubscriptionType: req.SubscriptionType,
|
SubscriptionType: req.SubscriptionType,
|
||||||
DailyLimitUSD: req.DailyLimitUSD,
|
DailyLimitUSD: req.DailyLimitUSD,
|
||||||
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
||||||
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
||||||
ImagePrice1K: req.ImagePrice1K,
|
ImagePrice1K: req.ImagePrice1K,
|
||||||
ImagePrice2K: req.ImagePrice2K,
|
ImagePrice2K: req.ImagePrice2K,
|
||||||
ImagePrice4K: req.ImagePrice4K,
|
ImagePrice4K: req.ImagePrice4K,
|
||||||
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
||||||
FallbackGroupID: req.FallbackGroupID,
|
FallbackGroupID: req.FallbackGroupID,
|
||||||
|
ModelRouting: req.ModelRouting,
|
||||||
|
ModelRoutingEnabled: req.ModelRoutingEnabled,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.GroupFromService(group))
|
response.Success(c, dto.GroupFromServiceAdmin(group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete handles deleting a group
|
// Delete handles deleting a group
|
||||||
|
|||||||
602
backend/internal/handler/admin/ops_alerts_handler.go
Normal file
602
backend/internal/handler/admin/ops_alerts_handler.go
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validOpsAlertMetricTypes = []string{
|
||||||
|
"success_rate",
|
||||||
|
"error_rate",
|
||||||
|
"upstream_error_rate",
|
||||||
|
"cpu_usage_percent",
|
||||||
|
"memory_usage_percent",
|
||||||
|
"concurrency_queue_depth",
|
||||||
|
}
|
||||||
|
|
||||||
|
var validOpsAlertMetricTypeSet = func() map[string]struct{} {
|
||||||
|
set := make(map[string]struct{}, len(validOpsAlertMetricTypes))
|
||||||
|
for _, v := range validOpsAlertMetricTypes {
|
||||||
|
set[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}()
|
||||||
|
|
||||||
|
var validOpsAlertOperators = []string{">", "<", ">=", "<=", "==", "!="}
|
||||||
|
|
||||||
|
var validOpsAlertOperatorSet = func() map[string]struct{} {
|
||||||
|
set := make(map[string]struct{}, len(validOpsAlertOperators))
|
||||||
|
for _, v := range validOpsAlertOperators {
|
||||||
|
set[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}()
|
||||||
|
|
||||||
|
var validOpsAlertSeverities = []string{"P0", "P1", "P2", "P3"}
|
||||||
|
|
||||||
|
var validOpsAlertSeveritySet = func() map[string]struct{} {
|
||||||
|
set := make(map[string]struct{}, len(validOpsAlertSeverities))
|
||||||
|
for _, v := range validOpsAlertSeverities {
|
||||||
|
set[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}()
|
||||||
|
|
||||||
|
type opsAlertRuleValidatedInput struct {
|
||||||
|
Name string
|
||||||
|
MetricType string
|
||||||
|
Operator string
|
||||||
|
Threshold float64
|
||||||
|
|
||||||
|
Severity string
|
||||||
|
|
||||||
|
WindowMinutes int
|
||||||
|
SustainedMinutes int
|
||||||
|
CooldownMinutes int
|
||||||
|
|
||||||
|
Enabled bool
|
||||||
|
NotifyEmail bool
|
||||||
|
|
||||||
|
WindowProvided bool
|
||||||
|
SustainedProvided bool
|
||||||
|
CooldownProvided bool
|
||||||
|
SeverityProvided bool
|
||||||
|
EnabledProvided bool
|
||||||
|
NotifyProvided bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPercentOrRateMetric(metricType string) bool {
|
||||||
|
switch metricType {
|
||||||
|
case "success_rate",
|
||||||
|
"error_rate",
|
||||||
|
"upstream_error_rate",
|
||||||
|
"cpu_usage_percent",
|
||||||
|
"memory_usage_percent":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpsAlertRulePayload(raw map[string]json.RawMessage) (*opsAlertRuleValidatedInput, error) {
|
||||||
|
if raw == nil {
|
||||||
|
return nil, fmt.Errorf("invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredFields := []string{"name", "metric_type", "operator", "threshold"}
|
||||||
|
for _, field := range requiredFields {
|
||||||
|
if _, ok := raw[field]; !ok {
|
||||||
|
return nil, fmt.Errorf("%s is required", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
if err := json.Unmarshal(raw["name"], &name); err != nil || strings.TrimSpace(name) == "" {
|
||||||
|
return nil, fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
|
||||||
|
var metricType string
|
||||||
|
if err := json.Unmarshal(raw["metric_type"], &metricType); err != nil || strings.TrimSpace(metricType) == "" {
|
||||||
|
return nil, fmt.Errorf("metric_type is required")
|
||||||
|
}
|
||||||
|
metricType = strings.TrimSpace(metricType)
|
||||||
|
if _, ok := validOpsAlertMetricTypeSet[metricType]; !ok {
|
||||||
|
return nil, fmt.Errorf("metric_type must be one of: %s", strings.Join(validOpsAlertMetricTypes, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
var operator string
|
||||||
|
if err := json.Unmarshal(raw["operator"], &operator); err != nil || strings.TrimSpace(operator) == "" {
|
||||||
|
return nil, fmt.Errorf("operator is required")
|
||||||
|
}
|
||||||
|
operator = strings.TrimSpace(operator)
|
||||||
|
if _, ok := validOpsAlertOperatorSet[operator]; !ok {
|
||||||
|
return nil, fmt.Errorf("operator must be one of: %s", strings.Join(validOpsAlertOperators, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
var threshold float64
|
||||||
|
if err := json.Unmarshal(raw["threshold"], &threshold); err != nil {
|
||||||
|
return nil, fmt.Errorf("threshold must be a number")
|
||||||
|
}
|
||||||
|
if math.IsNaN(threshold) || math.IsInf(threshold, 0) {
|
||||||
|
return nil, fmt.Errorf("threshold must be a finite number")
|
||||||
|
}
|
||||||
|
if isPercentOrRateMetric(metricType) {
|
||||||
|
if threshold < 0 || threshold > 100 {
|
||||||
|
return nil, fmt.Errorf("threshold must be between 0 and 100 for metric_type %s", metricType)
|
||||||
|
}
|
||||||
|
} else if threshold < 0 {
|
||||||
|
return nil, fmt.Errorf("threshold must be >= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
validated := &opsAlertRuleValidatedInput{
|
||||||
|
Name: name,
|
||||||
|
MetricType: metricType,
|
||||||
|
Operator: operator,
|
||||||
|
Threshold: threshold,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["severity"]; ok {
|
||||||
|
validated.SeverityProvided = true
|
||||||
|
var sev string
|
||||||
|
if err := json.Unmarshal(v, &sev); err != nil {
|
||||||
|
return nil, fmt.Errorf("severity must be a string")
|
||||||
|
}
|
||||||
|
sev = strings.ToUpper(strings.TrimSpace(sev))
|
||||||
|
if sev != "" {
|
||||||
|
if _, ok := validOpsAlertSeveritySet[sev]; !ok {
|
||||||
|
return nil, fmt.Errorf("severity must be one of: %s", strings.Join(validOpsAlertSeverities, ", "))
|
||||||
|
}
|
||||||
|
validated.Severity = sev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validated.Severity == "" {
|
||||||
|
validated.Severity = "P2"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["enabled"]; ok {
|
||||||
|
validated.EnabledProvided = true
|
||||||
|
if err := json.Unmarshal(v, &validated.Enabled); err != nil {
|
||||||
|
return nil, fmt.Errorf("enabled must be a boolean")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validated.Enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["notify_email"]; ok {
|
||||||
|
validated.NotifyProvided = true
|
||||||
|
if err := json.Unmarshal(v, &validated.NotifyEmail); err != nil {
|
||||||
|
return nil, fmt.Errorf("notify_email must be a boolean")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validated.NotifyEmail = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["window_minutes"]; ok {
|
||||||
|
validated.WindowProvided = true
|
||||||
|
if err := json.Unmarshal(v, &validated.WindowMinutes); err != nil {
|
||||||
|
return nil, fmt.Errorf("window_minutes must be an integer")
|
||||||
|
}
|
||||||
|
switch validated.WindowMinutes {
|
||||||
|
case 1, 5, 60:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("window_minutes must be one of: 1, 5, 60")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validated.WindowMinutes = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["sustained_minutes"]; ok {
|
||||||
|
validated.SustainedProvided = true
|
||||||
|
if err := json.Unmarshal(v, &validated.SustainedMinutes); err != nil {
|
||||||
|
return nil, fmt.Errorf("sustained_minutes must be an integer")
|
||||||
|
}
|
||||||
|
if validated.SustainedMinutes < 1 || validated.SustainedMinutes > 1440 {
|
||||||
|
return nil, fmt.Errorf("sustained_minutes must be between 1 and 1440")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validated.SustainedMinutes = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := raw["cooldown_minutes"]; ok {
|
||||||
|
validated.CooldownProvided = true
|
||||||
|
if err := json.Unmarshal(v, &validated.CooldownMinutes); err != nil {
|
||||||
|
return nil, fmt.Errorf("cooldown_minutes must be an integer")
|
||||||
|
}
|
||||||
|
if validated.CooldownMinutes < 0 || validated.CooldownMinutes > 1440 {
|
||||||
|
return nil, fmt.Errorf("cooldown_minutes must be between 0 and 1440")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validated.CooldownMinutes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return validated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertRules returns all ops alert rules.
|
||||||
|
// GET /api/v1/admin/ops/alert-rules
|
||||||
|
func (h *OpsHandler) ListAlertRules(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
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := h.opsService.ListAlertRules(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAlertRule creates an ops alert rule.
|
||||||
|
// POST /api/v1/admin/ops/alert-rules
|
||||||
|
func (h *OpsHandler) CreateAlertRule(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw map[string]json.RawMessage
|
||||||
|
if err := c.ShouldBindBodyWith(&raw, binding.JSON); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validated, err := validateOpsAlertRulePayload(raw)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rule service.OpsAlertRule
|
||||||
|
if err := c.ShouldBindBodyWith(&rule, binding.JSON); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.Name = validated.Name
|
||||||
|
rule.MetricType = validated.MetricType
|
||||||
|
rule.Operator = validated.Operator
|
||||||
|
rule.Threshold = validated.Threshold
|
||||||
|
rule.WindowMinutes = validated.WindowMinutes
|
||||||
|
rule.SustainedMinutes = validated.SustainedMinutes
|
||||||
|
rule.CooldownMinutes = validated.CooldownMinutes
|
||||||
|
rule.Severity = validated.Severity
|
||||||
|
rule.Enabled = validated.Enabled
|
||||||
|
rule.NotifyEmail = validated.NotifyEmail
|
||||||
|
|
||||||
|
created, err := h.opsService.CreateAlertRule(c.Request.Context(), &rule)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, created)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlertRule updates an existing ops alert rule.
|
||||||
|
// PUT /api/v1/admin/ops/alert-rules/:id
|
||||||
|
func (h *OpsHandler) UpdateAlertRule(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
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid rule ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw map[string]json.RawMessage
|
||||||
|
if err := c.ShouldBindBodyWith(&raw, binding.JSON); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validated, err := validateOpsAlertRulePayload(raw)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rule service.OpsAlertRule
|
||||||
|
if err := c.ShouldBindBodyWith(&rule, binding.JSON); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.ID = id
|
||||||
|
rule.Name = validated.Name
|
||||||
|
rule.MetricType = validated.MetricType
|
||||||
|
rule.Operator = validated.Operator
|
||||||
|
rule.Threshold = validated.Threshold
|
||||||
|
rule.WindowMinutes = validated.WindowMinutes
|
||||||
|
rule.SustainedMinutes = validated.SustainedMinutes
|
||||||
|
rule.CooldownMinutes = validated.CooldownMinutes
|
||||||
|
rule.Severity = validated.Severity
|
||||||
|
rule.Enabled = validated.Enabled
|
||||||
|
rule.NotifyEmail = validated.NotifyEmail
|
||||||
|
|
||||||
|
updated, err := h.opsService.UpdateAlertRule(c.Request.Context(), &rule)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlertRule deletes an ops alert rule.
|
||||||
|
// DELETE /api/v1/admin/ops/alert-rules/:id
|
||||||
|
func (h *OpsHandler) DeleteAlertRule(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
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid rule ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.opsService.DeleteAlertRule(c.Request.Context(), id); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"deleted": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlertEvent returns a single ops alert event.
|
||||||
|
// GET /api/v1/admin/ops/alert-events/:id
|
||||||
|
func (h *OpsHandler) GetAlertEvent(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
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid event ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ev, err := h.opsService.GetAlertEventByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlertEventStatus updates an ops alert event status.
|
||||||
|
// PUT /api/v1/admin/ops/alert-events/:id/status
|
||||||
|
func (h *OpsHandler) UpdateAlertEventStatus(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
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid event ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload.Status = strings.TrimSpace(payload.Status)
|
||||||
|
if payload.Status == "" {
|
||||||
|
response.BadRequest(c, "Invalid status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if payload.Status != service.OpsAlertStatusResolved && payload.Status != service.OpsAlertStatusManualResolved {
|
||||||
|
response.BadRequest(c, "Invalid status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedAt *time.Time
|
||||||
|
if payload.Status == service.OpsAlertStatusResolved || payload.Status == service.OpsAlertStatusManualResolved {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
resolvedAt = &now
|
||||||
|
}
|
||||||
|
if err := h.opsService.UpdateAlertEventStatus(c.Request.Context(), id, payload.Status, resolvedAt); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"updated": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertEvents lists recent ops alert events.
|
||||||
|
// GET /api/v1/admin/ops/alert-events
|
||||||
|
// CreateAlertSilence creates a scoped silence for ops alerts.
|
||||||
|
// POST /api/v1/admin/ops/alert-silences
|
||||||
|
func (h *OpsHandler) CreateAlertSilence(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
RuleID int64 `json:"rule_id"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
GroupID *int64 `json:"group_id"`
|
||||||
|
Region *string `json:"region"`
|
||||||
|
Until string `json:"until"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
until, err := time.Parse(time.RFC3339, strings.TrimSpace(payload.Until))
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid until")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createdBy := (*int64)(nil)
|
||||||
|
if subject, ok := middleware.GetAuthSubjectFromContext(c); ok {
|
||||||
|
uid := subject.UserID
|
||||||
|
createdBy = &uid
|
||||||
|
}
|
||||||
|
|
||||||
|
silence := &service.OpsAlertSilence{
|
||||||
|
RuleID: payload.RuleID,
|
||||||
|
Platform: strings.TrimSpace(payload.Platform),
|
||||||
|
GroupID: payload.GroupID,
|
||||||
|
Region: payload.Region,
|
||||||
|
Until: until,
|
||||||
|
Reason: strings.TrimSpace(payload.Reason),
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := h.opsService.CreateAlertSilence(c.Request.Context(), silence)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, created)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OpsHandler) ListAlertEvents(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
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 20
|
||||||
|
if raw := strings.TrimSpace(c.Query("limit")); raw != "" {
|
||||||
|
n, err := strconv.Atoi(raw)
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit = n
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsAlertEventFilter{
|
||||||
|
Limit: limit,
|
||||||
|
Status: strings.TrimSpace(c.Query("status")),
|
||||||
|
Severity: strings.TrimSpace(c.Query("severity")),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("email_sent")); v != "" {
|
||||||
|
vv := strings.ToLower(v)
|
||||||
|
switch vv {
|
||||||
|
case "true", "1":
|
||||||
|
b := true
|
||||||
|
filter.EmailSent = &b
|
||||||
|
case "false", "0":
|
||||||
|
b := false
|
||||||
|
filter.EmailSent = &b
|
||||||
|
default:
|
||||||
|
response.BadRequest(c, "Invalid email_sent")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor pagination: both params must be provided together.
|
||||||
|
rawTS := strings.TrimSpace(c.Query("before_fired_at"))
|
||||||
|
rawID := strings.TrimSpace(c.Query("before_id"))
|
||||||
|
if (rawTS == "") != (rawID == "") {
|
||||||
|
response.BadRequest(c, "before_fired_at and before_id must be provided together")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rawTS != "" {
|
||||||
|
ts, err := time.Parse(time.RFC3339Nano, rawTS)
|
||||||
|
if err != nil {
|
||||||
|
if t2, err2 := time.Parse(time.RFC3339, rawTS); err2 == nil {
|
||||||
|
ts = t2
|
||||||
|
} else {
|
||||||
|
response.BadRequest(c, "Invalid before_fired_at")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filter.BeforeFiredAt = &ts
|
||||||
|
}
|
||||||
|
if rawID != "" {
|
||||||
|
id, err := strconv.ParseInt(rawID, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid before_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.BeforeID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional global filter support (platform/group/time range).
|
||||||
|
if platform := strings.TrimSpace(c.Query("platform")); platform != "" {
|
||||||
|
filter.Platform = platform
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if startTime, endTime, err := parseOpsTimeRange(c, "24h"); err == nil {
|
||||||
|
// Only apply when explicitly provided to avoid surprising default narrowing.
|
||||||
|
if strings.TrimSpace(c.Query("start_time")) != "" || strings.TrimSpace(c.Query("end_time")) != "" || strings.TrimSpace(c.Query("time_range")) != "" {
|
||||||
|
filter.StartTime = &startTime
|
||||||
|
filter.EndTime = &endTime
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := h.opsService.ListAlertEvents(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, events)
|
||||||
|
}
|
||||||
243
backend/internal/handler/admin/ops_dashboard_handler.go
Normal file
243
backend/internal/handler/admin/ops_dashboard_handler.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDashboardOverview returns vNext ops dashboard overview (raw path).
|
||||||
|
// GET /api/v1/admin/ops/dashboard/overview
|
||||||
|
func (h *OpsHandler) GetDashboardOverview(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
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := h.opsService.GetDashboardOverview(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboardThroughputTrend returns throughput time series (raw path).
|
||||||
|
// GET /api/v1/admin/ops/dashboard/throughput-trend
|
||||||
|
func (h *OpsHandler) GetDashboardThroughputTrend(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))
|
||||||
|
data, err := h.opsService.GetThroughputTrend(c.Request.Context(), filter, bucketSeconds)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboardLatencyHistogram returns the latency distribution histogram (success requests).
|
||||||
|
// GET /api/v1/admin/ops/dashboard/latency-histogram
|
||||||
|
func (h *OpsHandler) GetDashboardLatencyHistogram(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
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := h.opsService.GetLatencyHistogram(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboardErrorTrend returns error counts time series (raw path).
|
||||||
|
// GET /api/v1/admin/ops/dashboard/error-trend
|
||||||
|
func (h *OpsHandler) GetDashboardErrorTrend(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))
|
||||||
|
data, err := h.opsService.GetErrorTrend(c.Request.Context(), filter, bucketSeconds)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboardErrorDistribution returns error distribution by status code (raw path).
|
||||||
|
// GET /api/v1/admin/ops/dashboard/error-distribution
|
||||||
|
func (h *OpsHandler) GetDashboardErrorDistribution(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
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := h.opsService.GetErrorDistribution(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickThroughputBucketSeconds(window time.Duration) int {
|
||||||
|
// Keep buckets predictable and avoid huge responses.
|
||||||
|
switch {
|
||||||
|
case window <= 2*time.Hour:
|
||||||
|
return 60
|
||||||
|
case window <= 24*time.Hour:
|
||||||
|
return 300
|
||||||
|
default:
|
||||||
|
return 3600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOpsQueryMode(c *gin.Context) service.OpsQueryMode {
|
||||||
|
if c == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
raw := strings.TrimSpace(c.Query("mode"))
|
||||||
|
if raw == "" {
|
||||||
|
// Empty means "use server default" (DB setting ops_query_mode_default).
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return service.ParseOpsQueryMode(raw)
|
||||||
|
}
|
||||||
925
backend/internal/handler/admin/ops_handler.go
Normal file
925
backend/internal/handler/admin/ops_handler.go
Normal file
@@ -0,0 +1,925 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpsHandler struct {
|
||||||
|
opsService *service.OpsService
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorLogByID returns ops error log detail.
|
||||||
|
// GET /api/v1/admin/ops/errors/:id
|
||||||
|
func (h *OpsHandler) GetErrorLogByID(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
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
detail, err := h.opsService.GetErrorLogByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opsListViewErrors = "errors"
|
||||||
|
opsListViewExcluded = "excluded"
|
||||||
|
opsListViewAll = "all"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseOpsViewParam(c *gin.Context) string {
|
||||||
|
if c == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v := strings.ToLower(strings.TrimSpace(c.Query("view")))
|
||||||
|
switch v {
|
||||||
|
case "", opsListViewErrors:
|
||||||
|
return opsListViewErrors
|
||||||
|
case opsListViewExcluded:
|
||||||
|
return opsListViewExcluded
|
||||||
|
case opsListViewAll:
|
||||||
|
return opsListViewAll
|
||||||
|
default:
|
||||||
|
return opsListViewErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOpsHandler(opsService *service.OpsService) *OpsHandler {
|
||||||
|
return &OpsHandler{opsService: opsService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorLogs lists ops error logs.
|
||||||
|
// GET /api/v1/admin/ops/errors
|
||||||
|
func (h *OpsHandler) GetErrorLogs(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
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
// Ops list can be larger than standard admin tables.
|
||||||
|
if pageSize > 500 {
|
||||||
|
pageSize = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsErrorLogFilter{Page: page, PageSize: pageSize}
|
||||||
|
|
||||||
|
if !startTime.IsZero() {
|
||||||
|
filter.StartTime = &startTime
|
||||||
|
}
|
||||||
|
if !endTime.IsZero() {
|
||||||
|
filter.EndTime = &endTime
|
||||||
|
}
|
||||||
|
filter.View = parseOpsViewParam(c)
|
||||||
|
filter.Phase = strings.TrimSpace(c.Query("phase"))
|
||||||
|
filter.Owner = strings.TrimSpace(c.Query("error_owner"))
|
||||||
|
filter.Source = strings.TrimSpace(c.Query("error_source"))
|
||||||
|
filter.Query = strings.TrimSpace(c.Query("q"))
|
||||||
|
filter.UserQuery = strings.TrimSpace(c.Query("user_query"))
|
||||||
|
|
||||||
|
// Force request errors: client-visible status >= 400.
|
||||||
|
// buildOpsErrorLogsWhere already applies this for non-upstream phase.
|
||||||
|
if strings.EqualFold(strings.TrimSpace(filter.Phase), "upstream") {
|
||||||
|
filter.Phase = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if platform := strings.TrimSpace(c.Query("platform")); platform != "" {
|
||||||
|
filter.Platform = platform
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("account_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid account_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.AccountID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("resolved")); v != "" {
|
||||||
|
switch strings.ToLower(v) {
|
||||||
|
case "1", "true", "yes":
|
||||||
|
b := true
|
||||||
|
filter.Resolved = &b
|
||||||
|
case "0", "false", "no":
|
||||||
|
b := false
|
||||||
|
filter.Resolved = &b
|
||||||
|
default:
|
||||||
|
response.BadRequest(c, "Invalid resolved")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusCodesStr := strings.TrimSpace(c.Query("status_codes")); statusCodesStr != "" {
|
||||||
|
parts := strings.Split(statusCodesStr, ",")
|
||||||
|
out := make([]int, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
p := strings.TrimSpace(part)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(p)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
response.BadRequest(c, "Invalid status_codes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = append(out, n)
|
||||||
|
}
|
||||||
|
filter.StatusCodes = out
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.GetErrorLogs(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Paginated(c, result.Errors, int64(result.Total), result.Page, result.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequestErrors lists client-visible request errors.
|
||||||
|
// GET /api/v1/admin/ops/request-errors
|
||||||
|
func (h *OpsHandler) ListRequestErrors(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
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
if pageSize > 500 {
|
||||||
|
pageSize = 500
|
||||||
|
}
|
||||||
|
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsErrorLogFilter{Page: page, PageSize: pageSize}
|
||||||
|
if !startTime.IsZero() {
|
||||||
|
filter.StartTime = &startTime
|
||||||
|
}
|
||||||
|
if !endTime.IsZero() {
|
||||||
|
filter.EndTime = &endTime
|
||||||
|
}
|
||||||
|
filter.View = parseOpsViewParam(c)
|
||||||
|
filter.Phase = strings.TrimSpace(c.Query("phase"))
|
||||||
|
filter.Owner = strings.TrimSpace(c.Query("error_owner"))
|
||||||
|
filter.Source = strings.TrimSpace(c.Query("error_source"))
|
||||||
|
filter.Query = strings.TrimSpace(c.Query("q"))
|
||||||
|
filter.UserQuery = strings.TrimSpace(c.Query("user_query"))
|
||||||
|
|
||||||
|
// Force request errors: client-visible status >= 400.
|
||||||
|
// buildOpsErrorLogsWhere already applies this for non-upstream phase.
|
||||||
|
if strings.EqualFold(strings.TrimSpace(filter.Phase), "upstream") {
|
||||||
|
filter.Phase = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if platform := strings.TrimSpace(c.Query("platform")); platform != "" {
|
||||||
|
filter.Platform = platform
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("account_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid account_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.AccountID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("resolved")); v != "" {
|
||||||
|
switch strings.ToLower(v) {
|
||||||
|
case "1", "true", "yes":
|
||||||
|
b := true
|
||||||
|
filter.Resolved = &b
|
||||||
|
case "0", "false", "no":
|
||||||
|
b := false
|
||||||
|
filter.Resolved = &b
|
||||||
|
default:
|
||||||
|
response.BadRequest(c, "Invalid resolved")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusCodesStr := strings.TrimSpace(c.Query("status_codes")); statusCodesStr != "" {
|
||||||
|
parts := strings.Split(statusCodesStr, ",")
|
||||||
|
out := make([]int, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
p := strings.TrimSpace(part)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(p)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
response.BadRequest(c, "Invalid status_codes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = append(out, n)
|
||||||
|
}
|
||||||
|
filter.StatusCodes = out
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.GetErrorLogs(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Paginated(c, result.Errors, int64(result.Total), result.Page, result.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestError returns request error detail.
|
||||||
|
// GET /api/v1/admin/ops/request-errors/:id
|
||||||
|
func (h *OpsHandler) GetRequestError(c *gin.Context) {
|
||||||
|
// same storage; just proxy to existing detail
|
||||||
|
h.GetErrorLogByID(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRequestErrorUpstreamErrors lists upstream error logs correlated to a request error.
|
||||||
|
// GET /api/v1/admin/ops/request-errors/:id/upstream-errors
|
||||||
|
func (h *OpsHandler) ListRequestErrorUpstreamErrors(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
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load request error to get correlation keys.
|
||||||
|
detail, err := h.opsService.GetErrorLogByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correlate by request_id/client_request_id.
|
||||||
|
requestID := strings.TrimSpace(detail.RequestID)
|
||||||
|
clientRequestID := strings.TrimSpace(detail.ClientRequestID)
|
||||||
|
if requestID == "" && clientRequestID == "" {
|
||||||
|
response.Paginated(c, []*service.OpsErrorLog{}, 0, 1, 10)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
if pageSize > 500 {
|
||||||
|
pageSize = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep correlation window wide enough so linked upstream errors
|
||||||
|
// are discoverable even when UI defaults to 1h elsewhere.
|
||||||
|
startTime, endTime, err := parseOpsTimeRange(c, "30d")
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsErrorLogFilter{Page: page, PageSize: pageSize}
|
||||||
|
if !startTime.IsZero() {
|
||||||
|
filter.StartTime = &startTime
|
||||||
|
}
|
||||||
|
if !endTime.IsZero() {
|
||||||
|
filter.EndTime = &endTime
|
||||||
|
}
|
||||||
|
filter.View = "all"
|
||||||
|
filter.Phase = "upstream"
|
||||||
|
filter.Owner = "provider"
|
||||||
|
filter.Source = strings.TrimSpace(c.Query("error_source"))
|
||||||
|
filter.Query = strings.TrimSpace(c.Query("q"))
|
||||||
|
|
||||||
|
if platform := strings.TrimSpace(c.Query("platform")); platform != "" {
|
||||||
|
filter.Platform = platform
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer exact match on request_id; if missing, fall back to client_request_id.
|
||||||
|
if requestID != "" {
|
||||||
|
filter.RequestID = requestID
|
||||||
|
} else {
|
||||||
|
filter.ClientRequestID = clientRequestID
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.GetErrorLogs(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If client asks for details, expand each upstream error log to include upstream response fields.
|
||||||
|
includeDetail := strings.TrimSpace(c.Query("include_detail"))
|
||||||
|
if includeDetail == "1" || strings.EqualFold(includeDetail, "true") || strings.EqualFold(includeDetail, "yes") {
|
||||||
|
details := make([]*service.OpsErrorLogDetail, 0, len(result.Errors))
|
||||||
|
for _, item := range result.Errors {
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d, err := h.opsService.GetErrorLogByID(c.Request.Context(), item.ID)
|
||||||
|
if err != nil || d == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
details = append(details, d)
|
||||||
|
}
|
||||||
|
response.Paginated(c, details, int64(result.Total), result.Page, result.PageSize)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Paginated(c, result.Errors, int64(result.Total), result.Page, result.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryRequestErrorClient retries the client request based on stored request body.
|
||||||
|
// POST /api/v1/admin/ops/request-errors/:id/retry-client
|
||||||
|
func (h *OpsHandler) RetryRequestErrorClient(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
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok || subject.UserID <= 0 {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.RetryError(c.Request.Context(), subject.UserID, id, service.OpsRetryModeClient, nil)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryRequestErrorUpstreamEvent retries a specific upstream attempt using captured upstream_request_body.
|
||||||
|
// POST /api/v1/admin/ops/request-errors/:id/upstream-errors/:idx/retry
|
||||||
|
func (h *OpsHandler) RetryRequestErrorUpstreamEvent(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
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok || subject.UserID <= 0 {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idxStr := strings.TrimSpace(c.Param("idx"))
|
||||||
|
idx, err := strconv.Atoi(idxStr)
|
||||||
|
if err != nil || idx < 0 {
|
||||||
|
response.BadRequest(c, "Invalid upstream idx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.RetryUpstreamEvent(c.Request.Context(), subject.UserID, id, idx)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveRequestError toggles resolved status.
|
||||||
|
// PUT /api/v1/admin/ops/request-errors/:id/resolve
|
||||||
|
func (h *OpsHandler) ResolveRequestError(c *gin.Context) {
|
||||||
|
h.UpdateErrorResolution(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUpstreamErrors lists independent upstream errors.
|
||||||
|
// GET /api/v1/admin/ops/upstream-errors
|
||||||
|
func (h *OpsHandler) ListUpstreamErrors(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
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
if pageSize > 500 {
|
||||||
|
pageSize = 500
|
||||||
|
}
|
||||||
|
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsErrorLogFilter{Page: page, PageSize: pageSize}
|
||||||
|
if !startTime.IsZero() {
|
||||||
|
filter.StartTime = &startTime
|
||||||
|
}
|
||||||
|
if !endTime.IsZero() {
|
||||||
|
filter.EndTime = &endTime
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.View = parseOpsViewParam(c)
|
||||||
|
filter.Phase = "upstream"
|
||||||
|
filter.Owner = "provider"
|
||||||
|
filter.Source = strings.TrimSpace(c.Query("error_source"))
|
||||||
|
filter.Query = strings.TrimSpace(c.Query("q"))
|
||||||
|
|
||||||
|
if platform := strings.TrimSpace(c.Query("platform")); platform != "" {
|
||||||
|
filter.Platform = platform
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("account_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid account_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.AccountID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("resolved")); v != "" {
|
||||||
|
switch strings.ToLower(v) {
|
||||||
|
case "1", "true", "yes":
|
||||||
|
b := true
|
||||||
|
filter.Resolved = &b
|
||||||
|
case "0", "false", "no":
|
||||||
|
b := false
|
||||||
|
filter.Resolved = &b
|
||||||
|
default:
|
||||||
|
response.BadRequest(c, "Invalid resolved")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusCodesStr := strings.TrimSpace(c.Query("status_codes")); statusCodesStr != "" {
|
||||||
|
parts := strings.Split(statusCodesStr, ",")
|
||||||
|
out := make([]int, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
p := strings.TrimSpace(part)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(p)
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
response.BadRequest(c, "Invalid status_codes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = append(out, n)
|
||||||
|
}
|
||||||
|
filter.StatusCodes = out
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.GetErrorLogs(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Paginated(c, result.Errors, int64(result.Total), result.Page, result.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpstreamError returns upstream error detail.
|
||||||
|
// GET /api/v1/admin/ops/upstream-errors/:id
|
||||||
|
func (h *OpsHandler) GetUpstreamError(c *gin.Context) {
|
||||||
|
h.GetErrorLogByID(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryUpstreamError retries upstream error using the original account_id.
|
||||||
|
// POST /api/v1/admin/ops/upstream-errors/:id/retry
|
||||||
|
func (h *OpsHandler) RetryUpstreamError(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
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok || subject.UserID <= 0 {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.RetryError(c.Request.Context(), subject.UserID, id, service.OpsRetryModeUpstream, nil)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveUpstreamError toggles resolved status.
|
||||||
|
// PUT /api/v1/admin/ops/upstream-errors/:id/resolve
|
||||||
|
func (h *OpsHandler) ResolveUpstreamError(c *gin.Context) {
|
||||||
|
h.UpdateErrorResolution(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Existing endpoints ====================
|
||||||
|
|
||||||
|
// ListRequestDetails returns a request-level list (success + error) for drill-down.
|
||||||
|
// GET /api/v1/admin/ops/requests
|
||||||
|
func (h *OpsHandler) ListRequestDetails(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
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
if pageSize > 100 {
|
||||||
|
pageSize = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsRequestDetailFilter{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
StartTime: &startTime,
|
||||||
|
EndTime: &endTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.Kind = strings.TrimSpace(c.Query("kind"))
|
||||||
|
filter.Platform = strings.TrimSpace(c.Query("platform"))
|
||||||
|
filter.Model = strings.TrimSpace(c.Query("model"))
|
||||||
|
filter.RequestID = strings.TrimSpace(c.Query("request_id"))
|
||||||
|
filter.Query = strings.TrimSpace(c.Query("q"))
|
||||||
|
filter.Sort = strings.TrimSpace(c.Query("sort"))
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("user_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid user_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.UserID = &id
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("api_key_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid api_key_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.APIKeyID = &id
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("account_id")); v != "" {
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid account_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.AccountID = &id
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(c.Query("min_duration_ms")); v != "" {
|
||||||
|
parsed, err := strconv.Atoi(v)
|
||||||
|
if err != nil || parsed < 0 {
|
||||||
|
response.BadRequest(c, "Invalid min_duration_ms")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.MinDurationMs = &parsed
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(c.Query("max_duration_ms")); v != "" {
|
||||||
|
parsed, err := strconv.Atoi(v)
|
||||||
|
if err != nil || parsed < 0 {
|
||||||
|
response.BadRequest(c, "Invalid max_duration_ms")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.MaxDurationMs = &parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := h.opsService.ListRequestDetails(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
// Invalid sort/kind/platform etc should be a bad request; keep it simple.
|
||||||
|
if strings.Contains(strings.ToLower(err.Error()), "invalid") {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Error(c, http.StatusInternalServerError, "Failed to list request details")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Paginated(c, out.Items, out.Total, out.Page, out.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
type opsRetryRequest struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
PinnedAccountID *int64 `json:"pinned_account_id"`
|
||||||
|
Force bool `json:"force"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type opsResolveRequest struct {
|
||||||
|
Resolved bool `json:"resolved"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryErrorRequest retries a failed request using stored request_body.
|
||||||
|
// POST /api/v1/admin/ops/errors/:id/retry
|
||||||
|
func (h *OpsHandler) RetryErrorRequest(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
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok || subject.UserID <= 0 {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := opsRetryRequest{Mode: service.OpsRetryModeClient}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(req.Mode) == "" {
|
||||||
|
req.Mode = service.OpsRetryModeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force flag is currently a UI-level acknowledgement. Server may still enforce safety constraints.
|
||||||
|
_ = req.Force
|
||||||
|
|
||||||
|
// Legacy endpoint safety: only allow retrying the client request here.
|
||||||
|
// Upstream retries must go through the split endpoints.
|
||||||
|
if strings.EqualFold(strings.TrimSpace(req.Mode), service.OpsRetryModeUpstream) {
|
||||||
|
response.BadRequest(c, "upstream retry is not supported on this endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.opsService.RetryError(c.Request.Context(), subject.UserID, id, req.Mode, req.PinnedAccountID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRetryAttempts lists retry attempts for an error log.
|
||||||
|
// GET /api/v1/admin/ops/errors/:id/retries
|
||||||
|
func (h *OpsHandler) ListRetryAttempts(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
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 50
|
||||||
|
if v := strings.TrimSpace(c.Query("limit")); v != "" {
|
||||||
|
n, err := strconv.Atoi(v)
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limit = n
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.opsService.ListRetryAttemptsByErrorID(c.Request.Context(), id, limit)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateErrorResolution allows manual resolve/unresolve.
|
||||||
|
// PUT /api/v1/admin/ops/errors/:id/resolve
|
||||||
|
func (h *OpsHandler) UpdateErrorResolution(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
|
||||||
|
}
|
||||||
|
|
||||||
|
subject, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok || subject.UserID <= 0 {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := strings.TrimSpace(c.Param("id"))
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
response.BadRequest(c, "Invalid error id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req opsResolveRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := subject.UserID
|
||||||
|
if err := h.opsService.UpdateErrorResolution(c.Request.Context(), id, req.Resolved, &uid, nil); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"ok": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOpsTimeRange(c *gin.Context, defaultRange string) (time.Time, time.Time, error) {
|
||||||
|
startStr := strings.TrimSpace(c.Query("start_time"))
|
||||||
|
endStr := strings.TrimSpace(c.Query("end_time"))
|
||||||
|
|
||||||
|
parseTS := func(s string) (time.Time, error) {
|
||||||
|
if s == "" {
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
return time.Parse(time.RFC3339, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := parseTS(startStr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, time.Time{}, err
|
||||||
|
}
|
||||||
|
end, err := parseTS(endStr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start/end explicitly provided (even partially)
|
||||||
|
if startStr != "" || endStr != "" {
|
||||||
|
if end.IsZero() {
|
||||||
|
end = time.Now()
|
||||||
|
}
|
||||||
|
if start.IsZero() {
|
||||||
|
dur, _ := parseOpsDuration(defaultRange)
|
||||||
|
start = end.Add(-dur)
|
||||||
|
}
|
||||||
|
if start.After(end) {
|
||||||
|
return time.Time{}, time.Time{}, fmt.Errorf("invalid time range: start_time must be <= end_time")
|
||||||
|
}
|
||||||
|
if end.Sub(start) > 30*24*time.Hour {
|
||||||
|
return time.Time{}, time.Time{}, fmt.Errorf("invalid time range: max window is 30 days")
|
||||||
|
}
|
||||||
|
return start, end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// time_range fallback
|
||||||
|
tr := strings.TrimSpace(c.Query("time_range"))
|
||||||
|
if tr == "" {
|
||||||
|
tr = defaultRange
|
||||||
|
}
|
||||||
|
dur, ok := parseOpsDuration(tr)
|
||||||
|
if !ok {
|
||||||
|
dur, _ = parseOpsDuration(defaultRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
end = time.Now()
|
||||||
|
start = end.Add(-dur)
|
||||||
|
if end.Sub(start) > 30*24*time.Hour {
|
||||||
|
return time.Time{}, time.Time{}, fmt.Errorf("invalid time range: max window is 30 days")
|
||||||
|
}
|
||||||
|
return start, end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOpsDuration(v string) (time.Duration, bool) {
|
||||||
|
switch strings.TrimSpace(v) {
|
||||||
|
case "5m":
|
||||||
|
return 5 * time.Minute, true
|
||||||
|
case "30m":
|
||||||
|
return 30 * time.Minute, true
|
||||||
|
case "1h":
|
||||||
|
return time.Hour, true
|
||||||
|
case "6h":
|
||||||
|
return 6 * time.Hour, true
|
||||||
|
case "24h":
|
||||||
|
return 24 * time.Hour, true
|
||||||
|
case "7d":
|
||||||
|
return 7 * 24 * time.Hour, true
|
||||||
|
case "30d":
|
||||||
|
return 30 * 24 * time.Hour, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
213
backend/internal/handler/admin/ops_realtime_handler.go
Normal file
213
backend/internal/handler/admin/ops_realtime_handler.go
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetConcurrencyStats returns real-time concurrency usage aggregated by platform/group/account.
|
||||||
|
// GET /api/v1/admin/ops/concurrency
|
||||||
|
func (h *OpsHandler) GetConcurrencyStats(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
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.opsService.IsRealtimeMonitoringEnabled(c.Request.Context()) {
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"enabled": false,
|
||||||
|
"platform": map[string]*service.PlatformConcurrencyInfo{},
|
||||||
|
"group": map[int64]*service.GroupConcurrencyInfo{},
|
||||||
|
"account": map[int64]*service.AccountConcurrencyInfo{},
|
||||||
|
"timestamp": time.Now().UTC(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
platformFilter := strings.TrimSpace(c.Query("platform"))
|
||||||
|
var groupID *int64
|
||||||
|
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
|
||||||
|
}
|
||||||
|
groupID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
platform, group, account, collectedAt, err := h.opsService.GetConcurrencyStats(c.Request.Context(), platformFilter, groupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := gin.H{
|
||||||
|
"enabled": true,
|
||||||
|
"platform": platform,
|
||||||
|
"group": group,
|
||||||
|
"account": account,
|
||||||
|
}
|
||||||
|
if collectedAt != nil {
|
||||||
|
payload["timestamp"] = collectedAt.UTC()
|
||||||
|
}
|
||||||
|
response.Success(c, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountAvailability returns account availability statistics.
|
||||||
|
// GET /api/v1/admin/ops/account-availability
|
||||||
|
//
|
||||||
|
// Query params:
|
||||||
|
// - platform: optional
|
||||||
|
// - group_id: optional
|
||||||
|
func (h *OpsHandler) GetAccountAvailability(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
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.opsService.IsRealtimeMonitoringEnabled(c.Request.Context()) {
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"enabled": false,
|
||||||
|
"platform": map[string]*service.PlatformAvailability{},
|
||||||
|
"group": map[int64]*service.GroupAvailability{},
|
||||||
|
"account": map[int64]*service.AccountAvailability{},
|
||||||
|
"timestamp": time.Now().UTC(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
platform := strings.TrimSpace(c.Query("platform"))
|
||||||
|
var groupID *int64
|
||||||
|
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
|
||||||
|
}
|
||||||
|
groupID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
platformStats, groupStats, accountStats, collectedAt, err := h.opsService.GetAccountAvailabilityStats(c.Request.Context(), platform, groupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := gin.H{
|
||||||
|
"enabled": true,
|
||||||
|
"platform": platformStats,
|
||||||
|
"group": groupStats,
|
||||||
|
"account": accountStats,
|
||||||
|
}
|
||||||
|
if collectedAt != nil {
|
||||||
|
payload["timestamp"] = collectedAt.UTC()
|
||||||
|
}
|
||||||
|
response.Success(c, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOpsRealtimeWindow(v string) (time.Duration, string, bool) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||||
|
case "", "1min", "1m":
|
||||||
|
return 1 * time.Minute, "1min", true
|
||||||
|
case "5min", "5m":
|
||||||
|
return 5 * time.Minute, "5min", true
|
||||||
|
case "30min", "30m":
|
||||||
|
return 30 * time.Minute, "30min", true
|
||||||
|
case "1h", "60m", "60min":
|
||||||
|
return 1 * time.Hour, "1h", true
|
||||||
|
default:
|
||||||
|
return 0, "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRealtimeTrafficSummary returns QPS/TPS current/peak/avg for the selected window.
|
||||||
|
// GET /api/v1/admin/ops/realtime-traffic
|
||||||
|
//
|
||||||
|
// Query params:
|
||||||
|
// - window: 1min|5min|30min|1h (default: 1min)
|
||||||
|
// - platform: optional
|
||||||
|
// - group_id: optional
|
||||||
|
func (h *OpsHandler) GetRealtimeTrafficSummary(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
|
||||||
|
}
|
||||||
|
|
||||||
|
windowDur, windowLabel, ok := parseOpsRealtimeWindow(c.Query("window"))
|
||||||
|
if !ok {
|
||||||
|
response.BadRequest(c, "Invalid window")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
platform := strings.TrimSpace(c.Query("platform"))
|
||||||
|
var groupID *int64
|
||||||
|
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
|
||||||
|
}
|
||||||
|
groupID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime := time.Now().UTC()
|
||||||
|
startTime := endTime.Add(-windowDur)
|
||||||
|
|
||||||
|
if !h.opsService.IsRealtimeMonitoringEnabled(c.Request.Context()) {
|
||||||
|
disabledSummary := &service.OpsRealtimeTrafficSummary{
|
||||||
|
Window: windowLabel,
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
|
Platform: platform,
|
||||||
|
GroupID: groupID,
|
||||||
|
QPS: service.OpsRateSummary{},
|
||||||
|
TPS: service.OpsRateSummary{},
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"enabled": false,
|
||||||
|
"summary": disabledSummary,
|
||||||
|
"timestamp": endTime,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &service.OpsDashboardFilter{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
|
Platform: platform,
|
||||||
|
GroupID: groupID,
|
||||||
|
QueryMode: service.OpsQueryModeRaw,
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := h.opsService.GetRealtimeTrafficSummary(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if summary != nil {
|
||||||
|
summary.Window = windowLabel
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"enabled": true,
|
||||||
|
"summary": summary,
|
||||||
|
"timestamp": endTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
194
backend/internal/handler/admin/ops_settings_handler.go
Normal file
194
backend/internal/handler/admin/ops_settings_handler.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetEmailNotificationConfig returns Ops email notification config (DB-backed).
|
||||||
|
// GET /api/v1/admin/ops/email-notification/config
|
||||||
|
func (h *OpsHandler) GetEmailNotificationConfig(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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := h.opsService.GetEmailNotificationConfig(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusInternalServerError, "Failed to get email notification config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailNotificationConfig updates Ops email notification config (DB-backed).
|
||||||
|
// PUT /api/v1/admin/ops/email-notification/config
|
||||||
|
func (h *OpsHandler) UpdateEmailNotificationConfig(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var req service.OpsEmailNotificationConfigUpdateRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.opsService.UpdateEmailNotificationConfig(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
// Most failures here are validation errors from request payload; treat as 400.
|
||||||
|
response.Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlertRuntimeSettings returns Ops alert evaluator runtime settings (DB-backed).
|
||||||
|
// GET /api/v1/admin/ops/runtime/alert
|
||||||
|
func (h *OpsHandler) GetAlertRuntimeSettings(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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := h.opsService.GetOpsAlertRuntimeSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusInternalServerError, "Failed to get alert runtime settings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlertRuntimeSettings updates Ops alert evaluator runtime settings (DB-backed).
|
||||||
|
// PUT /api/v1/admin/ops/runtime/alert
|
||||||
|
func (h *OpsHandler) UpdateAlertRuntimeSettings(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var req service.OpsAlertRuntimeSettings
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.opsService.UpdateOpsAlertRuntimeSettings(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdvancedSettings returns Ops advanced settings (DB-backed).
|
||||||
|
// GET /api/v1/admin/ops/advanced-settings
|
||||||
|
func (h *OpsHandler) GetAdvancedSettings(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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := h.opsService.GetOpsAdvancedSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusInternalServerError, "Failed to get advanced settings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAdvancedSettings updates Ops advanced settings (DB-backed).
|
||||||
|
// PUT /api/v1/admin/ops/advanced-settings
|
||||||
|
func (h *OpsHandler) UpdateAdvancedSettings(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var req service.OpsAdvancedSettings
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.opsService.UpdateOpsAdvancedSettings(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetricThresholds returns Ops metric thresholds (DB-backed).
|
||||||
|
// GET /api/v1/admin/ops/settings/metric-thresholds
|
||||||
|
func (h *OpsHandler) GetMetricThresholds(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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := h.opsService.GetMetricThresholds(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusInternalServerError, "Failed to get metric thresholds")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMetricThresholds updates Ops metric thresholds (DB-backed).
|
||||||
|
// PUT /api/v1/admin/ops/settings/metric-thresholds
|
||||||
|
func (h *OpsHandler) UpdateMetricThresholds(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var req service.OpsMetricThresholds
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.opsService.UpdateMetricThresholds(c.Request.Context(), &req)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
771
backend/internal/handler/admin/ops_ws_handler.go
Normal file
771
backend/internal/handler/admin/ops_ws_handler.go
Normal file
@@ -0,0 +1,771 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpsWSProxyConfig struct {
|
||||||
|
TrustProxy bool
|
||||||
|
TrustedProxies []netip.Prefix
|
||||||
|
OriginPolicy string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
envOpsWSTrustProxy = "OPS_WS_TRUST_PROXY"
|
||||||
|
envOpsWSTrustedProxies = "OPS_WS_TRUSTED_PROXIES"
|
||||||
|
envOpsWSOriginPolicy = "OPS_WS_ORIGIN_POLICY"
|
||||||
|
envOpsWSMaxConns = "OPS_WS_MAX_CONNS"
|
||||||
|
envOpsWSMaxConnsPerIP = "OPS_WS_MAX_CONNS_PER_IP"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OriginPolicyStrict = "strict"
|
||||||
|
OriginPolicyPermissive = "permissive"
|
||||||
|
)
|
||||||
|
|
||||||
|
var opsWSProxyConfig = loadOpsWSProxyConfigFromEnv()
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return isAllowedOpsWSOrigin(r)
|
||||||
|
},
|
||||||
|
// Subprotocol negotiation:
|
||||||
|
// - The frontend passes ["sub2api-admin", "jwt.<token>"].
|
||||||
|
// - We always select "sub2api-admin" so the token is never echoed back in the handshake response.
|
||||||
|
Subprotocols: []string{"sub2api-admin"},
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
qpsWSPushInterval = 2 * time.Second
|
||||||
|
qpsWSRefreshInterval = 5 * time.Second
|
||||||
|
qpsWSRequestCountWindow = 1 * time.Minute
|
||||||
|
|
||||||
|
defaultMaxWSConns = 100
|
||||||
|
defaultMaxWSConnsPerIP = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
var wsConnCount atomic.Int32
|
||||||
|
var wsConnCountByIP sync.Map // map[string]*atomic.Int32
|
||||||
|
|
||||||
|
const qpsWSIdleStopDelay = 30 * time.Second
|
||||||
|
|
||||||
|
const (
|
||||||
|
opsWSCloseRealtimeDisabled = 4001
|
||||||
|
)
|
||||||
|
|
||||||
|
var qpsWSIdleStopMu sync.Mutex
|
||||||
|
var qpsWSIdleStopTimer *time.Timer
|
||||||
|
|
||||||
|
func cancelQPSWSIdleStop() {
|
||||||
|
qpsWSIdleStopMu.Lock()
|
||||||
|
if qpsWSIdleStopTimer != nil {
|
||||||
|
qpsWSIdleStopTimer.Stop()
|
||||||
|
qpsWSIdleStopTimer = nil
|
||||||
|
}
|
||||||
|
qpsWSIdleStopMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleQPSWSIdleStop() {
|
||||||
|
qpsWSIdleStopMu.Lock()
|
||||||
|
if qpsWSIdleStopTimer != nil {
|
||||||
|
qpsWSIdleStopMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
qpsWSIdleStopTimer = time.AfterFunc(qpsWSIdleStopDelay, func() {
|
||||||
|
// Only stop if truly idle at fire time.
|
||||||
|
if wsConnCount.Load() == 0 {
|
||||||
|
qpsWSCache.Stop()
|
||||||
|
}
|
||||||
|
qpsWSIdleStopMu.Lock()
|
||||||
|
qpsWSIdleStopTimer = nil
|
||||||
|
qpsWSIdleStopMu.Unlock()
|
||||||
|
})
|
||||||
|
qpsWSIdleStopMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
type opsWSRuntimeLimits struct {
|
||||||
|
MaxConns int32
|
||||||
|
MaxConnsPerIP int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var opsWSLimits = loadOpsWSRuntimeLimitsFromEnv()
|
||||||
|
|
||||||
|
const (
|
||||||
|
qpsWSWriteTimeout = 10 * time.Second
|
||||||
|
qpsWSPongWait = 60 * time.Second
|
||||||
|
qpsWSPingInterval = 30 * time.Second
|
||||||
|
|
||||||
|
// We don't expect clients to send application messages; we only read to process control frames (Pong/Close).
|
||||||
|
qpsWSMaxReadBytes = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
type opsWSQPSCache struct {
|
||||||
|
refreshInterval time.Duration
|
||||||
|
requestCountWindow time.Duration
|
||||||
|
|
||||||
|
lastUpdatedUnixNano atomic.Int64
|
||||||
|
payload atomic.Value // []byte
|
||||||
|
|
||||||
|
opsService *service.OpsService
|
||||||
|
cancel context.CancelFunc
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var qpsWSCache = &opsWSQPSCache{
|
||||||
|
refreshInterval: qpsWSRefreshInterval,
|
||||||
|
requestCountWindow: qpsWSRequestCountWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *opsWSQPSCache) start(opsService *service.OpsService) {
|
||||||
|
if c == nil || opsService == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.running {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a previous refresh loop is currently stopping, wait for it to fully exit.
|
||||||
|
done := c.done
|
||||||
|
if done != nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
<-done
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.done == done && !c.running {
|
||||||
|
c.done = nil
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.opsService = opsService
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
c.cancel = cancel
|
||||||
|
c.done = make(chan struct{})
|
||||||
|
done = c.done
|
||||||
|
c.running = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
c.refreshLoop(ctx)
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the background refresh loop.
|
||||||
|
// It is safe to call multiple times.
|
||||||
|
func (c *opsWSQPSCache) Stop() {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
if !c.running {
|
||||||
|
done := c.done
|
||||||
|
c.mu.Unlock()
|
||||||
|
if done != nil {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cancel := c.cancel
|
||||||
|
c.cancel = nil
|
||||||
|
c.running = false
|
||||||
|
c.opsService = nil
|
||||||
|
done := c.done
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
if done != nil {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.done == done && !c.running {
|
||||||
|
c.done = nil
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *opsWSQPSCache) refreshLoop(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(c.refreshInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
c.refresh(ctx)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
c.refresh(ctx)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *opsWSQPSCache) refresh(parentCtx context.Context) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
opsService := c.opsService
|
||||||
|
c.mu.Unlock()
|
||||||
|
if opsService == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentCtx == nil {
|
||||||
|
parentCtx = context.Background()
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
stats, err := opsService.GetWindowStats(ctx, now.Add(-c.requestCountWindow), now)
|
||||||
|
if err != nil || stats == nil {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[OpsWS] refresh: get window stats failed: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCount := stats.SuccessCount + stats.ErrorCountTotal
|
||||||
|
qps := 0.0
|
||||||
|
tps := 0.0
|
||||||
|
if c.requestCountWindow > 0 {
|
||||||
|
seconds := c.requestCountWindow.Seconds()
|
||||||
|
qps = roundTo1DP(float64(requestCount) / seconds)
|
||||||
|
tps = roundTo1DP(float64(stats.TokenConsumed) / seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := gin.H{
|
||||||
|
"type": "qps_update",
|
||||||
|
"timestamp": now.Format(time.RFC3339),
|
||||||
|
"data": gin.H{
|
||||||
|
"qps": qps,
|
||||||
|
"tps": tps,
|
||||||
|
"request_count": requestCount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[OpsWS] refresh: marshal payload failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.payload.Store(msg)
|
||||||
|
c.lastUpdatedUnixNano.Store(now.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func roundTo1DP(v float64) float64 {
|
||||||
|
return math.Round(v*10) / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *opsWSQPSCache) getPayload() []byte {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cached, ok := c.payload.Load().([]byte); ok && cached != nil {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeWS(conn *websocket.Conn, code int, reason string) {
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := websocket.FormatCloseMessage(code, reason)
|
||||||
|
_ = conn.WriteControl(websocket.CloseMessage, msg, time.Now().Add(qpsWSWriteTimeout))
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QPSWSHandler handles realtime QPS push via WebSocket.
|
||||||
|
// GET /api/v1/admin/ops/ws/qps
|
||||||
|
func (h *OpsHandler) QPSWSHandler(c *gin.Context) {
|
||||||
|
clientIP := requestClientIP(c.Request)
|
||||||
|
|
||||||
|
if h == nil || h.opsService == nil {
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "ops service not initialized"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If realtime monitoring is disabled, prefer a successful WS upgrade followed by a clean close
|
||||||
|
// with a deterministic close code. This prevents clients from spinning on 404/1006 reconnect loops.
|
||||||
|
if !h.opsService.IsRealtimeMonitoringEnabled(c.Request.Context()) {
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "ops realtime monitoring is disabled"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
closeWS(conn, opsWSCloseRealtimeDisabled, "realtime_disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelQPSWSIdleStop()
|
||||||
|
// Lazily start the background refresh loop so unit tests that never hit the
|
||||||
|
// websocket route don't spawn goroutines that depend on DB/Redis stubs.
|
||||||
|
qpsWSCache.start(h.opsService)
|
||||||
|
|
||||||
|
// Reserve a global slot before upgrading the connection to keep the limit strict.
|
||||||
|
if !tryAcquireOpsWSTotalSlot(opsWSLimits.MaxConns) {
|
||||||
|
log.Printf("[OpsWS] connection limit reached: %d/%d", wsConnCount.Load(), opsWSLimits.MaxConns)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "too many connections"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if wsConnCount.Add(-1) == 0 {
|
||||||
|
scheduleQPSWSIdleStop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if opsWSLimits.MaxConnsPerIP > 0 && clientIP != "" {
|
||||||
|
if !tryAcquireOpsWSIPSlot(clientIP, opsWSLimits.MaxConnsPerIP) {
|
||||||
|
log.Printf("[OpsWS] per-ip connection limit reached: ip=%s limit=%d", clientIP, opsWSLimits.MaxConnsPerIP)
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "too many connections"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer releaseOpsWSIPSlot(clientIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[OpsWS] upgrade failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
handleQPSWebSocket(c.Request.Context(), conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryAcquireOpsWSTotalSlot(limit int32) bool {
|
||||||
|
if limit <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
current := wsConnCount.Load()
|
||||||
|
if current >= limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if wsConnCount.CompareAndSwap(current, current+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
current := counter.Load()
|
||||||
|
if current >= limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if counter.CompareAndSwap(current, current+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseOpsWSIPSlot(clientIP string) {
|
||||||
|
if strings.TrimSpace(clientIP) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := wsConnCountByIP.Load(clientIP)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
counter, ok := v.(*atomic.Int32)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := counter.Add(-1)
|
||||||
|
if next <= 0 {
|
||||||
|
// Best-effort cleanup; safe even if a new slot was acquired concurrently.
|
||||||
|
wsConnCountByIP.Delete(clientIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleQPSWebSocket(parentCtx context.Context, conn *websocket.Conn) {
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var closeOnce sync.Once
|
||||||
|
closeConn := func() {
|
||||||
|
closeOnce.Do(func() {
|
||||||
|
_ = conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
closeFrameCh := make(chan []byte, 1)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn.SetReadLimit(qpsWSMaxReadBytes)
|
||||||
|
if err := conn.SetReadDeadline(time.Now().Add(qpsWSPongWait)); err != nil {
|
||||||
|
log.Printf("[OpsWS] set read deadline failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.SetPongHandler(func(string) error {
|
||||||
|
return conn.SetReadDeadline(time.Now().Add(qpsWSPongWait))
|
||||||
|
})
|
||||||
|
conn.SetCloseHandler(func(code int, text string) error {
|
||||||
|
select {
|
||||||
|
case closeFrameCh <- websocket.FormatCloseMessage(code, text):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, _, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
|
||||||
|
log.Printf("[OpsWS] read failed: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Push QPS data every 2 seconds (values are globally cached and refreshed at most once per qpsWSRefreshInterval).
|
||||||
|
pushTicker := time.NewTicker(qpsWSPushInterval)
|
||||||
|
defer pushTicker.Stop()
|
||||||
|
|
||||||
|
// Heartbeat ping every 30 seconds.
|
||||||
|
pingTicker := time.NewTicker(qpsWSPingInterval)
|
||||||
|
defer pingTicker.Stop()
|
||||||
|
|
||||||
|
writeWithTimeout := func(messageType int, data []byte) error {
|
||||||
|
if err := conn.SetWriteDeadline(time.Now().Add(qpsWSWriteTimeout)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return conn.WriteMessage(messageType, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendClose := func(closeFrame []byte) {
|
||||||
|
if closeFrame == nil {
|
||||||
|
closeFrame = websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
|
||||||
|
}
|
||||||
|
_ = writeWithTimeout(websocket.CloseMessage, closeFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-pushTicker.C:
|
||||||
|
msg := qpsWSCache.getPayload()
|
||||||
|
if msg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := writeWithTimeout(websocket.TextMessage, msg); err != nil {
|
||||||
|
log.Printf("[OpsWS] write failed: %v", err)
|
||||||
|
cancel()
|
||||||
|
closeConn()
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-pingTicker.C:
|
||||||
|
if err := writeWithTimeout(websocket.PingMessage, nil); err != nil {
|
||||||
|
log.Printf("[OpsWS] ping failed: %v", err)
|
||||||
|
cancel()
|
||||||
|
closeConn()
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case closeFrame := <-closeFrameCh:
|
||||||
|
sendClose(closeFrame)
|
||||||
|
closeConn()
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
var closeFrame []byte
|
||||||
|
select {
|
||||||
|
case closeFrame = <-closeFrameCh:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
sendClose(closeFrame)
|
||||||
|
|
||||||
|
closeConn()
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAllowedOpsWSOrigin(r *http.Request) bool {
|
||||||
|
if r == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
origin := strings.TrimSpace(r.Header.Get("Origin"))
|
||||||
|
if origin == "" {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(opsWSProxyConfig.OriginPolicy)) {
|
||||||
|
case OriginPolicyStrict:
|
||||||
|
return false
|
||||||
|
case OriginPolicyPermissive, "":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(origin)
|
||||||
|
if err != nil || parsed.Hostname() == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
originHost := strings.ToLower(parsed.Hostname())
|
||||||
|
|
||||||
|
trustProxyHeaders := shouldTrustOpsWSProxyHeaders(r)
|
||||||
|
reqHost := hostWithoutPort(r.Host)
|
||||||
|
if trustProxyHeaders {
|
||||||
|
xfHost := strings.TrimSpace(r.Header.Get("X-Forwarded-Host"))
|
||||||
|
if xfHost != "" {
|
||||||
|
xfHost = strings.TrimSpace(strings.Split(xfHost, ",")[0])
|
||||||
|
if xfHost != "" {
|
||||||
|
reqHost = hostWithoutPort(xfHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reqHost = strings.ToLower(reqHost)
|
||||||
|
if reqHost == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return originHost == reqHost
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldTrustOpsWSProxyHeaders(r *http.Request) bool {
|
||||||
|
if r == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !opsWSProxyConfig.TrustProxy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
peerIP, ok := requestPeerIP(r)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isAddrInTrustedProxies(peerIP, opsWSProxyConfig.TrustedProxies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestPeerIP(r *http.Request) (netip.Addr, bool) {
|
||||||
|
if r == nil {
|
||||||
|
return netip.Addr{}, false
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
|
||||||
|
if err != nil {
|
||||||
|
host = strings.TrimSpace(r.RemoteAddr)
|
||||||
|
}
|
||||||
|
host = strings.TrimPrefix(host, "[")
|
||||||
|
host = strings.TrimSuffix(host, "]")
|
||||||
|
if host == "" {
|
||||||
|
return netip.Addr{}, false
|
||||||
|
}
|
||||||
|
addr, err := netip.ParseAddr(host)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, false
|
||||||
|
}
|
||||||
|
return addr.Unmap(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestClientIP(r *http.Request) string {
|
||||||
|
if r == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
trustProxyHeaders := shouldTrustOpsWSProxyHeaders(r)
|
||||||
|
if trustProxyHeaders {
|
||||||
|
xff := strings.TrimSpace(r.Header.Get("X-Forwarded-For"))
|
||||||
|
if xff != "" {
|
||||||
|
// Use the left-most entry (original client). If multiple proxies add values, they are comma-separated.
|
||||||
|
xff = strings.TrimSpace(strings.Split(xff, ",")[0])
|
||||||
|
xff = strings.TrimPrefix(xff, "[")
|
||||||
|
xff = strings.TrimSuffix(xff, "]")
|
||||||
|
if addr, err := netip.ParseAddr(xff); err == nil && addr.IsValid() {
|
||||||
|
return addr.Unmap().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer, ok := requestPeerIP(r); ok && peer.IsValid() {
|
||||||
|
return peer.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAddrInTrustedProxies(addr netip.Addr, trusted []netip.Prefix) bool {
|
||||||
|
if !addr.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, p := range trusted {
|
||||||
|
if p.Contains(addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOpsWSProxyConfigFromEnv() OpsWSProxyConfig {
|
||||||
|
cfg := OpsWSProxyConfig{
|
||||||
|
TrustProxy: true,
|
||||||
|
TrustedProxies: defaultTrustedProxies(),
|
||||||
|
OriginPolicy: OriginPolicyPermissive,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(os.Getenv(envOpsWSTrustProxy)); v != "" {
|
||||||
|
if parsed, err := strconv.ParseBool(v); err == nil {
|
||||||
|
cfg.TrustProxy = parsed
|
||||||
|
} else {
|
||||||
|
log.Printf("[OpsWS] invalid %s=%q (expected bool); using default=%v", envOpsWSTrustProxy, v, cfg.TrustProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw := strings.TrimSpace(os.Getenv(envOpsWSTrustedProxies)); raw != "" {
|
||||||
|
prefixes, invalid := parseTrustedProxyList(raw)
|
||||||
|
if len(invalid) > 0 {
|
||||||
|
log.Printf("[OpsWS] invalid %s entries ignored: %s", envOpsWSTrustedProxies, strings.Join(invalid, ", "))
|
||||||
|
}
|
||||||
|
cfg.TrustedProxies = prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(os.Getenv(envOpsWSOriginPolicy)); v != "" {
|
||||||
|
normalized := strings.ToLower(v)
|
||||||
|
switch normalized {
|
||||||
|
case OriginPolicyStrict, OriginPolicyPermissive:
|
||||||
|
cfg.OriginPolicy = normalized
|
||||||
|
default:
|
||||||
|
log.Printf("[OpsWS] invalid %s=%q (expected %q or %q); using default=%q", envOpsWSOriginPolicy, v, OriginPolicyStrict, OriginPolicyPermissive, cfg.OriginPolicy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOpsWSRuntimeLimitsFromEnv() opsWSRuntimeLimits {
|
||||||
|
cfg := opsWSRuntimeLimits{
|
||||||
|
MaxConns: defaultMaxWSConns,
|
||||||
|
MaxConnsPerIP: defaultMaxWSConnsPerIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := strings.TrimSpace(os.Getenv(envOpsWSMaxConns)); v != "" {
|
||||||
|
if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
|
||||||
|
cfg.MaxConns = int32(parsed)
|
||||||
|
} else {
|
||||||
|
log.Printf("[OpsWS] invalid %s=%q (expected int>0); using default=%d", envOpsWSMaxConns, v, cfg.MaxConns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(os.Getenv(envOpsWSMaxConnsPerIP)); v != "" {
|
||||||
|
if parsed, err := strconv.Atoi(v); err == nil && parsed >= 0 {
|
||||||
|
cfg.MaxConnsPerIP = int32(parsed)
|
||||||
|
} else {
|
||||||
|
log.Printf("[OpsWS] invalid %s=%q (expected int>=0); using default=%d", envOpsWSMaxConnsPerIP, v, cfg.MaxConnsPerIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTrustedProxies() []netip.Prefix {
|
||||||
|
prefixes, _ := parseTrustedProxyList("127.0.0.0/8,::1/128")
|
||||||
|
return prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTrustedProxyList(raw string) (prefixes []netip.Prefix, invalid []string) {
|
||||||
|
for _, token := range strings.Split(raw, ",") {
|
||||||
|
item := strings.TrimSpace(token)
|
||||||
|
if item == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
p netip.Prefix
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if strings.Contains(item, "/") {
|
||||||
|
p, err = netip.ParsePrefix(item)
|
||||||
|
} else {
|
||||||
|
var addr netip.Addr
|
||||||
|
addr, err = netip.ParseAddr(item)
|
||||||
|
if err == nil {
|
||||||
|
addr = addr.Unmap()
|
||||||
|
bits := 128
|
||||||
|
if addr.Is4() {
|
||||||
|
bits = 32
|
||||||
|
}
|
||||||
|
p = netip.PrefixFrom(addr, bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil || !p.IsValid() {
|
||||||
|
invalid = append(invalid, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes = append(prefixes, p.Masked())
|
||||||
|
}
|
||||||
|
return prefixes, invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostWithoutPort(hostport string) string {
|
||||||
|
hostport = strings.TrimSpace(hostport)
|
||||||
|
if hostport == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if host, _, err := net.SplitHostPort(hostport); err == nil {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(hostport, "[") && strings.HasSuffix(hostport, "]") {
|
||||||
|
return strings.Trim(hostport, "[]")
|
||||||
|
}
|
||||||
|
parts := strings.Split(hostport, ":")
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
@@ -196,6 +196,28 @@ func (h *ProxyHandler) Delete(c *gin.Context) {
|
|||||||
response.Success(c, gin.H{"message": "Proxy deleted successfully"})
|
response.Success(c, gin.H{"message": "Proxy deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BatchDelete handles batch deleting proxies
|
||||||
|
// POST /api/v1/admin/proxies/batch-delete
|
||||||
|
func (h *ProxyHandler) BatchDelete(c *gin.Context) {
|
||||||
|
type BatchDeleteRequest struct {
|
||||||
|
IDs []int64 `json:"ids" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req BatchDeleteRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.adminService.BatchDeleteProxies(c.Request.Context(), req.IDs)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
// Test handles testing proxy connectivity
|
// Test handles testing proxy connectivity
|
||||||
// POST /api/v1/admin/proxies/:id/test
|
// POST /api/v1/admin/proxies/:id/test
|
||||||
func (h *ProxyHandler) Test(c *gin.Context) {
|
func (h *ProxyHandler) Test(c *gin.Context) {
|
||||||
@@ -243,19 +265,17 @@ func (h *ProxyHandler) GetProxyAccounts(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page, pageSize := response.ParsePagination(c)
|
accounts, err := h.adminService.GetProxyAccounts(c.Request.Context(), proxyID)
|
||||||
|
|
||||||
accounts, total, err := h.adminService.GetProxyAccounts(c.Request.Context(), proxyID, page, pageSize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.Account, 0, len(accounts))
|
out := make([]dto.ProxyAccountSummary, 0, len(accounts))
|
||||||
for i := range accounts {
|
for i := range accounts {
|
||||||
out = append(out, *dto.AccountFromService(&accounts[i]))
|
out = append(out, *dto.ProxyAccountSummaryFromService(&accounts[i]))
|
||||||
}
|
}
|
||||||
response.Paginated(c, out, total, page, pageSize)
|
response.Success(c, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchCreateProxyItem represents a single proxy in batch create request
|
// BatchCreateProxyItem represents a single proxy in batch create request
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ func (h *RedeemHandler) List(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.RedeemCode, 0, len(codes))
|
out := make([]dto.AdminRedeemCode, 0, len(codes))
|
||||||
for i := range codes {
|
for i := range codes {
|
||||||
out = append(out, *dto.RedeemCodeFromService(&codes[i]))
|
out = append(out, *dto.RedeemCodeFromServiceAdmin(&codes[i]))
|
||||||
}
|
}
|
||||||
response.Paginated(c, out, total, page, pageSize)
|
response.Paginated(c, out, total, page, pageSize)
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func (h *RedeemHandler) GetByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.RedeemCodeFromService(code))
|
response.Success(c, dto.RedeemCodeFromServiceAdmin(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate handles generating new redeem codes
|
// Generate handles generating new redeem codes
|
||||||
@@ -100,9 +100,9 @@ func (h *RedeemHandler) Generate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.RedeemCode, 0, len(codes))
|
out := make([]dto.AdminRedeemCode, 0, len(codes))
|
||||||
for i := range codes {
|
for i := range codes {
|
||||||
out = append(out, *dto.RedeemCodeFromService(&codes[i]))
|
out = append(out, *dto.RedeemCodeFromServiceAdmin(&codes[i]))
|
||||||
}
|
}
|
||||||
response.Success(c, out)
|
response.Success(c, out)
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ func (h *RedeemHandler) Expire(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.RedeemCodeFromService(code))
|
response.Success(c, dto.RedeemCodeFromServiceAdmin(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStats handles getting redeem code statistics
|
// GetStats handles getting redeem code statistics
|
||||||
|
|||||||
@@ -19,14 +19,16 @@ type SettingHandler struct {
|
|||||||
settingService *service.SettingService
|
settingService *service.SettingService
|
||||||
emailService *service.EmailService
|
emailService *service.EmailService
|
||||||
turnstileService *service.TurnstileService
|
turnstileService *service.TurnstileService
|
||||||
|
opsService *service.OpsService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSettingHandler 创建系统设置处理器
|
// NewSettingHandler 创建系统设置处理器
|
||||||
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService) *SettingHandler {
|
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService) *SettingHandler {
|
||||||
return &SettingHandler{
|
return &SettingHandler{
|
||||||
settingService: settingService,
|
settingService: settingService,
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
turnstileService: turnstileService,
|
turnstileService: turnstileService,
|
||||||
|
opsService: opsService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +41,16 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if ops monitoring is enabled (respects config.ops.enabled)
|
||||||
|
opsEnabled := h.opsService != nil && h.opsService.IsMonitoringEnabled(c.Request.Context())
|
||||||
|
|
||||||
response.Success(c, dto.SystemSettings{
|
response.Success(c, dto.SystemSettings{
|
||||||
RegistrationEnabled: settings.RegistrationEnabled,
|
RegistrationEnabled: settings.RegistrationEnabled,
|
||||||
EmailVerifyEnabled: settings.EmailVerifyEnabled,
|
EmailVerifyEnabled: settings.EmailVerifyEnabled,
|
||||||
|
PromoCodeEnabled: settings.PromoCodeEnabled,
|
||||||
|
PasswordResetEnabled: settings.PasswordResetEnabled,
|
||||||
|
TotpEnabled: settings.TotpEnabled,
|
||||||
|
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
||||||
SMTPHost: settings.SMTPHost,
|
SMTPHost: settings.SMTPHost,
|
||||||
SMTPPort: settings.SMTPPort,
|
SMTPPort: settings.SMTPPort,
|
||||||
SMTPUsername: settings.SMTPUsername,
|
SMTPUsername: settings.SMTPUsername,
|
||||||
@@ -63,6 +72,9 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
ContactInfo: settings.ContactInfo,
|
ContactInfo: settings.ContactInfo,
|
||||||
DocURL: settings.DocURL,
|
DocURL: settings.DocURL,
|
||||||
HomeContent: settings.HomeContent,
|
HomeContent: settings.HomeContent,
|
||||||
|
HideCcsImportButton: settings.HideCcsImportButton,
|
||||||
|
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
|
||||||
|
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||||
DefaultConcurrency: settings.DefaultConcurrency,
|
DefaultConcurrency: settings.DefaultConcurrency,
|
||||||
DefaultBalance: settings.DefaultBalance,
|
DefaultBalance: settings.DefaultBalance,
|
||||||
EnableModelFallback: settings.EnableModelFallback,
|
EnableModelFallback: settings.EnableModelFallback,
|
||||||
@@ -72,14 +84,21 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
FallbackModelAntigravity: settings.FallbackModelAntigravity,
|
FallbackModelAntigravity: settings.FallbackModelAntigravity,
|
||||||
EnableIdentityPatch: settings.EnableIdentityPatch,
|
EnableIdentityPatch: settings.EnableIdentityPatch,
|
||||||
IdentityPatchPrompt: settings.IdentityPatchPrompt,
|
IdentityPatchPrompt: settings.IdentityPatchPrompt,
|
||||||
|
OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
|
||||||
|
OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
|
||||||
|
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
||||||
|
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSettingsRequest 更新设置请求
|
// UpdateSettingsRequest 更新设置请求
|
||||||
type UpdateSettingsRequest struct {
|
type UpdateSettingsRequest struct {
|
||||||
// 注册设置
|
// 注册设置
|
||||||
RegistrationEnabled bool `json:"registration_enabled"`
|
RegistrationEnabled bool `json:"registration_enabled"`
|
||||||
EmailVerifyEnabled bool `json:"email_verify_enabled"`
|
EmailVerifyEnabled bool `json:"email_verify_enabled"`
|
||||||
|
PromoCodeEnabled bool `json:"promo_code_enabled"`
|
||||||
|
PasswordResetEnabled bool `json:"password_reset_enabled"`
|
||||||
|
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
|
||||||
|
|
||||||
// 邮件服务设置
|
// 邮件服务设置
|
||||||
SMTPHost string `json:"smtp_host"`
|
SMTPHost string `json:"smtp_host"`
|
||||||
@@ -95,20 +114,23 @@ type UpdateSettingsRequest struct {
|
|||||||
TurnstileSiteKey string `json:"turnstile_site_key"`
|
TurnstileSiteKey string `json:"turnstile_site_key"`
|
||||||
TurnstileSecretKey string `json:"turnstile_secret_key"`
|
TurnstileSecretKey string `json:"turnstile_secret_key"`
|
||||||
|
|
||||||
// LinuxDo Connect OAuth 登录(终端用户 SSO)
|
// LinuxDo Connect OAuth 登录
|
||||||
LinuxDoConnectEnabled bool `json:"linuxdo_connect_enabled"`
|
LinuxDoConnectEnabled bool `json:"linuxdo_connect_enabled"`
|
||||||
LinuxDoConnectClientID string `json:"linuxdo_connect_client_id"`
|
LinuxDoConnectClientID string `json:"linuxdo_connect_client_id"`
|
||||||
LinuxDoConnectClientSecret string `json:"linuxdo_connect_client_secret"`
|
LinuxDoConnectClientSecret string `json:"linuxdo_connect_client_secret"`
|
||||||
LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"`
|
LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"`
|
||||||
|
|
||||||
// OEM设置
|
// OEM设置
|
||||||
SiteName string `json:"site_name"`
|
SiteName string `json:"site_name"`
|
||||||
SiteLogo string `json:"site_logo"`
|
SiteLogo string `json:"site_logo"`
|
||||||
SiteSubtitle string `json:"site_subtitle"`
|
SiteSubtitle string `json:"site_subtitle"`
|
||||||
APIBaseURL string `json:"api_base_url"`
|
APIBaseURL string `json:"api_base_url"`
|
||||||
ContactInfo string `json:"contact_info"`
|
ContactInfo string `json:"contact_info"`
|
||||||
DocURL string `json:"doc_url"`
|
DocURL string `json:"doc_url"`
|
||||||
HomeContent string `json:"home_content"`
|
HomeContent string `json:"home_content"`
|
||||||
|
HideCcsImportButton bool `json:"hide_ccs_import_button"`
|
||||||
|
PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"`
|
||||||
|
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
||||||
|
|
||||||
// 默认配置
|
// 默认配置
|
||||||
DefaultConcurrency int `json:"default_concurrency"`
|
DefaultConcurrency int `json:"default_concurrency"`
|
||||||
@@ -124,6 +146,12 @@ type UpdateSettingsRequest struct {
|
|||||||
// Identity patch configuration (Claude -> Gemini)
|
// Identity patch configuration (Claude -> Gemini)
|
||||||
EnableIdentityPatch bool `json:"enable_identity_patch"`
|
EnableIdentityPatch bool `json:"enable_identity_patch"`
|
||||||
IdentityPatchPrompt string `json:"identity_patch_prompt"`
|
IdentityPatchPrompt string `json:"identity_patch_prompt"`
|
||||||
|
|
||||||
|
// Ops monitoring (vNext)
|
||||||
|
OpsMonitoringEnabled *bool `json:"ops_monitoring_enabled"`
|
||||||
|
OpsRealtimeMonitoringEnabled *bool `json:"ops_realtime_monitoring_enabled"`
|
||||||
|
OpsQueryModeDefault *string `json:"ops_query_mode_default"`
|
||||||
|
OpsMetricsIntervalSeconds *int `json:"ops_metrics_interval_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSettings 更新系统设置
|
// UpdateSettings 更新系统设置
|
||||||
@@ -179,6 +207,16 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TOTP 双因素认证参数验证
|
||||||
|
// 只有手动配置了加密密钥才允许启用 TOTP 功能
|
||||||
|
if req.TotpEnabled && !previousSettings.TotpEnabled {
|
||||||
|
// 尝试启用 TOTP,检查加密密钥是否已手动配置
|
||||||
|
if !h.settingService.IsTotpEncryptionKeyConfigured() {
|
||||||
|
response.BadRequest(c, "Cannot enable TOTP: TOTP_ENCRYPTION_KEY environment variable must be configured first. Generate a key with 'openssl rand -hex 32' and set it in your environment.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LinuxDo Connect 参数验证
|
// LinuxDo Connect 参数验证
|
||||||
if req.LinuxDoConnectEnabled {
|
if req.LinuxDoConnectEnabled {
|
||||||
req.LinuxDoConnectClientID = strings.TrimSpace(req.LinuxDoConnectClientID)
|
req.LinuxDoConnectClientID = strings.TrimSpace(req.LinuxDoConnectClientID)
|
||||||
@@ -208,39 +246,109 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// “购买订阅”页面配置验证
|
||||||
|
purchaseEnabled := previousSettings.PurchaseSubscriptionEnabled
|
||||||
|
if req.PurchaseSubscriptionEnabled != nil {
|
||||||
|
purchaseEnabled = *req.PurchaseSubscriptionEnabled
|
||||||
|
}
|
||||||
|
purchaseURL := previousSettings.PurchaseSubscriptionURL
|
||||||
|
if req.PurchaseSubscriptionURL != nil {
|
||||||
|
purchaseURL = strings.TrimSpace(*req.PurchaseSubscriptionURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// - 启用时要求 URL 合法且非空
|
||||||
|
// - 禁用时允许为空;若提供了 URL 也做基本校验,避免误配置
|
||||||
|
if purchaseEnabled {
|
||||||
|
if purchaseURL == "" {
|
||||||
|
response.BadRequest(c, "Purchase Subscription URL is required when enabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := config.ValidateAbsoluteHTTPURL(purchaseURL); err != nil {
|
||||||
|
response.BadRequest(c, "Purchase Subscription URL must be an absolute http(s) URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if purchaseURL != "" {
|
||||||
|
if err := config.ValidateAbsoluteHTTPURL(purchaseURL); err != nil {
|
||||||
|
response.BadRequest(c, "Purchase Subscription URL must be an absolute http(s) URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ops metrics collector interval validation (seconds).
|
||||||
|
if req.OpsMetricsIntervalSeconds != nil {
|
||||||
|
v := *req.OpsMetricsIntervalSeconds
|
||||||
|
if v < 60 {
|
||||||
|
v = 60
|
||||||
|
}
|
||||||
|
if v > 3600 {
|
||||||
|
v = 3600
|
||||||
|
}
|
||||||
|
req.OpsMetricsIntervalSeconds = &v
|
||||||
|
}
|
||||||
|
|
||||||
settings := &service.SystemSettings{
|
settings := &service.SystemSettings{
|
||||||
RegistrationEnabled: req.RegistrationEnabled,
|
RegistrationEnabled: req.RegistrationEnabled,
|
||||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||||
SMTPHost: req.SMTPHost,
|
PromoCodeEnabled: req.PromoCodeEnabled,
|
||||||
SMTPPort: req.SMTPPort,
|
PasswordResetEnabled: req.PasswordResetEnabled,
|
||||||
SMTPUsername: req.SMTPUsername,
|
TotpEnabled: req.TotpEnabled,
|
||||||
SMTPPassword: req.SMTPPassword,
|
SMTPHost: req.SMTPHost,
|
||||||
SMTPFrom: req.SMTPFrom,
|
SMTPPort: req.SMTPPort,
|
||||||
SMTPFromName: req.SMTPFromName,
|
SMTPUsername: req.SMTPUsername,
|
||||||
SMTPUseTLS: req.SMTPUseTLS,
|
SMTPPassword: req.SMTPPassword,
|
||||||
TurnstileEnabled: req.TurnstileEnabled,
|
SMTPFrom: req.SMTPFrom,
|
||||||
TurnstileSiteKey: req.TurnstileSiteKey,
|
SMTPFromName: req.SMTPFromName,
|
||||||
TurnstileSecretKey: req.TurnstileSecretKey,
|
SMTPUseTLS: req.SMTPUseTLS,
|
||||||
LinuxDoConnectEnabled: req.LinuxDoConnectEnabled,
|
TurnstileEnabled: req.TurnstileEnabled,
|
||||||
LinuxDoConnectClientID: req.LinuxDoConnectClientID,
|
TurnstileSiteKey: req.TurnstileSiteKey,
|
||||||
LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret,
|
TurnstileSecretKey: req.TurnstileSecretKey,
|
||||||
LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL,
|
LinuxDoConnectEnabled: req.LinuxDoConnectEnabled,
|
||||||
SiteName: req.SiteName,
|
LinuxDoConnectClientID: req.LinuxDoConnectClientID,
|
||||||
SiteLogo: req.SiteLogo,
|
LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret,
|
||||||
SiteSubtitle: req.SiteSubtitle,
|
LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL,
|
||||||
APIBaseURL: req.APIBaseURL,
|
SiteName: req.SiteName,
|
||||||
ContactInfo: req.ContactInfo,
|
SiteLogo: req.SiteLogo,
|
||||||
DocURL: req.DocURL,
|
SiteSubtitle: req.SiteSubtitle,
|
||||||
HomeContent: req.HomeContent,
|
APIBaseURL: req.APIBaseURL,
|
||||||
DefaultConcurrency: req.DefaultConcurrency,
|
ContactInfo: req.ContactInfo,
|
||||||
DefaultBalance: req.DefaultBalance,
|
DocURL: req.DocURL,
|
||||||
EnableModelFallback: req.EnableModelFallback,
|
HomeContent: req.HomeContent,
|
||||||
FallbackModelAnthropic: req.FallbackModelAnthropic,
|
HideCcsImportButton: req.HideCcsImportButton,
|
||||||
FallbackModelOpenAI: req.FallbackModelOpenAI,
|
PurchaseSubscriptionEnabled: purchaseEnabled,
|
||||||
FallbackModelGemini: req.FallbackModelGemini,
|
PurchaseSubscriptionURL: purchaseURL,
|
||||||
FallbackModelAntigravity: req.FallbackModelAntigravity,
|
DefaultConcurrency: req.DefaultConcurrency,
|
||||||
EnableIdentityPatch: req.EnableIdentityPatch,
|
DefaultBalance: req.DefaultBalance,
|
||||||
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
EnableModelFallback: req.EnableModelFallback,
|
||||||
|
FallbackModelAnthropic: req.FallbackModelAnthropic,
|
||||||
|
FallbackModelOpenAI: req.FallbackModelOpenAI,
|
||||||
|
FallbackModelGemini: req.FallbackModelGemini,
|
||||||
|
FallbackModelAntigravity: req.FallbackModelAntigravity,
|
||||||
|
EnableIdentityPatch: req.EnableIdentityPatch,
|
||||||
|
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
||||||
|
OpsMonitoringEnabled: func() bool {
|
||||||
|
if req.OpsMonitoringEnabled != nil {
|
||||||
|
return *req.OpsMonitoringEnabled
|
||||||
|
}
|
||||||
|
return previousSettings.OpsMonitoringEnabled
|
||||||
|
}(),
|
||||||
|
OpsRealtimeMonitoringEnabled: func() bool {
|
||||||
|
if req.OpsRealtimeMonitoringEnabled != nil {
|
||||||
|
return *req.OpsRealtimeMonitoringEnabled
|
||||||
|
}
|
||||||
|
return previousSettings.OpsRealtimeMonitoringEnabled
|
||||||
|
}(),
|
||||||
|
OpsQueryModeDefault: func() string {
|
||||||
|
if req.OpsQueryModeDefault != nil {
|
||||||
|
return *req.OpsQueryModeDefault
|
||||||
|
}
|
||||||
|
return previousSettings.OpsQueryModeDefault
|
||||||
|
}(),
|
||||||
|
OpsMetricsIntervalSeconds: func() int {
|
||||||
|
if req.OpsMetricsIntervalSeconds != nil {
|
||||||
|
return *req.OpsMetricsIntervalSeconds
|
||||||
|
}
|
||||||
|
return previousSettings.OpsMetricsIntervalSeconds
|
||||||
|
}(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
||||||
@@ -260,6 +368,10 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
response.Success(c, dto.SystemSettings{
|
response.Success(c, dto.SystemSettings{
|
||||||
RegistrationEnabled: updatedSettings.RegistrationEnabled,
|
RegistrationEnabled: updatedSettings.RegistrationEnabled,
|
||||||
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
|
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
|
||||||
|
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
|
||||||
|
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
|
||||||
|
TotpEnabled: updatedSettings.TotpEnabled,
|
||||||
|
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
||||||
SMTPHost: updatedSettings.SMTPHost,
|
SMTPHost: updatedSettings.SMTPHost,
|
||||||
SMTPPort: updatedSettings.SMTPPort,
|
SMTPPort: updatedSettings.SMTPPort,
|
||||||
SMTPUsername: updatedSettings.SMTPUsername,
|
SMTPUsername: updatedSettings.SMTPUsername,
|
||||||
@@ -281,6 +393,9 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
ContactInfo: updatedSettings.ContactInfo,
|
ContactInfo: updatedSettings.ContactInfo,
|
||||||
DocURL: updatedSettings.DocURL,
|
DocURL: updatedSettings.DocURL,
|
||||||
HomeContent: updatedSettings.HomeContent,
|
HomeContent: updatedSettings.HomeContent,
|
||||||
|
HideCcsImportButton: updatedSettings.HideCcsImportButton,
|
||||||
|
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
|
||||||
|
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
|
||||||
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
||||||
DefaultBalance: updatedSettings.DefaultBalance,
|
DefaultBalance: updatedSettings.DefaultBalance,
|
||||||
EnableModelFallback: updatedSettings.EnableModelFallback,
|
EnableModelFallback: updatedSettings.EnableModelFallback,
|
||||||
@@ -290,6 +405,10 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
FallbackModelAntigravity: updatedSettings.FallbackModelAntigravity,
|
FallbackModelAntigravity: updatedSettings.FallbackModelAntigravity,
|
||||||
EnableIdentityPatch: updatedSettings.EnableIdentityPatch,
|
EnableIdentityPatch: updatedSettings.EnableIdentityPatch,
|
||||||
IdentityPatchPrompt: updatedSettings.IdentityPatchPrompt,
|
IdentityPatchPrompt: updatedSettings.IdentityPatchPrompt,
|
||||||
|
OpsMonitoringEnabled: updatedSettings.OpsMonitoringEnabled,
|
||||||
|
OpsRealtimeMonitoringEnabled: updatedSettings.OpsRealtimeMonitoringEnabled,
|
||||||
|
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
|
||||||
|
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +440,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.EmailVerifyEnabled != after.EmailVerifyEnabled {
|
if before.EmailVerifyEnabled != after.EmailVerifyEnabled {
|
||||||
changed = append(changed, "email_verify_enabled")
|
changed = append(changed, "email_verify_enabled")
|
||||||
}
|
}
|
||||||
|
if before.PasswordResetEnabled != after.PasswordResetEnabled {
|
||||||
|
changed = append(changed, "password_reset_enabled")
|
||||||
|
}
|
||||||
|
if before.TotpEnabled != after.TotpEnabled {
|
||||||
|
changed = append(changed, "totp_enabled")
|
||||||
|
}
|
||||||
if before.SMTPHost != after.SMTPHost {
|
if before.SMTPHost != after.SMTPHost {
|
||||||
changed = append(changed, "smtp_host")
|
changed = append(changed, "smtp_host")
|
||||||
}
|
}
|
||||||
@@ -384,6 +509,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.HomeContent != after.HomeContent {
|
if before.HomeContent != after.HomeContent {
|
||||||
changed = append(changed, "home_content")
|
changed = append(changed, "home_content")
|
||||||
}
|
}
|
||||||
|
if before.HideCcsImportButton != after.HideCcsImportButton {
|
||||||
|
changed = append(changed, "hide_ccs_import_button")
|
||||||
|
}
|
||||||
if before.DefaultConcurrency != after.DefaultConcurrency {
|
if before.DefaultConcurrency != after.DefaultConcurrency {
|
||||||
changed = append(changed, "default_concurrency")
|
changed = append(changed, "default_concurrency")
|
||||||
}
|
}
|
||||||
@@ -411,6 +539,18 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.IdentityPatchPrompt != after.IdentityPatchPrompt {
|
if before.IdentityPatchPrompt != after.IdentityPatchPrompt {
|
||||||
changed = append(changed, "identity_patch_prompt")
|
changed = append(changed, "identity_patch_prompt")
|
||||||
}
|
}
|
||||||
|
if before.OpsMonitoringEnabled != after.OpsMonitoringEnabled {
|
||||||
|
changed = append(changed, "ops_monitoring_enabled")
|
||||||
|
}
|
||||||
|
if before.OpsRealtimeMonitoringEnabled != after.OpsRealtimeMonitoringEnabled {
|
||||||
|
changed = append(changed, "ops_realtime_monitoring_enabled")
|
||||||
|
}
|
||||||
|
if before.OpsQueryModeDefault != after.OpsQueryModeDefault {
|
||||||
|
changed = append(changed, "ops_query_mode_default")
|
||||||
|
}
|
||||||
|
if before.OpsMetricsIntervalSeconds != after.OpsMetricsIntervalSeconds {
|
||||||
|
changed = append(changed, "ops_metrics_interval_seconds")
|
||||||
|
}
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,3 +727,68 @@ func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) {
|
|||||||
|
|
||||||
response.Success(c, gin.H{"message": "Admin API key deleted"})
|
response.Success(c, gin.H{"message": "Admin API key deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStreamTimeoutSettings 获取流超时处理配置
|
||||||
|
// GET /api/v1/admin/settings/stream-timeout
|
||||||
|
func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
|
||||||
|
settings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.StreamTimeoutSettings{
|
||||||
|
Enabled: settings.Enabled,
|
||||||
|
Action: settings.Action,
|
||||||
|
TempUnschedMinutes: settings.TempUnschedMinutes,
|
||||||
|
ThresholdCount: settings.ThresholdCount,
|
||||||
|
ThresholdWindowMinutes: settings.ThresholdWindowMinutes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
|
||||||
|
type UpdateStreamTimeoutSettingsRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
TempUnschedMinutes int `json:"temp_unsched_minutes"`
|
||||||
|
ThresholdCount int `json:"threshold_count"`
|
||||||
|
ThresholdWindowMinutes int `json:"threshold_window_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStreamTimeoutSettings 更新流超时处理配置
|
||||||
|
// PUT /api/v1/admin/settings/stream-timeout
|
||||||
|
func (h *SettingHandler) UpdateStreamTimeoutSettings(c *gin.Context) {
|
||||||
|
var req UpdateStreamTimeoutSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := &service.StreamTimeoutSettings{
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
Action: req.Action,
|
||||||
|
TempUnschedMinutes: req.TempUnschedMinutes,
|
||||||
|
ThresholdCount: req.ThresholdCount,
|
||||||
|
ThresholdWindowMinutes: req.ThresholdWindowMinutes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.settingService.SetStreamTimeoutSettings(c.Request.Context(), settings); err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新获取设置返回
|
||||||
|
updatedSettings, err := h.settingService.GetStreamTimeoutSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.StreamTimeoutSettings{
|
||||||
|
Enabled: updatedSettings.Enabled,
|
||||||
|
Action: updatedSettings.Action,
|
||||||
|
TempUnschedMinutes: updatedSettings.TempUnschedMinutes,
|
||||||
|
ThresholdCount: updatedSettings.ThresholdCount,
|
||||||
|
ThresholdWindowMinutes: updatedSettings.ThresholdWindowMinutes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ type BulkAssignSubscriptionRequest struct {
|
|||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendSubscriptionRequest represents extend subscription request
|
// AdjustSubscriptionRequest represents adjust subscription request (extend or shorten)
|
||||||
type ExtendSubscriptionRequest struct {
|
type AdjustSubscriptionRequest struct {
|
||||||
Days int `json:"days" binding:"required,min=1,max=36500"` // max 100 years
|
Days int `json:"days" binding:"required,min=-36500,max=36500"` // negative to shorten, positive to extend
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handles listing all subscriptions with pagination and filters
|
// List handles listing all subscriptions with pagination and filters
|
||||||
@@ -77,15 +77,19 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
|
|
||||||
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status)
|
// Parse sorting parameters
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "created_at")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
|
|
||||||
|
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.UserSubscription, 0, len(subscriptions))
|
out := make([]dto.AdminUserSubscription, 0, len(subscriptions))
|
||||||
for i := range subscriptions {
|
for i := range subscriptions {
|
||||||
out = append(out, *dto.UserSubscriptionFromService(&subscriptions[i]))
|
out = append(out, *dto.UserSubscriptionFromServiceAdmin(&subscriptions[i]))
|
||||||
}
|
}
|
||||||
response.PaginatedWithResult(c, out, toResponsePagination(pagination))
|
response.PaginatedWithResult(c, out, toResponsePagination(pagination))
|
||||||
}
|
}
|
||||||
@@ -105,7 +109,7 @@ func (h *SubscriptionHandler) GetByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.UserSubscriptionFromService(subscription))
|
response.Success(c, dto.UserSubscriptionFromServiceAdmin(subscription))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProgress handles getting subscription usage progress
|
// GetProgress handles getting subscription usage progress
|
||||||
@@ -150,7 +154,7 @@ func (h *SubscriptionHandler) Assign(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.UserSubscriptionFromService(subscription))
|
response.Success(c, dto.UserSubscriptionFromServiceAdmin(subscription))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BulkAssign handles bulk assigning subscriptions to multiple users
|
// BulkAssign handles bulk assigning subscriptions to multiple users
|
||||||
@@ -180,7 +184,7 @@ func (h *SubscriptionHandler) BulkAssign(c *gin.Context) {
|
|||||||
response.Success(c, dto.BulkAssignResultFromService(result))
|
response.Success(c, dto.BulkAssignResultFromService(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend handles extending a subscription
|
// Extend handles adjusting a subscription (extend or shorten)
|
||||||
// POST /api/v1/admin/subscriptions/:id/extend
|
// POST /api/v1/admin/subscriptions/:id/extend
|
||||||
func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
||||||
subscriptionID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
subscriptionID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
@@ -189,7 +193,7 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req ExtendSubscriptionRequest
|
var req AdjustSubscriptionRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
return
|
return
|
||||||
@@ -201,7 +205,7 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, dto.UserSubscriptionFromService(subscription))
|
response.Success(c, dto.UserSubscriptionFromServiceAdmin(subscription))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revoke handles revoking a subscription
|
// Revoke handles revoking a subscription
|
||||||
@@ -239,9 +243,9 @@ func (h *SubscriptionHandler) ListByGroup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.UserSubscription, 0, len(subscriptions))
|
out := make([]dto.AdminUserSubscription, 0, len(subscriptions))
|
||||||
for i := range subscriptions {
|
for i := range subscriptions {
|
||||||
out = append(out, *dto.UserSubscriptionFromService(&subscriptions[i]))
|
out = append(out, *dto.UserSubscriptionFromServiceAdmin(&subscriptions[i]))
|
||||||
}
|
}
|
||||||
response.PaginatedWithResult(c, out, toResponsePagination(pagination))
|
response.PaginatedWithResult(c, out, toResponsePagination(pagination))
|
||||||
}
|
}
|
||||||
@@ -261,9 +265,9 @@ func (h *SubscriptionHandler) ListByUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]dto.UserSubscription, 0, len(subscriptions))
|
out := make([]dto.AdminUserSubscription, 0, len(subscriptions))
|
||||||
for i := range subscriptions {
|
for i := range subscriptions {
|
||||||
out = append(out, *dto.UserSubscriptionFromService(&subscriptions[i]))
|
out = append(out, *dto.UserSubscriptionFromServiceAdmin(&subscriptions[i]))
|
||||||
}
|
}
|
||||||
response.Success(c, out)
|
response.Success(c, out)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user