mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-05 05:30:44 +08:00
Compare commits
668 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf45581104 | ||
|
|
e88b2890d1 | ||
|
|
1b5ae71d1f | ||
|
|
d4ff835bf1 | ||
|
|
e27b0adbc8 | ||
|
|
e59fa8637a | ||
|
|
58f758c816 | ||
|
|
feb6999d9a | ||
|
|
71f61bbc47 | ||
|
|
6d3ea64a35 | ||
|
|
1fca2bfab1 | ||
|
|
ce41afb756 | ||
|
|
b4a42a640d | ||
|
|
58b26cb4c8 | ||
|
|
b453c32743 | ||
|
|
3cd398b098 | ||
|
|
d3127b8eb1 | ||
|
|
6de1d0cb33 | ||
|
|
6c718578a5 | ||
|
|
0d241d52eb | ||
|
|
212eaa3a05 | ||
|
|
f3ab3fe5e2 | ||
|
|
b8c56ff940 | ||
|
|
38da737e6c | ||
|
|
1b2ea7a1df | ||
|
|
a9e5fc8539 | ||
|
|
9b213115e7 | ||
|
|
5534347328 | ||
|
|
2355029dc1 | ||
|
|
8d25335b01 | ||
|
|
c0b5900a37 | ||
|
|
35a9290528 | ||
|
|
c9145ad4d8 | ||
|
|
3851628a43 | ||
|
|
d72ac92694 | ||
|
|
2555951be4 | ||
|
|
669bff78c4 | ||
|
|
c90d1f2527 | ||
|
|
40cebc250f | ||
|
|
ddd495fb48 | ||
|
|
58f2044637 | ||
|
|
dfe3fdc1cc | ||
|
|
705131e172 | ||
|
|
88759407c7 | ||
|
|
6c99cc611c | ||
|
|
3457bcbfcd | ||
|
|
eb385457b2 | ||
|
|
4ea8b4cb4f | ||
|
|
91bdcf8994 | ||
|
|
8d03c52e15 | ||
|
|
0fbc9a44d3 | ||
|
|
632035aabd | ||
|
|
a51e0047b7 | ||
|
|
726730bb0e | ||
|
|
faff1771c4 | ||
|
|
b06cd06ec1 | ||
|
|
95751d8009 | ||
|
|
14e565a004 | ||
|
|
ce694701a9 | ||
|
|
12d03e4030 | ||
|
|
0b1ce6be8f | ||
|
|
28a6adaaa4 | ||
|
|
36990a0514 | ||
|
|
ebac0dc628 | ||
|
|
29d58f2414 | ||
|
|
dca0054e93 | ||
|
|
983fe58959 | ||
|
|
91c9b8d062 | ||
|
|
b384570de3 | ||
|
|
0507852a34 | ||
|
|
7b6ff135fb | ||
|
|
bf24de88ed | ||
|
|
ff6d4ab39a | ||
|
|
66fde7a2e6 | ||
|
|
e8efaa4cd9 | ||
|
|
00947d6492 | ||
|
|
cf70fb1b4e | ||
|
|
ef1a992cf0 | ||
|
|
1f6a73f0db | ||
|
|
f2e596f6ec | ||
|
|
b155bc564b | ||
|
|
055c48ab33 | ||
|
|
6663e1eda6 | ||
|
|
649afef512 | ||
|
|
4514f3fc11 | ||
|
|
095bef9554 | ||
|
|
83a16dec19 | ||
|
|
820c531814 | ||
|
|
1727b8df3b | ||
|
|
a025a15f5d | ||
|
|
72e5876c64 | ||
|
|
aeed2eb9ad | ||
|
|
46bc5ca73b | ||
|
|
0b3feb9d4c | ||
|
|
ca8692c747 | ||
|
|
318aa5e0d3 | ||
|
|
1dfd974432 | ||
|
|
cc396f59cf | ||
|
|
aa8b9cc508 | ||
|
|
6a2cf09ee0 | ||
|
|
c6fd88116b | ||
|
|
8f0dbdeaba | ||
|
|
007c09b84e | ||
|
|
73f3c068ef | ||
|
|
9a92fa4a60 | ||
|
|
576af710be | ||
|
|
b5642bd068 | ||
|
|
128f322252 | ||
|
|
17d7e57a2e | ||
|
|
50288e6b01 | ||
|
|
ab3e44e4bd | ||
|
|
61607990c8 | ||
|
|
b65275235f | ||
|
|
e298a71834 | ||
|
|
3f6fa1e3db | ||
|
|
f2c2abe628 | ||
|
|
ff5b467fbe | ||
|
|
8c10941142 | ||
|
|
f5764d8dc6 | ||
|
|
81ca4f12dd | ||
|
|
941c469ab9 | ||
|
|
8fcd819e6f | ||
|
|
9abdaed20c | ||
|
|
eb94342f78 | ||
|
|
d563eb2336 | ||
|
|
3ee6f085db | ||
|
|
7cca69a136 | ||
|
|
093a5a260e | ||
|
|
b6d46fd52f | ||
|
|
2c072c0ed6 | ||
|
|
1f39bf8a78 | ||
|
|
fdd8499ffc | ||
|
|
9398ea7af5 | ||
|
|
29dce1a59c | ||
|
|
c729ee425f | ||
|
|
c489f23810 | ||
|
|
47a544230a | ||
|
|
c13c81f09d | ||
|
|
20544a4447 | ||
|
|
b688ebeefa | ||
|
|
1854050df3 | ||
|
|
c7f4a649df | ||
|
|
ef5c8e6839 | ||
|
|
d571f300e5 | ||
|
|
ce96527dd9 | ||
|
|
f8b8b53985 | ||
|
|
b20e142249 | ||
|
|
7c6dc9dda8 | ||
|
|
5875571215 | ||
|
|
975e6b1563 | ||
|
|
f6fd7c83e3 | ||
|
|
c2965c0fb0 | ||
|
|
fdad55956e | ||
|
|
bb399e56b0 | ||
|
|
fa68cbad1b | ||
|
|
995ef1348a | ||
|
|
0f03393010 | ||
|
|
4b1ffc23f5 | ||
|
|
c7137dffa8 | ||
|
|
5a3375ce52 | ||
|
|
8e834fd9f5 | ||
|
|
02046744eb | ||
|
|
68d7ec9155 | ||
|
|
7537dce0f0 | ||
|
|
5f41b74707 | ||
|
|
25d961d4e0 | ||
|
|
08c4e514f8 | ||
|
|
91b1d812ce | ||
|
|
1f05d9f79d | ||
|
|
9f8cffe887 | ||
|
|
995bee143a | ||
|
|
f10e56be7e | ||
|
|
2f8e10db46 | ||
|
|
5418e15e63 | ||
|
|
bcf84cc153 | ||
|
|
ce8520c9e6 | ||
|
|
0b3928c33e | ||
|
|
73d72651b4 | ||
|
|
adbedd488c | ||
|
|
13b72f6bc2 | ||
|
|
c5aa96a3aa | ||
|
|
d927c0e45f | ||
|
|
31660c4c5f | ||
|
|
4321adab71 | ||
|
|
68f151f5c0 | ||
|
|
ecad083ffc | ||
|
|
fee43e8474 | ||
|
|
4838ab74b3 | ||
|
|
fef9259aaa | ||
|
|
ad7c10727a | ||
|
|
ccd42c1d1a | ||
|
|
bd8eadb75b | ||
|
|
70a9d0d3a2 | ||
|
|
7cd3824863 | ||
|
|
db9021f9c1 | ||
|
|
a2418c6040 | ||
|
|
1fb29d59b7 | ||
|
|
8c4a217f03 | ||
|
|
bda7c39e55 | ||
|
|
3583283ebb | ||
|
|
4feacf2213 | ||
|
|
73eb731881 | ||
|
|
186e36752d | ||
|
|
421728a985 | ||
|
|
39a5701184 | ||
|
|
27948c777e | ||
|
|
c64ed46d05 | ||
|
|
c64465ff7e | ||
|
|
095200bd16 | ||
|
|
2c667a159c | ||
|
|
bac408044f | ||
|
|
4edcfe1f7c | ||
|
|
9259dcb6f5 | ||
|
|
7ef933c7cf | ||
|
|
7d312822c1 | ||
|
|
1b3e5c6ea6 | ||
|
|
efe8401e92 | ||
|
|
0b845c2532 | ||
|
|
fe60412a17 | ||
|
|
5c39e6f2fb | ||
|
|
a225a241d7 | ||
|
|
553a486d17 | ||
|
|
c73374a221 | ||
|
|
94e26dee4f | ||
|
|
4617ef2bb8 | ||
|
|
8afa8c1091 | ||
|
|
578608d301 | ||
|
|
0d45d8669e | ||
|
|
73708da60d | ||
|
|
c810cad7c8 | ||
|
|
94bba415b1 | ||
|
|
4f7629a4cb | ||
|
|
4015f31f28 | ||
|
|
9dccbe1b07 | ||
|
|
9a88df7f28 | ||
|
|
a47f622e7e | ||
|
|
3529148455 | ||
|
|
01d8286bd9 | ||
|
|
21b6f2d593 | ||
|
|
528ff5d28c | ||
|
|
ba7d2aecbb | ||
|
|
0236b97d49 | ||
|
|
26f6b1eeff | ||
|
|
dc447ccebe | ||
|
|
7ec29638f4 | ||
|
|
4c9562af20 | ||
|
|
71942fd322 | ||
|
|
550b979ac5 | ||
|
|
3878a5a46f | ||
|
|
e443a6a1ea | ||
|
|
963494ec6f | ||
|
|
42d73118fd | ||
|
|
f2f819d70f | ||
|
|
525cdb8830 | ||
|
|
a6764e82f2 | ||
|
|
1de18b89dd | ||
|
|
882518c111 | ||
|
|
8027531d07 | ||
|
|
30706355a4 | ||
|
|
dfe99507b8 | ||
|
|
c1717c9a6c | ||
|
|
1fd1a58a7a | ||
|
|
fad07507be | ||
|
|
a20c211162 | ||
|
|
9f6ab6b817 | ||
|
|
bf3d6c0e6e | ||
|
|
241023f3fc | ||
|
|
1292c44b41 | ||
|
|
b4fce47049 | ||
|
|
e7780cd8c8 | ||
|
|
af96c8ea53 | ||
|
|
7d26b81075 | ||
|
|
b8ada63ac3 | ||
|
|
cfaac12af1 | ||
|
|
6028efd26c | ||
|
|
62a566ef2c | ||
|
|
94419f434c | ||
|
|
21f349c032 | ||
|
|
28e36f7925 | ||
|
|
6c02076333 | ||
|
|
7414bdf0e3 | ||
|
|
e6326b2929 | ||
|
|
17cdcebd04 | ||
|
|
a14babdc73 | ||
|
|
aadc6a763a | ||
|
|
f16af8bf88 | ||
|
|
5ceaef4500 | ||
|
|
1ac7219a92 | ||
|
|
d4cc9871c4 | ||
|
|
961c30e7c0 | ||
|
|
13e85b3147 | ||
|
|
50a3c7fa0b | ||
|
|
bd9d2671d7 | ||
|
|
62b40636e0 | ||
|
|
eeff451bc5 | ||
|
|
56fcb20f94 | ||
|
|
7134266acf | ||
|
|
2e4ac88ad9 | ||
|
|
51547fa216 | ||
|
|
2005fc97a8 | ||
|
|
0772d9250e | ||
|
|
aa6047c460 | ||
|
|
045cba78b4 | ||
|
|
8989d0d4b6 | ||
|
|
c521117b99 | ||
|
|
e0f52a8ab8 | ||
|
|
6c23fadf7e | ||
|
|
869952d113 | ||
|
|
07ab051ee4 | ||
|
|
f2d98fc0c7 | ||
|
|
2b41cec840 | ||
|
|
6cf77040e7 | ||
|
|
20b70bc5fd | ||
|
|
4905e7193a | ||
|
|
9c1f4b8e72 | ||
|
|
9857c17631 | ||
|
|
7e34bb946f | ||
|
|
47b748851b | ||
|
|
a6f99cf534 | ||
|
|
a120a6bc32 | ||
|
|
d557d1a190 | ||
|
|
e0286e5085 | ||
|
|
4b41e898a4 | ||
|
|
668e164793 | ||
|
|
fa2e6188d0 | ||
|
|
7fde9ebbc2 | ||
|
|
aef7c3b9bb | ||
|
|
a0b76bd608 | ||
|
|
c1fab7f8d8 | ||
|
|
f42c8f2abe | ||
|
|
aa5846b282 | ||
|
|
594a0ade38 | ||
|
|
d45cc23171 | ||
|
|
d795734352 | ||
|
|
4da9fdd1d5 | ||
|
|
6b218caa21 | ||
|
|
5c138007d0 | ||
|
|
1acfc46f46 | ||
|
|
fbffb08aae | ||
|
|
8640a62319 | ||
|
|
fa782e70a4 | ||
|
|
afd72abc6e | ||
|
|
71f72e167e | ||
|
|
6595c7601e | ||
|
|
67c0506290 | ||
|
|
6447be4534 | ||
|
|
3741617ebd | ||
|
|
ab4e8b2cf0 | ||
|
|
474165d7aa | ||
|
|
94e067a2e2 | ||
|
|
4293c89166 | ||
|
|
ec82c37da5 | ||
|
|
552a4b998a | ||
|
|
0d2061b268 | ||
|
|
8a260defc2 | ||
|
|
e14c87597a | ||
|
|
f3f19d35aa | ||
|
|
ced90e1d84 | ||
|
|
17e4033340 | ||
|
|
044d3a013d | ||
|
|
1fc9dd7b68 | ||
|
|
8147866c09 | ||
|
|
7bd1972f94 | ||
|
|
2c9dcfe27b | ||
|
|
1b79b0f3ff | ||
|
|
c637e6cf31 | ||
|
|
d3a9f5bb88 | ||
|
|
7eb0415a8a | ||
|
|
bdbc8fa08f | ||
|
|
63f3af0f94 | ||
|
|
686f890fbf | ||
|
|
220fbe6544 | ||
|
|
ae44a94325 | ||
|
|
3718d6dcd4 | ||
|
|
90b3838173 | ||
|
|
19d3ecc76f | ||
|
|
6fba4ebb13 | ||
|
|
c31974c913 | ||
|
|
6177fa5dd8 | ||
|
|
cfe72159d0 | ||
|
|
8321e4a647 | ||
|
|
3084330d0c | ||
|
|
b566649e79 | ||
|
|
10a6180e4a | ||
|
|
cbe9e78977 | ||
|
|
74145b1f39 | ||
|
|
359e56751b | ||
|
|
5899784aa4 | ||
|
|
9e8959c56d | ||
|
|
1bff2292a6 | ||
|
|
cf9247754e | ||
|
|
eefab15958 | ||
|
|
0e23732631 | ||
|
|
37c044fb4b | ||
|
|
6da5fa01b9 | ||
|
|
616930f9d3 | ||
|
|
b9c31fa7c4 | ||
|
|
17b339972c | ||
|
|
39f8bd91b9 | ||
|
|
aa4e37d085 | ||
|
|
f59b66b7d4 | ||
|
|
8f0ea7a02d | ||
|
|
a1dc00890e | ||
|
|
dfbcc363d1 | ||
|
|
1047f973d5 | ||
|
|
e32977dd73 | ||
|
|
b5f78ec1e8 | ||
|
|
e0f290fdc8 | ||
|
|
fc00a4e3b2 | ||
|
|
db1f6ded88 | ||
|
|
4644af2ccc | ||
|
|
2e3e8687e1 | ||
|
|
ca42a45802 | ||
|
|
9350ecb62b | ||
|
|
a4a026e8da | ||
|
|
342fd03e72 | ||
|
|
e3f1fd9b63 | ||
|
|
e4a4dfd038 | ||
|
|
a377e99088 | ||
|
|
1d3d7a3033 | ||
|
|
e7086cb3a3 | ||
|
|
4f2a97073e | ||
|
|
7407e3b45d | ||
|
|
01ef7340aa | ||
|
|
1c960d22c1 | ||
|
|
ece0606fed | ||
|
|
2666422b99 | ||
|
|
e6d59216d4 | ||
|
|
4e8615f276 | ||
|
|
91e4d95660 | ||
|
|
45456fa24c | ||
|
|
4588258d80 | ||
|
|
c12e48f966 | ||
|
|
ec8f50a658 | ||
|
|
99c9191784 | ||
|
|
6bb02d141f | ||
|
|
07bb2a5f3f | ||
|
|
417861a48e | ||
|
|
b7e878de64 | ||
|
|
05edb5514b | ||
|
|
e90ec847b6 | ||
|
|
6344fa2a86 | ||
|
|
7e288acc90 | ||
|
|
29b0e4a8a5 | ||
|
|
27ff222cfb | ||
|
|
11f7b83522 | ||
|
|
f7177be3b6 | ||
|
|
875b417fde | ||
|
|
2573107b32 | ||
|
|
5b85005945 | ||
|
|
1ee984478f | ||
|
|
fd693dc526 | ||
|
|
e73531ce9b | ||
|
|
53ad1645cf | ||
|
|
ecea13757b | ||
|
|
af9c4a7dd0 | ||
|
|
80d8d6c3bc | ||
|
|
d648811233 | ||
|
|
34695acb85 | ||
|
|
a63de12182 | ||
|
|
f16910d616 | ||
|
|
64b3f3cec1 | ||
|
|
6a685727d0 | ||
|
|
32d25f76fc | ||
|
|
69cafe8674 | ||
|
|
18ba8d9166 | ||
|
|
e97fd7e81c | ||
|
|
cdb64b0d33 | ||
|
|
8d4d3b03bb | ||
|
|
addefe79e1 | ||
|
|
b764d3b8f6 | ||
|
|
611fd884bd | ||
|
|
826090e099 | ||
|
|
7399de6ecc | ||
|
|
25cb5e7505 | ||
|
|
5c13ec3121 | ||
|
|
d8aff3a7e3 | ||
|
|
f44927b9f8 | ||
|
|
c0110cb5af | ||
|
|
1f8e1142a0 | ||
|
|
1e51de88d6 | ||
|
|
30995b5397 | ||
|
|
eb60f67054 | ||
|
|
78193ceec1 | ||
|
|
f0e08e7687 | ||
|
|
10b8259259 | ||
|
|
6826149a8f | ||
|
|
eb0b77bf4d | ||
|
|
9d81467937 | ||
|
|
fd8ccaf01a | ||
|
|
c9debc50b1 | ||
|
|
2b30e3b6d7 | ||
|
|
6e90ec6111 | ||
|
|
8dd38f4775 | ||
|
|
fbd73f248f | ||
|
|
3fcefe6c32 | ||
|
|
f740d2c291 | ||
|
|
bf6585a40f | ||
|
|
8c2dd7b3f0 | ||
|
|
4167c437a8 | ||
|
|
0ddaef3c9a | ||
|
|
2fc6aaf936 | ||
|
|
1c0519f1c7 | ||
|
|
6bbe7800be | ||
|
|
2694149489 | ||
|
|
a17ac50118 | ||
|
|
656a77d585 | ||
|
|
7455476c60 | ||
|
|
36cda57c81 | ||
|
|
9f1f203b84 | ||
|
|
b41a8ca93f | ||
|
|
e3cf0c0e10 | ||
|
|
de18bce9aa | ||
|
|
3cc407bc0e | ||
|
|
00a0a12138 | ||
|
|
b08767a4f9 | ||
|
|
ac6bde7a98 | ||
|
|
d2d41d68dd | ||
|
|
944b7f7617 | ||
|
|
53825eb073 | ||
|
|
1a7f49513f | ||
|
|
885a2ce7ef | ||
|
|
14ba80a0af | ||
|
|
5fa22fdf82 | ||
|
|
bcaae2eb91 | ||
|
|
767a41e263 | ||
|
|
252d6c5301 | ||
|
|
7a4e65ad4b | ||
|
|
a582aa89a9 | ||
|
|
acefa1da12 | ||
|
|
a88698f3fc | ||
|
|
ebc6755b33 | ||
|
|
c8eff34388 | ||
|
|
f19b03825b | ||
|
|
25178cdbe1 | ||
|
|
a461538d58 | ||
|
|
b43ee62947 | ||
|
|
ebe6f418f3 | ||
|
|
391e79f8ee | ||
|
|
c7fcb7a84b | ||
|
|
87f4ed591e | ||
|
|
440d2e28ed | ||
|
|
6cb8980404 | ||
|
|
fe752bbd35 | ||
|
|
c74d451fa2 | ||
|
|
12d743fb35 | ||
|
|
6acb9f7910 | ||
|
|
eb6f5c6927 | ||
|
|
7ccb4c8ea3 | ||
|
|
4ce986d47d | ||
|
|
91ef085d7d | ||
|
|
97aaa24733 | ||
|
|
faf6441633 | ||
|
|
00c151b463 | ||
|
|
106b20cdbf | ||
|
|
c069b3b1e8 | ||
|
|
a2ae9f1f27 | ||
|
|
4cd6d86426 | ||
|
|
fa72f1947a | ||
|
|
9ee7d3935d | ||
|
|
1071fe0ac7 | ||
|
|
0be003377f | ||
|
|
ca3f497b56 | ||
|
|
034b84b707 | ||
|
|
1624523c4e | ||
|
|
313afe14ce | ||
|
|
01180b316f | ||
|
|
ee7d061001 | ||
|
|
60c5949a74 | ||
|
|
2ebbd4c94d | ||
|
|
785115c62b | ||
|
|
e643fc382c | ||
|
|
34aad82ac3 | ||
|
|
0c29468f90 | ||
|
|
9301dae63e | ||
|
|
2475d4a205 | ||
|
|
be75fc3474 | ||
|
|
785e049af3 | ||
|
|
be4e49e6d7 | ||
|
|
1307d604e7 | ||
|
|
45d57018eb | ||
|
|
03bf348530 | ||
|
|
cab60ef735 | ||
|
|
a3791104f9 | ||
|
|
2b3e40bb2a | ||
|
|
0c1dcad429 | ||
|
|
101ef0cf62 | ||
|
|
0debe0a80c | ||
|
|
d22e62ac8a | ||
|
|
1ee17383f8 | ||
|
|
b59c79c458 | ||
|
|
bcb6444f89 | ||
|
|
c2b14693b4 | ||
|
|
92d35409de | ||
|
|
351a08f813 | ||
|
|
a58dc787a9 | ||
|
|
7079edc2d0 | ||
|
|
da89583ccc | ||
|
|
a42a1f08e9 | ||
|
|
ebd5253e22 | ||
|
|
6411645ffc | ||
|
|
c0c322ba16 | ||
|
|
d35c5cd491 | ||
|
|
7a353028e7 | ||
|
|
2d8d3b7857 | ||
|
|
4190293b07 | ||
|
|
421b4c0aff | ||
|
|
cd69a7cb85 | ||
|
|
0c9ba9e86c | ||
|
|
1b4d2a41c9 | ||
|
|
0787d2b47a | ||
|
|
97bf1d85ab | ||
|
|
207a493fab | ||
|
|
1f3f9e131e | ||
|
|
4ddedfaaf9 | ||
|
|
3ebebef95f | ||
|
|
9f7ad47598 | ||
|
|
3c83cd8be2 | ||
|
|
963b3b768c | ||
|
|
f6709fb5d6 | ||
|
|
921599948b | ||
|
|
5df3cafa99 | ||
|
|
1a2143c1fe | ||
|
|
dd25281305 | ||
|
|
49d0301dde | ||
|
|
d90e56eb45 | ||
|
|
838ada8864 | ||
|
|
65a106792a | ||
|
|
ee4bfcbb81 | ||
|
|
a087f089b8 | ||
|
|
afbe8bf001 | ||
|
|
2a3ef0be06 | ||
|
|
3403909354 | ||
|
|
005d0c5f53 | ||
|
|
8aaaeb29cc | ||
|
|
230f8abd04 | ||
|
|
a18bbb5f2f | ||
|
|
60fce4f1dc | ||
|
|
9af65efcdb | ||
|
|
bc194a7d8c | ||
|
|
c28f691f32 | ||
|
|
ff1f114989 | ||
|
|
cac230206d | ||
|
|
79ae15d5e8 | ||
|
|
0cce0a8877 | ||
|
|
225fd035ae | ||
|
|
fb7d1346b5 | ||
|
|
491a744481 | ||
|
|
f366026435 | ||
|
|
1a0d4ed668 | ||
|
|
63a8c76946 | ||
|
|
f355a68bc9 | ||
|
|
c87e6526c1 | ||
|
|
af3a5076d6 | ||
|
|
18f2e21414 | ||
|
|
8a8cdeebb4 | ||
|
|
12b33f4ea4 | ||
|
|
01b3a09d7d | ||
|
|
0d6c1c7790 | ||
|
|
95e366b6c6 | ||
|
|
77701143bf | ||
|
|
02dea7b09b | ||
|
|
c26f93c4a0 | ||
|
|
c826ac28ef | ||
|
|
1893b0eb30 | ||
|
|
05527b13db | ||
|
|
ae5d9c8bfc | ||
|
|
9117c2a4ec |
@@ -61,6 +61,9 @@ temp/
|
|||||||
deploy/install.sh
|
deploy/install.sh
|
||||||
deploy/sub2api.service
|
deploy/sub2api.service
|
||||||
deploy/sub2api-sudoers
|
deploy/sub2api-sudoers
|
||||||
|
deploy/data/
|
||||||
|
deploy/postgres_data/
|
||||||
|
deploy/redis_data/
|
||||||
|
|
||||||
# GoReleaser
|
# GoReleaser
|
||||||
.goreleaser.yaml
|
.goreleaser.yaml
|
||||||
|
|||||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -4,6 +4,13 @@ backend/migrations/*.sql text eol=lf
|
|||||||
# Go 源代码文件
|
# Go 源代码文件
|
||||||
*.go text eol=lf
|
*.go text eol=lf
|
||||||
|
|
||||||
|
# 前端 源代码文件
|
||||||
|
*.ts text eol=lf
|
||||||
|
*.tsx text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.jsx text eol=lf
|
||||||
|
*.vue text eol=lf
|
||||||
|
|
||||||
# Shell 脚本
|
# Shell 脚本
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
|
||||||
|
|||||||
14
.github/audit-exceptions.yml
vendored
14
.github/audit-exceptions.yml
vendored
@@ -14,3 +14,17 @@ exceptions:
|
|||||||
mitigation: "Load only on export; restrict export permissions and data scope"
|
mitigation: "Load only on export; restrict export permissions and data scope"
|
||||||
expires_on: "2026-04-05"
|
expires_on: "2026-04-05"
|
||||||
owner: "security@your-domain"
|
owner: "security@your-domain"
|
||||||
|
- package: lodash
|
||||||
|
advisory: "GHSA-r5fr-rjxr-66jc"
|
||||||
|
severity: high
|
||||||
|
reason: "lodash _.template not used with untrusted input; only internal admin UI templates"
|
||||||
|
mitigation: "No user-controlled template strings; plan to migrate to lodash-es tree-shaken imports"
|
||||||
|
expires_on: "2026-07-02"
|
||||||
|
owner: "security@your-domain"
|
||||||
|
- package: lodash-es
|
||||||
|
advisory: "GHSA-r5fr-rjxr-66jc"
|
||||||
|
severity: high
|
||||||
|
reason: "lodash-es _.template not used with untrusted input; only internal admin UI templates"
|
||||||
|
mitigation: "No user-controlled template strings; plan to migrate to native JS alternatives"
|
||||||
|
expires_on: "2026-07-02"
|
||||||
|
owner: "security@your-domain"
|
||||||
|
|||||||
6
.github/workflows/backend-ci.yml
vendored
6
.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.7'
|
go version | grep -q 'go1.26.1'
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
run: make test-unit
|
run: make test-unit
|
||||||
@@ -38,10 +38,10 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
- name: Verify Go version
|
- name: Verify Go version
|
||||||
run: |
|
run: |
|
||||||
go version | grep -q 'go1.25.7'
|
go version | grep -q 'go1.26.1'
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v9
|
uses: golangci/golangci-lint-action@v9
|
||||||
with:
|
with:
|
||||||
version: v2.7
|
version: v2.9
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
35
.github/workflows/release.yml
vendored
35
.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.7'
|
go version | grep -q 'go1.26.1'
|
||||||
|
|
||||||
# Docker setup for GoReleaser
|
# Docker setup for GoReleaser
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
@@ -271,3 +271,36 @@ jobs:
|
|||||||
parse_mode: "Markdown",
|
parse_mode: "Markdown",
|
||||||
disable_web_page_preview: true
|
disable_web_page_preview: true
|
||||||
}')"
|
}')"
|
||||||
|
|
||||||
|
sync-version-file:
|
||||||
|
needs: [release]
|
||||||
|
if: ${{ needs.release.result == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout default branch
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.repository.default_branch }}
|
||||||
|
|
||||||
|
- name: Sync VERSION file to released tag
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
VERSION=${{ github.event.inputs.tag }}
|
||||||
|
VERSION=${VERSION#v}
|
||||||
|
else
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURRENT_VERSION=$(tr -d '\r\n' < backend/cmd/server/VERSION || true)
|
||||||
|
if [ "$CURRENT_VERSION" = "$VERSION" ]; then
|
||||||
|
echo "VERSION file already matches $VERSION"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$VERSION" > backend/cmd/server/VERSION
|
||||||
|
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add backend/cmd/server/VERSION
|
||||||
|
git commit -m "chore: sync VERSION to ${VERSION} [skip ci]"
|
||||||
|
git push origin HEAD:${{ github.event.repository.default_branch }}
|
||||||
|
|||||||
2
.github/workflows/security-scan.yml
vendored
2
.github/workflows/security-scan.yml
vendored
@@ -23,7 +23,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.7'
|
go version | grep -q 'go1.26.1'
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ dockers:
|
|||||||
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
|
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
|
||||||
dockerfile: Dockerfile.goreleaser
|
dockerfile: Dockerfile.goreleaser
|
||||||
use: buildx
|
use: buildx
|
||||||
|
extra_files:
|
||||||
|
- deploy/docker-entrypoint.sh
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/amd64"
|
- "--platform=linux/amd64"
|
||||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ dockers:
|
|||||||
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
|
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
|
||||||
dockerfile: Dockerfile.goreleaser
|
dockerfile: Dockerfile.goreleaser
|
||||||
use: buildx
|
use: buildx
|
||||||
|
extra_files:
|
||||||
|
- deploy/docker-entrypoint.sh
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/amd64"
|
- "--platform=linux/amd64"
|
||||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||||
@@ -76,6 +78,8 @@ dockers:
|
|||||||
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"
|
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"
|
||||||
dockerfile: Dockerfile.goreleaser
|
dockerfile: Dockerfile.goreleaser
|
||||||
use: buildx
|
use: buildx
|
||||||
|
extra_files:
|
||||||
|
- deploy/docker-entrypoint.sh
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/arm64"
|
- "--platform=linux/arm64"
|
||||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||||
@@ -89,6 +93,8 @@ dockers:
|
|||||||
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
|
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
|
||||||
dockerfile: Dockerfile.goreleaser
|
dockerfile: Dockerfile.goreleaser
|
||||||
use: buildx
|
use: buildx
|
||||||
|
extra_files:
|
||||||
|
- deploy/docker-entrypoint.sh
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/amd64"
|
- "--platform=linux/amd64"
|
||||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||||
@@ -102,6 +108,8 @@ dockers:
|
|||||||
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
|
- "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
|
||||||
dockerfile: Dockerfile.goreleaser
|
dockerfile: Dockerfile.goreleaser
|
||||||
use: buildx
|
use: buildx
|
||||||
|
extra_files:
|
||||||
|
- deploy/docker-entrypoint.sh
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/arm64"
|
- "--platform=linux/arm64"
|
||||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||||
|
|||||||
33
Dockerfile
33
Dockerfile
@@ -7,8 +7,9 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
ARG NODE_IMAGE=node:24-alpine
|
ARG NODE_IMAGE=node:24-alpine
|
||||||
ARG GOLANG_IMAGE=golang:1.25.7-alpine
|
ARG GOLANG_IMAGE=golang:1.26.1-alpine
|
||||||
ARG ALPINE_IMAGE=alpine:3.21
|
ARG ALPINE_IMAGE=alpine:3.21
|
||||||
|
ARG POSTGRES_IMAGE=postgres:18-alpine
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
ARG GOPROXY=https://goproxy.cn,direct
|
||||||
ARG GOSUMDB=sum.golang.google.cn
|
ARG GOSUMDB=sum.golang.google.cn
|
||||||
|
|
||||||
@@ -73,7 +74,12 @@ RUN VERSION_VALUE="${VERSION}" && \
|
|||||||
./cmd/server
|
./cmd/server
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Stage 3: Final Runtime Image
|
# Stage 3: PostgreSQL Client (version-matched with docker-compose)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
FROM ${POSTGRES_IMAGE} AS pg-client
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Stage 4: Final Runtime Image
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
FROM ${ALPINE_IMAGE}
|
FROM ${ALPINE_IMAGE}
|
||||||
|
|
||||||
@@ -86,8 +92,21 @@ LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
|
|||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
|
su-exec \
|
||||||
|
libpq \
|
||||||
|
zstd-libs \
|
||||||
|
lz4-libs \
|
||||||
|
krb5-libs \
|
||||||
|
libldap \
|
||||||
|
libedit \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Copy pg_dump and psql from the same postgres image used in docker-compose
|
||||||
|
# This ensures version consistency between backup tools and the database server
|
||||||
|
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
|
||||||
|
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
|
||||||
|
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup -g 1000 sub2api && \
|
RUN addgroup -g 1000 sub2api && \
|
||||||
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
|
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
|
||||||
@@ -102,8 +121,9 @@ COPY --from=backend-builder --chown=sub2api:sub2api /app/backend/resources /app/
|
|||||||
# Create data directory
|
# Create data directory
|
||||||
RUN mkdir -p /app/data && chown sub2api:sub2api /app/data
|
RUN mkdir -p /app/data && chown sub2api:sub2api /app/data
|
||||||
|
|
||||||
# Switch to non-root user
|
# Copy entrypoint script (fixes volume permissions then drops to sub2api)
|
||||||
USER sub2api
|
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /app/docker-entrypoint.sh
|
||||||
|
|
||||||
# Expose port (can be overridden by SERVER_PORT env var)
|
# Expose port (can be overridden by SERVER_PORT env var)
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
@@ -112,5 +132,6 @@ EXPOSE 8080
|
|||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||||
CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
||||||
|
|
||||||
# Run the application
|
# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
|
||||||
ENTRYPOINT ["/app/sub2api"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
CMD ["/app/sub2api"]
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
# It only packages the pre-built binary, no compilation needed.
|
# It only packages the pre-built binary, no compilation needed.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
FROM alpine:3.19
|
ARG ALPINE_IMAGE=alpine:3.21
|
||||||
|
ARG POSTGRES_IMAGE=postgres:18-alpine
|
||||||
|
|
||||||
|
FROM ${POSTGRES_IMAGE} AS pg-client
|
||||||
|
|
||||||
|
FROM ${ALPINE_IMAGE}
|
||||||
|
|
||||||
LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
|
LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
|
||||||
LABEL description="Sub2API - AI API Gateway Platform"
|
LABEL description="Sub2API - AI API Gateway Platform"
|
||||||
@@ -16,8 +21,21 @@ RUN apk add --no-cache \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
|
su-exec \
|
||||||
|
libpq \
|
||||||
|
zstd-libs \
|
||||||
|
lz4-libs \
|
||||||
|
krb5-libs \
|
||||||
|
libldap \
|
||||||
|
libedit \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Copy pg_dump and psql from a version-matched PostgreSQL image so backup and
|
||||||
|
# restore work in the runtime container without requiring Docker socket access.
|
||||||
|
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
|
||||||
|
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
|
||||||
|
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup -g 1000 sub2api && \
|
RUN addgroup -g 1000 sub2api && \
|
||||||
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
|
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
|
||||||
@@ -30,11 +48,15 @@ COPY sub2api /app/sub2api
|
|||||||
# Create data directory
|
# Create data directory
|
||||||
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
|
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
|
||||||
|
|
||||||
USER sub2api
|
# Copy entrypoint script (fixes volume permissions then drops to sub2api)
|
||||||
|
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /app/docker-entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||||
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/app/sub2api"]
|
# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
CMD ["/app/sub2api"]
|
||||||
|
|||||||
100
README.md
100
README.md
@@ -8,27 +8,31 @@
|
|||||||
[](https://redis.io/)
|
[](https://redis.io/)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
|
||||||
|
|
||||||
**AI API Gateway Platform for Subscription Quota Distribution**
|
**AI API Gateway Platform for Subscription Quota Distribution**
|
||||||
|
|
||||||
English | [中文](README_CN.md)
|
English | [中文](README_CN.md) | [日本語](README_JA.md)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
> **Sub2API officially uses only the domains `sub2api.org` and `pincc.ai`. Other websites using the Sub2API name may be third-party deployments or services and are not affiliated with this project. Please verify and exercise your own judgment.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
Try Sub2API online: **https://demo.sub2api.org/**
|
Try Sub2API online: **[https://demo.sub2api.org/](https://demo.sub2api.org/)**
|
||||||
|
|
||||||
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
|
Demo credentials (shared demo environment; **not** created automatically for self-hosted installs):
|
||||||
|
|
||||||
| Email | Password |
|
| Email | Password |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| admin@sub2api.com | admin123 |
|
| admin@sub2api.org | admin123 |
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions (like Claude Code $200/month). Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
|
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -39,6 +43,29 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
|
|||||||
- **Concurrency Control** - Per-user and per-account concurrency limits
|
- **Concurrency Control** - Per-user and per-account concurrency limits
|
||||||
- **Rate Limiting** - Configurable request and token rate limits
|
- **Rate Limiting** - Configurable request and token rate limits
|
||||||
- **Admin Dashboard** - Web interface for monitoring and management
|
- **Admin Dashboard** - Web interface for monitoring and management
|
||||||
|
- **External System Integration** - Embed external systems (e.g. payment, ticketing) via iframe to extend the admin dashboard
|
||||||
|
|
||||||
|
## Don't Want to Self-Host?
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
|
||||||
|
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> is the official relay service built on Sub2API, offering stable access to Claude Code, Codex, Gemini and other popular models — ready to use, no deployment or maintenance required.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
|
||||||
|
<td>Thanks to PackyCode for sponsoring this project! PackyCode is a reliable and efficient API relay service provider, offering relay services for Claude Code, Codex, Gemini, and more. PackyCode provides special discounts for our software users: register using <a href="https://www.packyapi.com/register?aff=sub2api">this link</a> and enter the "sub2api" promo code during first recharge to get 10% off.</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
Community projects that extend or integrate with Sub2API:
|
||||||
|
|
||||||
|
| Project | Description | Features |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | Self-service payment system | Self-service top-up and subscription purchase; supports YiPay protocol, WeChat Pay, Alipay, Stripe; embeddable via iframe |
|
||||||
|
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native |
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
@@ -51,10 +78,15 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Documentation
|
## Nginx Reverse Proxy Note
|
||||||
|
|
||||||
- Dependency Security: `docs/dependency-security.md`
|
When using Nginx as a reverse proxy for Sub2API (or CRS) with Codex CLI, add the following to the `http` block in your Nginx configuration:
|
||||||
- Admin Payment Integration API: `docs/ADMIN_PAYMENT_INTEGRATION_API.md`
|
|
||||||
|
```nginx
|
||||||
|
underscores_in_headers on;
|
||||||
|
```
|
||||||
|
|
||||||
|
Nginx drops headers containing underscores by default (e.g. `session_id`), which breaks sticky session routing in multi-account setups.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -150,14 +182,14 @@ mkdir -p sub2api-deploy && cd sub2api-deploy
|
|||||||
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||||
|
|
||||||
# Start services
|
# Start services
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose up -d
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
docker-compose -f docker-compose.local.yml logs -f sub2api
|
docker compose logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
**What the script does:**
|
**What the script does:**
|
||||||
- Downloads `docker-compose.local.yml` and `.env.example`
|
- Downloads `docker-compose.local.yml` (saved as `docker-compose.yml`) and `.env.example`
|
||||||
- Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
|
- Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
|
||||||
- Creates `.env` file with auto-generated secrets
|
- Creates `.env` file with auto-generated secrets
|
||||||
- Creates data directories (uses local directories for easy backup/migration)
|
- Creates data directories (uses local directories for easy backup/migration)
|
||||||
@@ -217,16 +249,16 @@ mkdir -p data postgres_data redis_data
|
|||||||
|
|
||||||
# 5. Start all services
|
# 5. Start all services
|
||||||
# Option A: Local directory version (recommended - easy migration)
|
# Option A: Local directory version (recommended - easy migration)
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
# Option B: Named volumes version (simple setup)
|
# Option B: Named volumes version (simple setup)
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 6. Check status
|
# 6. Check status
|
||||||
docker-compose -f docker-compose.local.yml ps
|
docker compose -f docker-compose.local.yml ps
|
||||||
|
|
||||||
# 7. View logs
|
# 7. View logs
|
||||||
docker-compose -f docker-compose.local.yml logs -f sub2api
|
docker compose -f docker-compose.local.yml logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Deployment Versions
|
#### Deployment Versions
|
||||||
@@ -244,15 +276,15 @@ Open `http://YOUR_SERVER_IP:8080` in your browser.
|
|||||||
|
|
||||||
If admin password was auto-generated, find it in logs:
|
If admin password was auto-generated, find it in logs:
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Upgrade
|
#### Upgrade
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pull latest image and recreate container
|
# Pull latest image and recreate container
|
||||||
docker-compose -f docker-compose.local.yml pull
|
docker compose -f docker-compose.local.yml pull
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Easy Migration (Local Directory Version)
|
#### Easy Migration (Local Directory Version)
|
||||||
@@ -261,7 +293,7 @@ When using `docker-compose.local.yml`, migrate to a new server easily:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On source server
|
# On source server
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
cd ..
|
cd ..
|
||||||
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
||||||
|
|
||||||
@@ -271,23 +303,23 @@ scp sub2api-complete.tar.gz user@new-server:/path/
|
|||||||
# On new server
|
# On new server
|
||||||
tar xzf sub2api-complete.tar.gz
|
tar xzf sub2api-complete.tar.gz
|
||||||
cd sub2api-deploy/
|
cd sub2api-deploy/
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Useful Commands
|
#### Useful Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop all services
|
# Stop all services
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
|
|
||||||
# Restart
|
# Restart
|
||||||
docker-compose -f docker-compose.local.yml restart
|
docker compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
# View all logs
|
# View all logs
|
||||||
docker-compose -f docker-compose.local.yml logs -f
|
docker compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
# Remove all data (caution!)
|
# Remove all data (caution!)
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
rm -rf data/ postgres_data/ redis_data/
|
rm -rf data/ postgres_data/ redis_data/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -522,6 +554,28 @@ sub2api/
|
|||||||
└── install.sh # One-click installation script
|
└── install.sh # One-click installation script
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
> **Please read carefully before using this project:**
|
||||||
|
>
|
||||||
|
> :rotating_light: **Terms of Service Risk**: Using this project may violate Anthropic's Terms of Service. Please read Anthropic's user agreement carefully before use. All risks arising from the use of this project are borne solely by the user.
|
||||||
|
>
|
||||||
|
> :book: **Disclaimer**: This project is for technical learning and research purposes only. The author assumes no responsibility for account suspension, service interruption, or any other losses caused by the use of this project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
103
README_CN.md
103
README_CN.md
@@ -8,27 +8,30 @@
|
|||||||
[](https://redis.io/)
|
[](https://redis.io/)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
|
||||||
|
|
||||||
**AI API 网关平台 - 订阅配额分发管理**
|
**AI API 网关平台 - 订阅配额分发管理**
|
||||||
|
|
||||||
[English](README.md) | 中文
|
[English](README.md) | 中文 | [日本語](README_JA.md)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
> **Sub2API 官方仅使用 `sub2api.org` 与 `pincc.ai` 两个域名。其他使用 Sub2API 名义的网站可能为第三方部署或服务,与本项目无关,请自行甄别。**
|
||||||
---
|
---
|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
||||||
体验地址:**https://v2.pincc.ai/**
|
体验地址:**[https://demo.sub2api.org/](https://demo.sub2api.org/)**
|
||||||
|
|
||||||
演示账号(共享演示环境;自建部署不会自动创建该账号):
|
演示账号(共享演示环境;自建部署不会自动创建该账号):
|
||||||
|
|
||||||
| 邮箱 | 密码 |
|
| 邮箱 | 密码 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| admin@sub2api.com | admin123 |
|
| admin@sub2api.org | admin123 |
|
||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(如 Claude Code $200/月)的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务,平台负责鉴权、计费、负载均衡和请求转发。
|
Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务,平台负责鉴权、计费、负载均衡和请求转发。
|
||||||
|
|
||||||
## 核心功能
|
## 核心功能
|
||||||
|
|
||||||
@@ -39,6 +42,29 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
|
|||||||
- **并发控制** - 用户级和账号级并发限制
|
- **并发控制** - 用户级和账号级并发限制
|
||||||
- **速率限制** - 可配置的请求和 Token 速率限制
|
- **速率限制** - 可配置的请求和 Token 速率限制
|
||||||
- **管理后台** - Web 界面进行监控和管理
|
- **管理后台** - Web 界面进行监控和管理
|
||||||
|
- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如支付、工单等),扩展管理后台功能
|
||||||
|
|
||||||
|
## 不想自建?试试官方中转
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
|
||||||
|
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> 是基于 Sub2API 搭建的官方中转服务,提供 Claude Code、Codex、Gemini 等主流模型的稳定中转,开箱即用,免去自建部署与运维烦恼。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
|
||||||
|
<td>感谢 PackyCode 赞助了本项目!PackyCode 是一家稳定、高效的API中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。PackyCode 为本软件的用户提供了特别优惠,使用<a href="https://www.packyapi.com/register?aff=sub2api">此链接</a>注册并在充值时填写"sub2api"优惠码,首次充值可以享受9折优惠!</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## 生态项目
|
||||||
|
|
||||||
|
围绕 Sub2API 的社区扩展与集成项目:
|
||||||
|
|
||||||
|
| 项目 | 说明 | 功能 |
|
||||||
|
|------|------|------|
|
||||||
|
| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | 自助支付系统 | 用户自助充值、自助订阅购买;兼容易支付协议、微信官方支付、支付宝官方支付、Stripe;支持 iframe 嵌入管理后台 |
|
||||||
|
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用(iOS/Android/Web),支持用户管理、账号管理、监控看板、多后端切换;基于 Expo + React Native 构建 |
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -51,17 +77,18 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 文档
|
## Nginx 反向代理注意事项
|
||||||
|
|
||||||
- 依赖安全:`docs/dependency-security.md`
|
通过 Nginx 反向代理 Sub2API(或 CRS 服务)并搭配 Codex CLI 使用时,需要在 Nginx 配置的 `http` 块中添加:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
underscores_in_headers on;
|
||||||
|
```
|
||||||
|
|
||||||
|
Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),这会导致多账号环境下的粘性会话功能失效。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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” 错误。
|
|
||||||
|
|
||||||
## 部署方式
|
## 部署方式
|
||||||
|
|
||||||
### 方式一:脚本安装(推荐)
|
### 方式一:脚本安装(推荐)
|
||||||
@@ -154,14 +181,14 @@ mkdir -p sub2api-deploy && cd sub2api-deploy
|
|||||||
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 查看日志
|
# 查看日志
|
||||||
docker-compose -f docker-compose.local.yml logs -f sub2api
|
docker compose logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
**脚本功能:**
|
**脚本功能:**
|
||||||
- 下载 `docker-compose.local.yml` 和 `.env.example`
|
- 下载 `docker-compose.local.yml`(本地保存为 `docker-compose.yml`)和 `.env.example`
|
||||||
- 自动生成安全凭证(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)
|
- 自动生成安全凭证(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)
|
||||||
- 创建 `.env` 文件并填充自动生成的密钥
|
- 创建 `.env` 文件并填充自动生成的密钥
|
||||||
- 创建数据目录(使用本地目录,便于备份和迁移)
|
- 创建数据目录(使用本地目录,便于备份和迁移)
|
||||||
@@ -221,16 +248,16 @@ mkdir -p data postgres_data redis_data
|
|||||||
|
|
||||||
# 5. 启动所有服务
|
# 5. 启动所有服务
|
||||||
# 选项 A:本地目录版(推荐 - 易于迁移)
|
# 选项 A:本地目录版(推荐 - 易于迁移)
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
# 选项 B:命名卷版(简单设置)
|
# 选项 B:命名卷版(简单设置)
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 6. 查看状态
|
# 6. 查看状态
|
||||||
docker-compose -f docker-compose.local.yml ps
|
docker compose -f docker-compose.local.yml ps
|
||||||
|
|
||||||
# 7. 查看日志
|
# 7. 查看日志
|
||||||
docker-compose -f docker-compose.local.yml logs -f sub2api
|
docker compose -f docker-compose.local.yml logs -f sub2api
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 部署版本对比
|
#### 部署版本对比
|
||||||
@@ -260,15 +287,15 @@ docker-compose -f docker-compose.local.yml logs -f sub2api
|
|||||||
|
|
||||||
如果管理员密码是自动生成的,在日志中查找:
|
如果管理员密码是自动生成的,在日志中查找:
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 升级
|
#### 升级
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 拉取最新镜像并重建容器
|
# 拉取最新镜像并重建容器
|
||||||
docker-compose -f docker-compose.local.yml pull
|
docker compose -f docker-compose.local.yml pull
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 轻松迁移(本地目录版)
|
#### 轻松迁移(本地目录版)
|
||||||
@@ -277,7 +304,7 @@ docker-compose -f docker-compose.local.yml up -d
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 源服务器
|
# 源服务器
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
cd ..
|
cd ..
|
||||||
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
||||||
|
|
||||||
@@ -287,23 +314,23 @@ scp sub2api-complete.tar.gz user@new-server:/path/
|
|||||||
# 新服务器
|
# 新服务器
|
||||||
tar xzf sub2api-complete.tar.gz
|
tar xzf sub2api-complete.tar.gz
|
||||||
cd sub2api-deploy/
|
cd sub2api-deploy/
|
||||||
docker-compose -f docker-compose.local.yml up -d
|
docker compose -f docker-compose.local.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 常用命令
|
#### 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 停止所有服务
|
# 停止所有服务
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
|
|
||||||
# 重启
|
# 重启
|
||||||
docker-compose -f docker-compose.local.yml restart
|
docker compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
# 查看所有日志
|
# 查看所有日志
|
||||||
docker-compose -f docker-compose.local.yml logs -f
|
docker compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
# 删除所有数据(谨慎!)
|
# 删除所有数据(谨慎!)
|
||||||
docker-compose -f docker-compose.local.yml down
|
docker compose -f docker-compose.local.yml down
|
||||||
rm -rf data/ postgres_data/ redis_data/
|
rm -rf data/ postgres_data/ redis_data/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -588,6 +615,28 @@ sub2api/
|
|||||||
└── install.sh # 一键安装脚本
|
└── install.sh # 一键安装脚本
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 免责声明
|
||||||
|
|
||||||
|
> **使用本项目前请仔细阅读:**
|
||||||
|
>
|
||||||
|
> :rotating_light: **服务条款风险**: 使用本项目可能违反 Anthropic 的服务条款。请在使用前仔细阅读 Anthropic 的用户协议,使用本项目的一切风险由用户自行承担。
|
||||||
|
>
|
||||||
|
> :book: **免责声明**: 本项目仅供技术学习和研究使用,作者不对因使用本项目导致的账户封禁、服务中断或其他损失承担任何责任。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
589
README_JA.md
Normal file
589
README_JA.md
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
# Sub2API
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://golang.org/)
|
||||||
|
[](https://vuejs.org/)
|
||||||
|
[](https://www.postgresql.org/)
|
||||||
|
[](https://redis.io/)
|
||||||
|
[](https://www.docker.com/)
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
**サブスクリプションクォータ配分のための AI API ゲートウェイプラットフォーム**
|
||||||
|
|
||||||
|
[English](README.md) | [中文](README_CN.md) | 日本語
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> **Sub2API が公式に使用しているドメインは `sub2api.org` と `pincc.ai` のみです。Sub2API の名称を使用している他のウェブサイトは、サードパーティによるデプロイやサービスであり、本プロジェクトとは一切関係がありません。ご利用の際はご自身で確認・判断をお願いします。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## デモ
|
||||||
|
|
||||||
|
Sub2API をオンラインでお試しください: **[https://demo.sub2api.org/](https://demo.sub2api.org/)**
|
||||||
|
|
||||||
|
デモ用認証情報(共有デモ環境です。セルフホスト環境では**自動作成されません**):
|
||||||
|
|
||||||
|
| メールアドレス | パスワード |
|
||||||
|
|-------|----------|
|
||||||
|
| admin@sub2api.org | admin123 |
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
Sub2API は、AI 製品のサブスクリプションから API クォータを配分・管理するために設計された AI API ゲートウェイプラットフォームです。ユーザーはプラットフォームが生成した API キーを通じて上流の AI サービスにアクセスでき、プラットフォームは認証、課金、負荷分散、リクエスト転送を処理します。
|
||||||
|
|
||||||
|
## 機能
|
||||||
|
|
||||||
|
- **マルチアカウント管理** - 複数の上流アカウントタイプ(OAuth、APIキー)をサポート
|
||||||
|
- **APIキー配布** - ユーザー向けの APIキーの生成と管理
|
||||||
|
- **精密な課金** - トークンレベルの使用量追跡とコスト計算
|
||||||
|
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
|
||||||
|
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
|
||||||
|
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
|
||||||
|
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
|
||||||
|
- **外部システム連携** - 外部システム(決済、チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
|
||||||
|
|
||||||
|
## セルフホストが不要な方へ
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="180" align="center" valign="middle"><a href="https://shop.pincc.ai/"><img src="assets/partners/logos/pincc-logo.png" alt="pincc" width="150"></a></td>
|
||||||
|
<td valign="middle"><b><a href="https://shop.pincc.ai/">PinCC</a></b> は Sub2API 上に構築された公式リレーサービスで、Claude Code、Codex、Gemini などの人気モデルへの安定したアクセスを提供します。デプロイやメンテナンスは不要で、すぐにご利用いただけます。</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://www.packyapi.com/register?aff=sub2api"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></a></td>
|
||||||
|
<td>PackyCode のご支援に感謝します!PackyCode は Claude Code、Codex、Gemini などのリレーサービスを提供する信頼性の高い API 中継プラットフォームです。本ソフト利用者向けに特別割引があります:<a href="https://www.packyapi.com/register?aff=sub2api">このリンク</a>で登録し、チャージ時に「sub2api」クーポンを入力すると 10% オフになります。</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## エコシステム
|
||||||
|
|
||||||
|
Sub2API を拡張・統合するコミュニティプロジェクト:
|
||||||
|
|
||||||
|
| プロジェクト | 説明 | 機能 |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | セルフサービス決済システム | セルフサービスによるチャージおよびサブスクリプション購入。YiPay プロトコル、WeChat Pay、Alipay、Stripe 対応。iframe での埋め込み可能 |
|
||||||
|
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | モバイル管理コンソール | ユーザー管理、アカウント管理、監視ダッシュボード、マルチバックエンド切り替えが可能なクロスプラットフォームアプリ(iOS/Android/Web)。Expo + React Native で構築 |
|
||||||
|
|
||||||
|
## 技術スタック
|
||||||
|
|
||||||
|
| コンポーネント | 技術 |
|
||||||
|
|-----------|------------|
|
||||||
|
| バックエンド | Go 1.25.7, Gin, Ent |
|
||||||
|
| フロントエンド | Vue 3.4+, Vite 5+, TailwindCSS |
|
||||||
|
| データベース | PostgreSQL 15+ |
|
||||||
|
| キャッシュ/キュー | Redis 7+ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nginx リバースプロキシに関する注意
|
||||||
|
|
||||||
|
Sub2API(または CRS)を Nginx でリバースプロキシし、Codex CLI と組み合わせて使用する場合、Nginx の `http` ブロックに以下の設定を追加してください:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
underscores_in_headers on;
|
||||||
|
```
|
||||||
|
|
||||||
|
Nginx はデフォルトでアンダースコアを含むヘッダー(例: `session_id`)を破棄するため、マルチアカウント構成でのスティッキーセッションルーティングに支障をきたします。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## デプロイ
|
||||||
|
|
||||||
|
### 方法1: スクリプトによるインストール(推奨)
|
||||||
|
|
||||||
|
GitHub Releases からビルド済みバイナリをダウンロードするワンクリックインストールスクリプトです。
|
||||||
|
|
||||||
|
#### 前提条件
|
||||||
|
|
||||||
|
- Linux サーバー(amd64 または arm64)
|
||||||
|
- PostgreSQL 15+(インストール済みかつ稼働中)
|
||||||
|
- Redis 7+(インストール済みかつ稼働中)
|
||||||
|
- root 権限
|
||||||
|
|
||||||
|
#### インストール手順
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
スクリプトは以下を実行します:
|
||||||
|
1. システムアーキテクチャの検出
|
||||||
|
2. 最新リリースのダウンロード
|
||||||
|
3. バイナリを `/opt/sub2api` にインストール
|
||||||
|
4. systemd サービスの作成
|
||||||
|
5. システムユーザーと権限の設定
|
||||||
|
|
||||||
|
#### インストール後の作業
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. サービスを起動
|
||||||
|
sudo systemctl start sub2api
|
||||||
|
|
||||||
|
# 2. 起動時の自動起動を有効化
|
||||||
|
sudo systemctl enable sub2api
|
||||||
|
|
||||||
|
# 3. ブラウザでセットアップウィザードを開く
|
||||||
|
# http://YOUR_SERVER_IP:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
セットアップウィザードでは以下の設定を行います:
|
||||||
|
- データベース設定
|
||||||
|
- Redis 設定
|
||||||
|
- 管理者アカウントの作成
|
||||||
|
|
||||||
|
#### アップグレード
|
||||||
|
|
||||||
|
**管理ダッシュボード**の左上にある**アップデートを確認**ボタンをクリックすることで、ダッシュボードから直接アップグレードできます。
|
||||||
|
|
||||||
|
Web インターフェースでは以下が可能です:
|
||||||
|
- 新しいバージョンの自動確認
|
||||||
|
- ワンクリックでのアップデートのダウンロードと適用
|
||||||
|
- 必要に応じたロールバック
|
||||||
|
|
||||||
|
#### よく使うコマンド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ステータスを確認
|
||||||
|
sudo systemctl status sub2api
|
||||||
|
|
||||||
|
# ログを表示
|
||||||
|
sudo journalctl -u sub2api -f
|
||||||
|
|
||||||
|
# サービスを再起動
|
||||||
|
sudo systemctl restart sub2api
|
||||||
|
|
||||||
|
# アンインストール
|
||||||
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方法2: Docker Compose(推奨)
|
||||||
|
|
||||||
|
PostgreSQL と Redis のコンテナを含む Docker Compose でデプロイします。
|
||||||
|
|
||||||
|
#### 前提条件
|
||||||
|
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose v2+
|
||||||
|
|
||||||
|
#### クイックスタート(ワンクリックデプロイ)
|
||||||
|
|
||||||
|
自動デプロイスクリプトを使用して簡単にセットアップできます:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# デプロイ用ディレクトリを作成
|
||||||
|
mkdir -p sub2api-deploy && cd sub2api-deploy
|
||||||
|
|
||||||
|
# デプロイ準備スクリプトをダウンロードして実行
|
||||||
|
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||||
|
|
||||||
|
# サービスを起動
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# ログを表示
|
||||||
|
docker compose logs -f sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
**スクリプトの動作内容:**
|
||||||
|
- `docker-compose.local.yml`(`docker-compose.yml` として保存)と `.env.example` をダウンロード
|
||||||
|
- セキュアな認証情報(JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD)を自動生成
|
||||||
|
- 自動生成されたシークレットで `.env` ファイルを作成
|
||||||
|
- データディレクトリを作成(バックアップ・移行が容易なローカルディレクトリを使用)
|
||||||
|
- 生成された認証情報を参照用に表示
|
||||||
|
|
||||||
|
#### 手動デプロイ
|
||||||
|
|
||||||
|
手動でセットアップする場合:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. リポジトリをクローン
|
||||||
|
git clone https://github.com/Wei-Shaw/sub2api.git
|
||||||
|
cd sub2api/deploy
|
||||||
|
|
||||||
|
# 2. 環境設定ファイルをコピー
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 3. 設定を編集(セキュアなパスワードを生成)
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**`.env` の必須設定:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PostgreSQL パスワード(必須)
|
||||||
|
POSTGRES_PASSWORD=your_secure_password_here
|
||||||
|
|
||||||
|
# JWT シークレット(推奨 - 再起動後もユーザーのログイン状態を保持)
|
||||||
|
JWT_SECRET=your_jwt_secret_here
|
||||||
|
|
||||||
|
# TOTP 暗号化キー(推奨 - 再起動後も二要素認証を維持)
|
||||||
|
TOTP_ENCRYPTION_KEY=your_totp_key_here
|
||||||
|
|
||||||
|
# オプション: 管理者アカウント
|
||||||
|
ADMIN_EMAIL=admin@example.com
|
||||||
|
ADMIN_PASSWORD=your_admin_password
|
||||||
|
|
||||||
|
# オプション: カスタムポート
|
||||||
|
SERVER_PORT=8080
|
||||||
|
```
|
||||||
|
|
||||||
|
**セキュアなシークレットの生成方法:**
|
||||||
|
```bash
|
||||||
|
# JWT_SECRET を生成
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# TOTP_ENCRYPTION_KEY を生成
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# POSTGRES_PASSWORD を生成
|
||||||
|
openssl rand -hex 32
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 4. データディレクトリを作成(ローカルバージョンの場合)
|
||||||
|
mkdir -p data postgres_data redis_data
|
||||||
|
|
||||||
|
# 5. すべてのサービスを起動
|
||||||
|
# オプション A: ローカルディレクトリバージョン(推奨 - 移行が容易)
|
||||||
|
docker compose -f docker-compose.local.yml up -d
|
||||||
|
|
||||||
|
# オプション B: 名前付きボリュームバージョン(シンプルなセットアップ)
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 6. ステータスを確認
|
||||||
|
docker compose -f docker-compose.local.yml ps
|
||||||
|
|
||||||
|
# 7. ログを表示
|
||||||
|
docker compose -f docker-compose.local.yml logs -f sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
#### デプロイバージョン
|
||||||
|
|
||||||
|
| バージョン | データストレージ | 移行 | 推奨用途 |
|
||||||
|
|---------|-------------|-----------|----------|
|
||||||
|
| **docker-compose.local.yml** | ローカルディレクトリ | ✅ 容易(ディレクトリ全体を tar) | 本番環境、頻繁なバックアップ |
|
||||||
|
| **docker-compose.yml** | 名前付きボリューム | ⚠️ docker コマンドが必要 | シンプルなセットアップ |
|
||||||
|
|
||||||
|
**推奨:** データ管理が容易な `docker-compose.local.yml`(スクリプトによるデプロイ)を使用してください。
|
||||||
|
|
||||||
|
#### アクセス
|
||||||
|
|
||||||
|
ブラウザで `http://YOUR_SERVER_IP:8080` を開いてください。
|
||||||
|
|
||||||
|
管理者パスワードが自動生成された場合は、ログで確認できます:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### アップグレード
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 最新イメージをプルしてコンテナを再作成
|
||||||
|
docker compose -f docker-compose.local.yml pull
|
||||||
|
docker compose -f docker-compose.local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 簡単な移行(ローカルディレクトリバージョン)
|
||||||
|
|
||||||
|
`docker-compose.local.yml` を使用している場合、新しいサーバーへの移行が簡単です:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 移行元サーバーにて
|
||||||
|
docker compose -f docker-compose.local.yml down
|
||||||
|
cd ..
|
||||||
|
tar czf sub2api-complete.tar.gz sub2api-deploy/
|
||||||
|
|
||||||
|
# 新しいサーバーに転送
|
||||||
|
scp sub2api-complete.tar.gz user@new-server:/path/
|
||||||
|
|
||||||
|
# 移行先サーバーにて
|
||||||
|
tar xzf sub2api-complete.tar.gz
|
||||||
|
cd sub2api-deploy/
|
||||||
|
docker compose -f docker-compose.local.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### よく使うコマンド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# すべてのサービスを停止
|
||||||
|
docker compose -f docker-compose.local.yml down
|
||||||
|
|
||||||
|
# 再起動
|
||||||
|
docker compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
|
# すべてのログを表示
|
||||||
|
docker compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
|
# すべてのデータを削除(注意!)
|
||||||
|
docker compose -f docker-compose.local.yml down
|
||||||
|
rm -rf data/ postgres_data/ redis_data/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方法3: ソースからビルド
|
||||||
|
|
||||||
|
開発やカスタマイズのためにソースコードからビルドして実行します。
|
||||||
|
|
||||||
|
#### 前提条件
|
||||||
|
|
||||||
|
- Go 1.21+
|
||||||
|
- Node.js 18+
|
||||||
|
- PostgreSQL 15+
|
||||||
|
- Redis 7+
|
||||||
|
|
||||||
|
#### ビルド手順
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. リポジトリをクローン
|
||||||
|
git clone https://github.com/Wei-Shaw/sub2api.git
|
||||||
|
cd sub2api
|
||||||
|
|
||||||
|
# 2. pnpm をインストール(未インストールの場合)
|
||||||
|
npm install -g pnpm
|
||||||
|
|
||||||
|
# 3. フロントエンドをビルド
|
||||||
|
cd frontend
|
||||||
|
pnpm install
|
||||||
|
pnpm run build
|
||||||
|
# 出力先: ../backend/internal/web/dist/
|
||||||
|
|
||||||
|
# 4. フロントエンドを組み込んだバックエンドをビルド
|
||||||
|
cd ../backend
|
||||||
|
go build -tags embed -o sub2api ./cmd/server
|
||||||
|
|
||||||
|
# 5. 設定ファイルを作成
|
||||||
|
cp ../deploy/config.example.yaml ./config.yaml
|
||||||
|
|
||||||
|
# 6. 設定を編集
|
||||||
|
nano config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意:** `-tags embed` フラグはフロントエンドをバイナリに組み込みます。このフラグがない場合、バイナリはフロントエンド UI を提供しません。
|
||||||
|
|
||||||
|
**`config.yaml` の主要設定:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
host: "0.0.0.0"
|
||||||
|
port: 8080
|
||||||
|
mode: "release"
|
||||||
|
|
||||||
|
database:
|
||||||
|
host: "localhost"
|
||||||
|
port: 5432
|
||||||
|
user: "postgres"
|
||||||
|
password: "your_password"
|
||||||
|
dbname: "sub2api"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "localhost"
|
||||||
|
port: 6379
|
||||||
|
password: ""
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: "change-this-to-a-secure-random-string"
|
||||||
|
expire_hour: 24
|
||||||
|
|
||||||
|
default:
|
||||||
|
user_concurrency: 5
|
||||||
|
user_balance: 0
|
||||||
|
api_key_prefix: "sk-"
|
||||||
|
rate_multiplier: 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sora ステータス(一時的に利用不可)
|
||||||
|
|
||||||
|
> ⚠️ Sora 関連の機能は、上流統合およびメディア配信の技術的問題により一時的に利用できません。
|
||||||
|
> 現時点では本番環境で Sora に依存しないでください。
|
||||||
|
> 既存の `gateway.sora_*` 設定キーは予約されていますが、これらの問題が解決されるまで有効にならない場合があります。
|
||||||
|
|
||||||
|
`config.yaml` では追加のセキュリティ関連オプションも利用できます:
|
||||||
|
|
||||||
|
- `cors.allowed_origins` - CORS 許可リスト
|
||||||
|
- `security.url_allowlist` - 上流/価格/CRS ホストの許可リスト
|
||||||
|
- `security.url_allowlist.enabled` - URL バリデーションの無効化(注意して使用)
|
||||||
|
- `security.url_allowlist.allow_insecure_http` - バリデーション無効時に HTTP URL を許可
|
||||||
|
- `security.url_allowlist.allow_private_hosts` - プライベート/ローカル IP アドレスを許可
|
||||||
|
- `security.response_headers.enabled` - 設定可能なレスポンスヘッダーフィルタリングを有効化(無効時はデフォルトの許可リストを使用)
|
||||||
|
- `security.csp` - Content-Security-Policy ヘッダーの制御
|
||||||
|
- `billing.circuit_breaker` - 課金エラー時にフェイルクローズ
|
||||||
|
- `server.trusted_proxies` - X-Forwarded-For パースの有効化
|
||||||
|
- `turnstile.required` - リリースモードでの Turnstile 必須化
|
||||||
|
|
||||||
|
**⚠️ セキュリティ警告: HTTP URL 設定**
|
||||||
|
|
||||||
|
`security.url_allowlist.enabled=false` の場合、システムはデフォルトで最小限の URL バリデーションを行い、**HTTP URL を拒否**して HTTPS のみを許可します。HTTP URL を許可するには(開発環境や内部テスト用など)、以下を明示的に設定する必要があります:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
security:
|
||||||
|
url_allowlist:
|
||||||
|
enabled: false # 許可リストチェックを無効化
|
||||||
|
allow_insecure_http: true # HTTP URL を許可(⚠️ セキュリティリスクあり)
|
||||||
|
```
|
||||||
|
|
||||||
|
**または環境変数で設定:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SECURITY_URL_ALLOWLIST_ENABLED=false
|
||||||
|
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**HTTP を許可するリスク:**
|
||||||
|
- API キーとデータが**平文**で送信される(傍受の危険性)
|
||||||
|
- **中間者攻撃(MITM)**を受けやすい
|
||||||
|
- **本番環境には不適切**
|
||||||
|
|
||||||
|
**HTTP を使用すべき場面:**
|
||||||
|
- ✅ ローカルサーバーでの開発・テスト(http://localhost)
|
||||||
|
- ✅ 信頼できるエンドポイントを持つ内部ネットワーク
|
||||||
|
- ✅ HTTPS 取得前のアカウント接続テスト
|
||||||
|
- ❌ 本番環境(HTTPS のみを使用)
|
||||||
|
|
||||||
|
**この設定なしで表示されるエラー例:**
|
||||||
|
```
|
||||||
|
Invalid base URL: invalid url scheme: http
|
||||||
|
```
|
||||||
|
|
||||||
|
URL バリデーションまたはレスポンスヘッダーフィルタリングを無効にする場合は、ネットワーク層を強化してください:
|
||||||
|
- 上流ドメイン/IP のエグレス許可リストを適用
|
||||||
|
- プライベート/ループバック/リンクローカル範囲をブロック
|
||||||
|
- TLS のみのアウトバウンドトラフィックを強制
|
||||||
|
- プロキシで機密性の高い上流レスポンスヘッダーを除去
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 6. アプリケーションを実行
|
||||||
|
./sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 開発モード
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# バックエンド(ホットリロード付き)
|
||||||
|
cd backend
|
||||||
|
go run ./cmd/server
|
||||||
|
|
||||||
|
# フロントエンド(ホットリロード付き)
|
||||||
|
cd frontend
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### コード生成
|
||||||
|
|
||||||
|
`backend/ent/schema` を編集した場合、Ent + Wire を再生成してください:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
go generate ./ent
|
||||||
|
go generate ./cmd/server
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## シンプルモード
|
||||||
|
|
||||||
|
シンプルモードは、フル SaaS 機能を必要とせず、素早くアクセスしたい個人開発者や社内チーム向けに設計されています。
|
||||||
|
|
||||||
|
- 有効化: 環境変数 `RUN_MODE=simple` を設定
|
||||||
|
- 違い: SaaS 関連機能を非表示にし、課金プロセスをスキップ
|
||||||
|
- セキュリティに関する注意: 本番環境では `SIMPLE_MODE_CONFIRM=true` も設定する必要があります
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Antigravity サポート
|
||||||
|
|
||||||
|
Sub2API は [Antigravity](https://antigravity.so/) アカウントをサポートしています。認証後、Claude および Gemini モデル用の専用エンドポイントが利用可能になります。
|
||||||
|
|
||||||
|
### 専用エンドポイント
|
||||||
|
|
||||||
|
| エンドポイント | モデル |
|
||||||
|
|----------|-------|
|
||||||
|
| `/antigravity/v1/messages` | Claude モデル |
|
||||||
|
| `/antigravity/v1beta/` | Gemini モデル |
|
||||||
|
|
||||||
|
### Claude Code の設定
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
|
||||||
|
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
### ハイブリッドスケジューリングモード
|
||||||
|
|
||||||
|
Antigravity アカウントはオプションの**ハイブリッドスケジューリング**をサポートしています。有効にすると、汎用エンドポイント `/v1/messages` および `/v1beta/` も Antigravity アカウントにリクエストをルーティングします。
|
||||||
|
|
||||||
|
> **⚠️ 警告**: Anthropic Claude と Antigravity Claude は**同じ会話コンテキスト内で混在させることはできません**。グループを使用して適切に分離してください。
|
||||||
|
|
||||||
|
### 既知の問題
|
||||||
|
|
||||||
|
Claude Code では、Plan Mode を自動的に終了できません。(通常、ネイティブの Claude API を使用する場合、計画が完了すると Claude Code はユーザーに計画を承認または拒否するオプションをポップアップ表示します。)
|
||||||
|
|
||||||
|
**回避策**: `Shift + Tab` を押して手動で Plan Mode を終了し、計画を承認または拒否するためのレスポンスを入力してください。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## プロジェクト構成
|
||||||
|
|
||||||
|
```
|
||||||
|
sub2api/
|
||||||
|
├── backend/ # Go バックエンドサービス
|
||||||
|
│ ├── cmd/server/ # アプリケーションエントリ
|
||||||
|
│ ├── internal/ # 内部モジュール
|
||||||
|
│ │ ├── config/ # 設定
|
||||||
|
│ │ ├── model/ # データモデル
|
||||||
|
│ │ ├── service/ # ビジネスロジック
|
||||||
|
│ │ ├── handler/ # HTTP ハンドラー
|
||||||
|
│ │ └── gateway/ # API ゲートウェイコア
|
||||||
|
│ └── resources/ # 静的リソース
|
||||||
|
│
|
||||||
|
├── frontend/ # Vue 3 フロントエンド
|
||||||
|
│ └── src/
|
||||||
|
│ ├── api/ # API 呼び出し
|
||||||
|
│ ├── stores/ # 状態管理
|
||||||
|
│ ├── views/ # ページコンポーネント
|
||||||
|
│ └── components/ # 再利用可能なコンポーネント
|
||||||
|
│
|
||||||
|
└── deploy/ # デプロイファイル
|
||||||
|
├── docker-compose.yml # Docker Compose 設定
|
||||||
|
├── .env.example # Docker Compose 用環境変数
|
||||||
|
├── config.example.yaml # バイナリデプロイ用フル設定ファイル
|
||||||
|
└── install.sh # ワンクリックインストールスクリプト
|
||||||
|
```
|
||||||
|
|
||||||
|
## 免責事項
|
||||||
|
|
||||||
|
> **本プロジェクトをご利用の前に、以下をよくお読みください:**
|
||||||
|
>
|
||||||
|
> :rotating_light: **利用規約違反のリスク**: 本プロジェクトの使用は Anthropic の利用規約に違反する可能性があります。使用前に Anthropic のユーザー契約をよくお読みください。本プロジェクトの使用に起因するすべてのリスクは、ユーザー自身が負うものとします。
|
||||||
|
>
|
||||||
|
> :book: **免責事項**: 本プロジェクトは技術的な学習および研究目的のみで提供されています。作者は、本プロジェクトの使用によるアカウント停止、サービス中断、その他の損失について一切の責任を負いません。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## スター履歴
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ライセンス
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**このプロジェクトが役に立ったら、ぜひスターをお願いします!**
|
||||||
|
|
||||||
|
</div>
|
||||||
BIN
assets/partners/logos/packycode.png
Normal file
BIN
assets/partners/logos/packycode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
assets/partners/logos/pincc-logo.png
Normal file
BIN
assets/partners/logos/pincc-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
@@ -93,20 +93,13 @@ linters:
|
|||||||
check-escaping-errors: true
|
check-escaping-errors: true
|
||||||
staticcheck:
|
staticcheck:
|
||||||
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
|
# https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
|
||||||
# Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
|
|
||||||
dot-import-whitelist:
|
dot-import-whitelist:
|
||||||
- fmt
|
- fmt
|
||||||
# https://staticcheck.dev/docs/configuration/options/#initialisms
|
# https://staticcheck.dev/docs/configuration/options/#initialisms
|
||||||
# Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
|
|
||||||
initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS" ]
|
initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS" ]
|
||||||
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
|
# https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
|
||||||
# Default: ["200", "400", "404", "500"]
|
|
||||||
http-status-code-whitelist: [ "200", "400", "404", "500" ]
|
http-status-code-whitelist: [ "200", "400", "404", "500" ]
|
||||||
# SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks
|
# "all" enables every SA/ST/S/QF check; only list the ones to disable.
|
||||||
# Example (to disable some checks): [ "all", "-SA1000", "-SA1001"]
|
|
||||||
# Run `GL_DEBUG=staticcheck golangci-lint run --enable=staticcheck` to see all available checks and enabled by config checks.
|
|
||||||
# Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
|
|
||||||
# Temporarily disable style checks to allow CI to pass
|
|
||||||
checks:
|
checks:
|
||||||
- all
|
- all
|
||||||
- -ST1000 # Package comment format
|
- -ST1000 # Package comment format
|
||||||
@@ -114,489 +107,19 @@ linters:
|
|||||||
- -ST1020 # Comment on exported method format
|
- -ST1020 # Comment on exported method format
|
||||||
- -ST1021 # Comment on exported type format
|
- -ST1021 # Comment on exported type format
|
||||||
- -ST1022 # Comment on exported variable format
|
- -ST1022 # Comment on exported variable format
|
||||||
# Invalid regular expression.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1000
|
|
||||||
- SA1000
|
|
||||||
# Invalid template.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1001
|
|
||||||
- SA1001
|
|
||||||
# Invalid format in 'time.Parse'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1002
|
|
||||||
- SA1002
|
|
||||||
# Unsupported argument to functions in 'encoding/binary'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1003
|
|
||||||
- SA1003
|
|
||||||
# Suspiciously small untyped constant in 'time.Sleep'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1004
|
|
||||||
- SA1004
|
|
||||||
# Invalid first argument to 'exec.Command'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1005
|
|
||||||
- SA1005
|
|
||||||
# 'Printf' with dynamic first argument and no further arguments.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1006
|
|
||||||
- SA1006
|
|
||||||
# Invalid URL in 'net/url.Parse'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1007
|
|
||||||
- SA1007
|
|
||||||
# Non-canonical key in 'http.Header' map.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1008
|
|
||||||
- SA1008
|
|
||||||
# '(*regexp.Regexp).FindAll' called with 'n == 0', which will always return zero results.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1010
|
|
||||||
- SA1010
|
|
||||||
# Various methods in the "strings" package expect valid UTF-8, but invalid input is provided.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1011
|
|
||||||
- SA1011
|
|
||||||
# A nil 'context.Context' is being passed to a function, consider using 'context.TODO' instead.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1012
|
|
||||||
- SA1012
|
|
||||||
# 'io.Seeker.Seek' is being called with the whence constant as the first argument, but it should be the second.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1013
|
|
||||||
- SA1013
|
|
||||||
# Non-pointer value passed to 'Unmarshal' or 'Decode'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1014
|
|
||||||
- SA1014
|
|
||||||
# Using 'time.Tick' in a way that will leak. Consider using 'time.NewTicker', and only use 'time.Tick' in tests, commands and endless functions.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1015
|
|
||||||
- SA1015
|
|
||||||
# Trapping a signal that cannot be trapped.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1016
|
|
||||||
- SA1016
|
|
||||||
# Channels used with 'os/signal.Notify' should be buffered.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1017
|
|
||||||
- SA1017
|
|
||||||
# 'strings.Replace' called with 'n == 0', which does nothing.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1018
|
|
||||||
- SA1018
|
|
||||||
# Using a deprecated function, variable, constant or field.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1019
|
|
||||||
- SA1019
|
|
||||||
# Using an invalid host:port pair with a 'net.Listen'-related function.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1020
|
|
||||||
- SA1020
|
|
||||||
# Using 'bytes.Equal' to compare two 'net.IP'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1021
|
|
||||||
- SA1021
|
|
||||||
# Modifying the buffer in an 'io.Writer' implementation.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1023
|
|
||||||
- SA1023
|
|
||||||
# A string cutset contains duplicate characters.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1024
|
|
||||||
- SA1024
|
|
||||||
# It is not possible to use '(*time.Timer).Reset''s return value correctly.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1025
|
|
||||||
- SA1025
|
|
||||||
# Cannot marshal channels or functions.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1026
|
|
||||||
- SA1026
|
|
||||||
# Atomic access to 64-bit variable must be 64-bit aligned.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1027
|
|
||||||
- SA1027
|
|
||||||
# 'sort.Slice' can only be used on slices.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1028
|
|
||||||
- SA1028
|
|
||||||
# Inappropriate key in call to 'context.WithValue'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1029
|
|
||||||
- SA1029
|
|
||||||
# Invalid argument in call to a 'strconv' function.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1030
|
|
||||||
- SA1030
|
|
||||||
# Overlapping byte slices passed to an encoder.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1031
|
|
||||||
- SA1031
|
|
||||||
# Wrong order of arguments to 'errors.Is'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA1032
|
|
||||||
- SA1032
|
|
||||||
# 'sync.WaitGroup.Add' called inside the goroutine, leading to a race condition.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA2000
|
|
||||||
- SA2000
|
|
||||||
# Empty critical section, did you mean to defer the unlock?.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA2001
|
|
||||||
- SA2001
|
|
||||||
# Called 'testing.T.FailNow' or 'SkipNow' in a goroutine, which isn't allowed.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA2002
|
|
||||||
- SA2002
|
|
||||||
# Deferred 'Lock' right after locking, likely meant to defer 'Unlock' instead.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA2003
|
|
||||||
- SA2003
|
|
||||||
# 'TestMain' doesn't call 'os.Exit', hiding test failures.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA3000
|
|
||||||
- SA3000
|
|
||||||
# Assigning to 'b.N' in benchmarks distorts the results.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA3001
|
|
||||||
- SA3001
|
|
||||||
# Binary operator has identical expressions on both sides.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4000
|
|
||||||
- SA4000
|
|
||||||
# '&*x' gets simplified to 'x', it does not copy 'x'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4001
|
|
||||||
- SA4001
|
|
||||||
# Comparing unsigned values against negative values is pointless.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4003
|
|
||||||
- SA4003
|
|
||||||
# The loop exits unconditionally after one iteration.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4004
|
|
||||||
- SA4004
|
|
||||||
# Field assignment that will never be observed. Did you mean to use a pointer receiver?.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4005
|
|
||||||
- SA4005
|
|
||||||
# A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4006
|
|
||||||
- SA4006
|
|
||||||
# The variable in the loop condition never changes, are you incrementing the wrong variable?.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4008
|
|
||||||
- SA4008
|
|
||||||
# A function argument is overwritten before its first use.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4009
|
|
||||||
- SA4009
|
|
||||||
# The result of 'append' will never be observed anywhere.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4010
|
|
||||||
- SA4010
|
|
||||||
# Break statement with no effect. Did you mean to break out of an outer loop?.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4011
|
|
||||||
- SA4011
|
|
||||||
# Comparing a value against NaN even though no value is equal to NaN.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4012
|
|
||||||
- SA4012
|
|
||||||
# Negating a boolean twice ('!!b') is the same as writing 'b'. This is either redundant, or a typo.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4013
|
|
||||||
- SA4013
|
|
||||||
# An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4014
|
|
||||||
- SA4014
|
|
||||||
# Calling functions like 'math.Ceil' on floats converted from integers doesn't do anything useful.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4015
|
|
||||||
- SA4015
|
|
||||||
# Certain bitwise operations, such as 'x ^ 0', do not do anything useful.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4016
|
|
||||||
- SA4016
|
|
||||||
# Discarding the return values of a function without side effects, making the call pointless.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4017
|
|
||||||
- SA4017
|
|
||||||
# Self-assignment of variables.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4018
|
|
||||||
- SA4018
|
|
||||||
# Multiple, identical build constraints in the same file.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4019
|
|
||||||
- SA4019
|
|
||||||
# Unreachable case clause in a type switch.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4020
|
|
||||||
- SA4020
|
|
||||||
# "x = append(y)" is equivalent to "x = y".
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4021
|
|
||||||
- SA4021
|
|
||||||
# Comparing the address of a variable against nil.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4022
|
|
||||||
- SA4022
|
|
||||||
# Impossible comparison of interface value with untyped nil.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4023
|
|
||||||
- SA4023
|
|
||||||
# Checking for impossible return value from a builtin function.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4024
|
|
||||||
- SA4024
|
|
||||||
# Integer division of literals that results in zero.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4025
|
|
||||||
- SA4025
|
|
||||||
# Go constants cannot express negative zero.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4026
|
|
||||||
- SA4026
|
|
||||||
# '(*net/url.URL).Query' returns a copy, modifying it doesn't change the URL.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4027
|
|
||||||
- SA4027
|
|
||||||
# 'x % 1' is always zero.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4028
|
|
||||||
- SA4028
|
|
||||||
# Ineffective attempt at sorting slice.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4029
|
|
||||||
- SA4029
|
|
||||||
# Ineffective attempt at generating random number.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4030
|
|
||||||
- SA4030
|
|
||||||
# Checking never-nil value against nil.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4031
|
|
||||||
- SA4031
|
|
||||||
# Comparing 'runtime.GOOS' or 'runtime.GOARCH' against impossible value.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA4032
|
|
||||||
- SA4032
|
|
||||||
# Assignment to nil map.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5000
|
|
||||||
- SA5000
|
|
||||||
# Deferring 'Close' before checking for a possible error.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5001
|
|
||||||
- SA5001
|
|
||||||
# The empty for loop ("for {}") spins and can block the scheduler.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5002
|
|
||||||
- SA5002
|
|
||||||
# Defers in infinite loops will never execute.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5003
|
|
||||||
- SA5003
|
|
||||||
# "for { select { ..." with an empty default branch spins.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5004
|
|
||||||
- SA5004
|
|
||||||
# The finalizer references the finalized object, preventing garbage collection.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5005
|
|
||||||
- SA5005
|
|
||||||
# Infinite recursive call.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5007
|
|
||||||
- SA5007
|
|
||||||
# Invalid struct tag.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5008
|
|
||||||
- SA5008
|
|
||||||
# Invalid Printf call.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5009
|
|
||||||
- SA5009
|
|
||||||
# Impossible type assertion.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5010
|
|
||||||
- SA5010
|
|
||||||
# Possible nil pointer dereference.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5011
|
|
||||||
- SA5011
|
|
||||||
# Passing odd-sized slice to function expecting even size.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA5012
|
|
||||||
- SA5012
|
|
||||||
# Using 'regexp.Match' or related in a loop, should use 'regexp.Compile'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6000
|
|
||||||
- SA6000
|
|
||||||
# Missing an optimization opportunity when indexing maps by byte slices.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6001
|
|
||||||
- SA6001
|
|
||||||
# Storing non-pointer values in 'sync.Pool' allocates memory.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6002
|
|
||||||
- SA6002
|
|
||||||
# Converting a string to a slice of runes before ranging over it.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6003
|
|
||||||
- SA6003
|
|
||||||
# Inefficient string comparison with 'strings.ToLower' or 'strings.ToUpper'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6005
|
|
||||||
- SA6005
|
|
||||||
# Using io.WriteString to write '[]byte'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA6006
|
|
||||||
- SA6006
|
|
||||||
# Defers in range loops may not run when you expect them to.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9001
|
|
||||||
- SA9001
|
|
||||||
# Using a non-octal 'os.FileMode' that looks like it was meant to be in octal.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9002
|
|
||||||
- SA9002
|
|
||||||
# Empty body in an if or else branch.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9003
|
|
||||||
- SA9003
|
|
||||||
# Only the first constant has an explicit type.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9004
|
|
||||||
- SA9004
|
|
||||||
# Trying to marshal a struct with no public fields nor custom marshaling.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9005
|
|
||||||
- SA9005
|
|
||||||
# Dubious bit shifting of a fixed size integer value.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9006
|
|
||||||
- SA9006
|
|
||||||
# Deleting a directory that shouldn't be deleted.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9007
|
|
||||||
- SA9007
|
|
||||||
# 'else' branch of a type assertion is probably not reading the right value.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9008
|
|
||||||
- SA9008
|
|
||||||
# Ineffectual Go compiler directive.
|
|
||||||
# https://staticcheck.dev/docs/checks/#SA9009
|
|
||||||
- SA9009
|
|
||||||
# NOTE: ST1000, ST1001, ST1003, ST1020, ST1021, ST1022 are disabled above
|
|
||||||
# Incorrectly formatted error string.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1005
|
|
||||||
- ST1005
|
|
||||||
# Poorly chosen receiver name.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1006
|
|
||||||
- ST1006
|
|
||||||
# A function's error value should be its last return value.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1008
|
|
||||||
- ST1008
|
|
||||||
# Poorly chosen name for variable of type 'time.Duration'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1011
|
|
||||||
- ST1011
|
|
||||||
# Poorly chosen name for error variable.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1012
|
|
||||||
- ST1012
|
|
||||||
# Should use constants for HTTP error codes, not magic numbers.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1013
|
|
||||||
- ST1013
|
|
||||||
# A switch's default case should be the first or last case.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1015
|
|
||||||
- ST1015
|
|
||||||
# Use consistent method receiver names.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1016
|
|
||||||
- ST1016
|
|
||||||
# Don't use Yoda conditions.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1017
|
|
||||||
- ST1017
|
|
||||||
# Avoid zero-width and control characters in string literals.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1018
|
|
||||||
- ST1018
|
|
||||||
# Importing the same package multiple times.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1019
|
|
||||||
- ST1019
|
|
||||||
# NOTE: ST1020, ST1021, ST1022 removed (disabled above)
|
|
||||||
# Redundant type in variable declaration.
|
|
||||||
# https://staticcheck.dev/docs/checks/#ST1023
|
|
||||||
- ST1023
|
|
||||||
# Use plain channel send or receive instead of single-case select.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1000
|
|
||||||
- S1000
|
|
||||||
# Replace for loop with call to copy.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1001
|
|
||||||
- S1001
|
|
||||||
# Omit comparison with boolean constant.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1002
|
|
||||||
- S1002
|
|
||||||
# Replace call to 'strings.Index' with 'strings.Contains'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1003
|
|
||||||
- S1003
|
|
||||||
# Replace call to 'bytes.Compare' with 'bytes.Equal'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1004
|
|
||||||
- S1004
|
|
||||||
# Drop unnecessary use of the blank identifier.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1005
|
|
||||||
- S1005
|
|
||||||
# Use "for { ... }" for infinite loops.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1006
|
|
||||||
- S1006
|
|
||||||
# Simplify regular expression by using raw string literal.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1007
|
|
||||||
- S1007
|
|
||||||
# Simplify returning boolean expression.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1008
|
|
||||||
- S1008
|
|
||||||
# Omit redundant nil check on slices, maps, and channels.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1009
|
|
||||||
- S1009
|
|
||||||
# Omit default slice index.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1010
|
|
||||||
- S1010
|
|
||||||
# Use a single 'append' to concatenate two slices.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1011
|
|
||||||
- S1011
|
|
||||||
# Replace 'time.Now().Sub(x)' with 'time.Since(x)'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1012
|
|
||||||
- S1012
|
|
||||||
# Use a type conversion instead of manually copying struct fields.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1016
|
|
||||||
- S1016
|
|
||||||
# Replace manual trimming with 'strings.TrimPrefix'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1017
|
|
||||||
- S1017
|
|
||||||
# Use "copy" for sliding elements.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1018
|
|
||||||
- S1018
|
|
||||||
# Simplify "make" call by omitting redundant arguments.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1019
|
|
||||||
- S1019
|
|
||||||
# Omit redundant nil check in type assertion.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1020
|
|
||||||
- S1020
|
|
||||||
# Merge variable declaration and assignment.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1021
|
|
||||||
- S1021
|
|
||||||
# Omit redundant control flow.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1023
|
|
||||||
- S1023
|
|
||||||
# Replace 'x.Sub(time.Now())' with 'time.Until(x)'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1024
|
|
||||||
- S1024
|
|
||||||
# Don't use 'fmt.Sprintf("%s", x)' unnecessarily.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1025
|
|
||||||
- S1025
|
|
||||||
# Simplify error construction with 'fmt.Errorf'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1028
|
|
||||||
- S1028
|
|
||||||
# Range over the string directly.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1029
|
|
||||||
- S1029
|
|
||||||
# Use 'bytes.Buffer.String' or 'bytes.Buffer.Bytes'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1030
|
|
||||||
- S1030
|
|
||||||
# Omit redundant nil check around loop.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1031
|
|
||||||
- S1031
|
|
||||||
# Use 'sort.Ints(x)', 'sort.Float64s(x)', and 'sort.Strings(x)'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1032
|
|
||||||
- S1032
|
|
||||||
# Unnecessary guard around call to "delete".
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1033
|
|
||||||
- S1033
|
|
||||||
# Use result of type assertion to simplify cases.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1034
|
|
||||||
- S1034
|
|
||||||
# Redundant call to 'net/http.CanonicalHeaderKey' in method call on 'net/http.Header'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1035
|
|
||||||
- S1035
|
|
||||||
# Unnecessary guard around map access.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1036
|
|
||||||
- S1036
|
|
||||||
# Elaborate way of sleeping.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1037
|
|
||||||
- S1037
|
|
||||||
# Unnecessarily complex way of printing formatted string.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1038
|
|
||||||
- S1038
|
|
||||||
# Unnecessary use of 'fmt.Sprint'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1039
|
|
||||||
- S1039
|
|
||||||
# Type assertion to current type.
|
|
||||||
# https://staticcheck.dev/docs/checks/#S1040
|
|
||||||
- S1040
|
|
||||||
# Apply De Morgan's law.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1001
|
|
||||||
- QF1001
|
|
||||||
# Convert untagged switch to tagged switch.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1002
|
|
||||||
- QF1002
|
|
||||||
# Convert if/else-if chain to tagged switch.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1003
|
|
||||||
- QF1003
|
|
||||||
# Use 'strings.ReplaceAll' instead of 'strings.Replace' with 'n == -1'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1004
|
|
||||||
- QF1004
|
|
||||||
# Expand call to 'math.Pow'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1005
|
|
||||||
- QF1005
|
|
||||||
# Lift 'if'+'break' into loop condition.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1006
|
|
||||||
- QF1006
|
|
||||||
# Merge conditional assignment into variable declaration.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1007
|
|
||||||
- QF1007
|
|
||||||
# Omit embedded fields from selector expression.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1008
|
|
||||||
- QF1008
|
|
||||||
# Use 'time.Time.Equal' instead of '==' operator.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1009
|
|
||||||
- QF1009
|
|
||||||
# Convert slice of bytes to string when printing it.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1010
|
|
||||||
- QF1010
|
|
||||||
# Omit redundant type from variable declaration.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1011
|
|
||||||
- QF1011
|
|
||||||
# Use 'fmt.Fprintf(x, ...)' instead of 'x.Write(fmt.Sprintf(...))'.
|
|
||||||
# https://staticcheck.dev/docs/checks/#QF1012
|
|
||||||
- QF1012
|
|
||||||
unused:
|
unused:
|
||||||
# Mark all struct fields that have been written to as used.
|
|
||||||
# Default: true
|
# Default: true
|
||||||
field-writes-are-uses: false
|
field-writes-are-uses: true
|
||||||
# Treat IncDec statement (e.g. `i++` or `i--`) as both read and write operation instead of just write.
|
|
||||||
# Default: false
|
# Default: false
|
||||||
post-statements-are-reads: true
|
post-statements-are-reads: true
|
||||||
# Mark all exported fields as used.
|
|
||||||
# default: true
|
|
||||||
exported-fields-are-used: false
|
|
||||||
# Mark all function parameters as used.
|
|
||||||
# default: true
|
|
||||||
parameters-are-used: true
|
|
||||||
# Mark all local variables as used.
|
|
||||||
# default: true
|
|
||||||
local-variables-are-used: false
|
|
||||||
# Mark all identifiers inside generated files as used.
|
|
||||||
# Default: true
|
# Default: true
|
||||||
generated-is-used: false
|
exported-fields-are-used: true
|
||||||
|
# Default: true
|
||||||
|
parameters-are-used: true
|
||||||
|
# Default: true
|
||||||
|
local-variables-are-used: false
|
||||||
|
# Default: true — must be true, ent generates 130K+ lines of code
|
||||||
|
generated-is-used: true
|
||||||
|
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
userRepo := repository.NewUserRepository(client, sqlDB)
|
userRepo := repository.NewUserRepository(client, sqlDB)
|
||||||
authService := service.NewAuthService(userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil)
|
authService := service.NewAuthService(client, userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.1.88
|
0.1.106
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
// Server layer ProviderSet
|
// Server layer ProviderSet
|
||||||
server.ProviderSet,
|
server.ProviderSet,
|
||||||
|
|
||||||
|
// Privacy client factory for OpenAI training opt-out
|
||||||
|
providePrivacyClientFactory,
|
||||||
|
|
||||||
// BuildInfo provider
|
// BuildInfo provider
|
||||||
provideServiceBuildInfo,
|
provideServiceBuildInfo,
|
||||||
|
|
||||||
@@ -53,6 +56,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providePrivacyClientFactory() service.PrivacyClientFactory {
|
||||||
|
return repository.CreatePrivacyReqClient
|
||||||
|
}
|
||||||
|
|
||||||
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
||||||
return service.BuildInfo{
|
return service.BuildInfo{
|
||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
@@ -87,6 +94,7 @@ func provideCleanup(
|
|||||||
antigravityOAuth *service.AntigravityOAuthService,
|
antigravityOAuth *service.AntigravityOAuthService,
|
||||||
openAIGateway *service.OpenAIGatewayService,
|
openAIGateway *service.OpenAIGatewayService,
|
||||||
scheduledTestRunner *service.ScheduledTestRunnerService,
|
scheduledTestRunner *service.ScheduledTestRunnerService,
|
||||||
|
backupSvc *service.BackupService,
|
||||||
) func() {
|
) func() {
|
||||||
return func() {
|
return func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
@@ -223,6 +231,12 @@ func provideCleanup(
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
|
{"BackupService", func() error {
|
||||||
|
if backupSvc != nil {
|
||||||
|
backupSvc.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
infraSteps := []cleanupStep{
|
infraSteps := []cleanupStep{
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
refreshTokenCache := repository.NewRefreshTokenCache(redisClient)
|
refreshTokenCache := repository.NewRefreshTokenCache(redisClient)
|
||||||
settingRepository := repository.NewSettingRepository(client)
|
settingRepository := repository.NewSettingRepository(client)
|
||||||
groupRepository := repository.NewGroupRepository(client, db)
|
groupRepository := repository.NewGroupRepository(client, db)
|
||||||
|
channelRepository := repository.NewChannelRepository(db)
|
||||||
settingService := service.ProvideSettingService(settingRepository, groupRepository, configConfig)
|
settingService := service.ProvideSettingService(settingRepository, groupRepository, configConfig)
|
||||||
emailCache := repository.NewEmailCache(redisClient)
|
emailCache := repository.NewEmailCache(redisClient)
|
||||||
emailService := service.NewEmailService(settingRepository, emailCache)
|
emailService := service.NewEmailService(settingRepository, emailCache)
|
||||||
@@ -67,7 +68,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
|
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
|
||||||
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||||
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
|
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)
|
||||||
authService := service.NewAuthService(userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService, subscriptionService)
|
authService := service.NewAuthService(client, userRepository, redeemCodeRepository, refreshTokenCache, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService, subscriptionService)
|
||||||
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache)
|
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache)
|
||||||
redeemCache := repository.NewRedeemCache(redisClient)
|
redeemCache := repository.NewRedeemCache(redisClient)
|
||||||
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
|
||||||
@@ -81,6 +82,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
userHandler := handler.NewUserHandler(userService)
|
userHandler := handler.NewUserHandler(userService)
|
||||||
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
||||||
usageLogRepository := repository.NewUsageLogRepository(client, db)
|
usageLogRepository := repository.NewUsageLogRepository(client, db)
|
||||||
|
usageBillingRepository := repository.NewUsageBillingRepository(client, db)
|
||||||
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
|
usageService := service.NewUsageService(usageLogRepository, userRepository, client, apiKeyAuthCacheInvalidator)
|
||||||
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
|
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
|
||||||
redeemHandler := handler.NewRedeemHandler(redeemService)
|
redeemHandler := handler.NewRedeemHandler(redeemService)
|
||||||
@@ -104,15 +106,16 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
proxyRepository := repository.NewProxyRepository(client, db)
|
proxyRepository := repository.NewProxyRepository(client, db)
|
||||||
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
|
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
|
||||||
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
|
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
|
||||||
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, soraAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService)
|
privacyClientFactory := providePrivacyClientFactory()
|
||||||
|
adminService := service.NewAdminService(userRepository, groupRepository, accountRepository, soraAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory)
|
||||||
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
|
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
|
||||||
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
|
concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
|
||||||
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService)
|
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService)
|
||||||
groupHandler := admin.NewGroupHandler(adminService)
|
|
||||||
claudeOAuthClient := repository.NewClaudeOAuthClient()
|
claudeOAuthClient := repository.NewClaudeOAuthClient()
|
||||||
oAuthService := service.NewOAuthService(proxyRepository, claudeOAuthClient)
|
oAuthService := service.NewOAuthService(proxyRepository, claudeOAuthClient)
|
||||||
openAIOAuthClient := repository.NewOpenAIOAuthClient()
|
openAIOAuthClient := repository.NewOpenAIOAuthClient()
|
||||||
openAIOAuthService := service.NewOpenAIOAuthService(proxyRepository, openAIOAuthClient)
|
openAIOAuthService := service.NewOpenAIOAuthService(proxyRepository, openAIOAuthClient)
|
||||||
|
openAIOAuthService.SetPrivacyClientFactory(privacyClientFactory)
|
||||||
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
|
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
|
||||||
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient()
|
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient()
|
||||||
driveClient := repository.NewGeminiDriveClient()
|
driveClient := repository.NewGeminiDriveClient()
|
||||||
@@ -122,6 +125,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
tempUnschedCache := repository.NewTempUnschedCache(redisClient)
|
tempUnschedCache := repository.NewTempUnschedCache(redisClient)
|
||||||
timeoutCounterCache := repository.NewTimeoutCounterCache(redisClient)
|
timeoutCounterCache := repository.NewTimeoutCounterCache(redisClient)
|
||||||
geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
|
geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
|
||||||
|
oauthRefreshAPI := service.NewOAuthRefreshAPI(accountRepository, geminiTokenCache)
|
||||||
compositeTokenCacheInvalidator := service.NewCompositeTokenCacheInvalidator(geminiTokenCache)
|
compositeTokenCacheInvalidator := service.NewCompositeTokenCacheInvalidator(geminiTokenCache)
|
||||||
rateLimitService := service.ProvideRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache, timeoutCounterCache, settingService, compositeTokenCacheInvalidator)
|
rateLimitService := service.ProvideRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache, timeoutCounterCache, settingService, compositeTokenCacheInvalidator)
|
||||||
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
||||||
@@ -129,21 +133,31 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
|
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
|
||||||
usageCache := service.NewUsageCache()
|
usageCache := service.NewUsageCache()
|
||||||
identityCache := repository.NewIdentityCache(redisClient)
|
identityCache := repository.NewIdentityCache(redisClient)
|
||||||
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache)
|
geminiTokenProvider := service.ProvideGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService, oauthRefreshAPI)
|
||||||
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService)
|
|
||||||
gatewayCache := repository.NewGatewayCache(redisClient)
|
gatewayCache := repository.NewGatewayCache(redisClient)
|
||||||
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
|
schedulerOutboxRepository := repository.NewSchedulerOutboxRepository(db)
|
||||||
schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig)
|
schedulerSnapshotService := service.ProvideSchedulerSnapshotService(schedulerCache, schedulerOutboxRepository, accountRepository, groupRepository, configConfig)
|
||||||
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
|
antigravityTokenProvider := service.ProvideAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService, oauthRefreshAPI, tempUnschedCache)
|
||||||
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, schedulerSnapshotService, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
|
internal500CounterCache := repository.NewInternal500CounterCache(redisClient)
|
||||||
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
|
tlsFingerprintProfileRepository := repository.NewTLSFingerprintProfileRepository(client)
|
||||||
|
tlsFingerprintProfileCache := repository.NewTLSFingerprintProfileCache(redisClient)
|
||||||
|
tlsFingerprintProfileService := service.NewTLSFingerprintProfileService(tlsFingerprintProfileRepository, tlsFingerprintProfileCache)
|
||||||
|
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, usageCache, identityCache, tlsFingerprintProfileService)
|
||||||
|
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, schedulerSnapshotService, antigravityTokenProvider, rateLimitService, httpUpstream, settingService, internal500CounterCache)
|
||||||
|
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService)
|
||||||
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
|
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
|
||||||
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
|
sessionLimitCache := repository.ProvideSessionLimitCache(redisClient, configConfig)
|
||||||
rpmCache := repository.NewRPMCache(redisClient)
|
rpmCache := repository.NewRPMCache(redisClient)
|
||||||
|
groupCapacityService := service.NewGroupCapacityService(accountRepository, groupRepository, concurrencyService, sessionLimitCache, rpmCache)
|
||||||
|
groupHandler := admin.NewGroupHandler(adminService, dashboardService, groupCapacityService)
|
||||||
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator)
|
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator)
|
||||||
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
|
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
|
||||||
dataManagementService := service.NewDataManagementService()
|
dataManagementService := service.NewDataManagementService()
|
||||||
dataManagementHandler := admin.NewDataManagementHandler(dataManagementService)
|
dataManagementHandler := admin.NewDataManagementHandler(dataManagementService)
|
||||||
|
backupObjectStoreFactory := repository.NewS3BackupStoreFactory()
|
||||||
|
dbDumper := repository.NewPgDumper(configConfig)
|
||||||
|
backupService := service.ProvideBackupService(settingRepository, configConfig, secretEncryptor, backupObjectStoreFactory, dbDumper)
|
||||||
|
backupHandler := admin.NewBackupHandler(backupService, userService)
|
||||||
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
||||||
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
||||||
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
geminiOAuthHandler := admin.NewGeminiOAuthHandler(geminiOAuthService)
|
||||||
@@ -160,11 +174,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
billingService := service.NewBillingService(configConfig, pricingService)
|
billingService := service.NewBillingService(configConfig, pricingService)
|
||||||
identityService := service.NewIdentityService(identityCache)
|
identityService := service.NewIdentityService(identityCache)
|
||||||
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
||||||
claudeTokenProvider := service.NewClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService)
|
claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oauthRefreshAPI)
|
||||||
digestSessionStore := service.NewDigestSessionStore()
|
digestSessionStore := service.NewDigestSessionStore()
|
||||||
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore)
|
channelService := service.NewChannelService(channelRepository, apiKeyAuthCacheInvalidator)
|
||||||
openAITokenProvider := service.NewOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService)
|
modelPricingResolver := service.NewModelPricingResolver(channelService, billingService)
|
||||||
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider)
|
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService, channelService, modelPricingResolver)
|
||||||
|
openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oauthRefreshAPI)
|
||||||
|
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, modelPricingResolver, channelService)
|
||||||
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
||||||
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
|
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
|
||||||
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
|
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
|
||||||
@@ -194,12 +210,14 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient)
|
errorPassthroughCache := repository.NewErrorPassthroughCache(redisClient)
|
||||||
errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache)
|
errorPassthroughService := service.NewErrorPassthroughService(errorPassthroughRepository, errorPassthroughCache)
|
||||||
errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService)
|
errorPassthroughHandler := admin.NewErrorPassthroughHandler(errorPassthroughService)
|
||||||
|
tlsFingerprintProfileHandler := admin.NewTLSFingerprintProfileHandler(tlsFingerprintProfileService)
|
||||||
adminAPIKeyHandler := admin.NewAdminAPIKeyHandler(adminService)
|
adminAPIKeyHandler := admin.NewAdminAPIKeyHandler(adminService)
|
||||||
scheduledTestPlanRepository := repository.NewScheduledTestPlanRepository(db)
|
scheduledTestPlanRepository := repository.NewScheduledTestPlanRepository(db)
|
||||||
scheduledTestResultRepository := repository.NewScheduledTestResultRepository(db)
|
scheduledTestResultRepository := repository.NewScheduledTestResultRepository(db)
|
||||||
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
|
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
|
||||||
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
|
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
|
||||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, adminAPIKeyHandler, scheduledTestHandler)
|
channelHandler := admin.NewChannelHandler(channelService, billingService)
|
||||||
|
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler)
|
||||||
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
|
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
|
||||||
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
|
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
|
||||||
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
|
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
|
||||||
@@ -226,11 +244,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig)
|
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig)
|
||||||
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
|
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
|
||||||
soraMediaCleanupService := service.ProvideSoraMediaCleanupService(soraMediaStorage, configConfig)
|
soraMediaCleanupService := service.ProvideSoraMediaCleanupService(soraMediaStorage, configConfig)
|
||||||
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, soraAccountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache)
|
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, soraAccountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oauthRefreshAPI)
|
||||||
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
||||||
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
|
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
|
||||||
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, configConfig)
|
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig)
|
||||||
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, soraMediaCleanupService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService)
|
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, soraMediaCleanupService, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService)
|
||||||
application := &Application{
|
application := &Application{
|
||||||
Server: httpServer,
|
Server: httpServer,
|
||||||
Cleanup: v,
|
Cleanup: v,
|
||||||
@@ -245,6 +263,10 @@ type Application struct {
|
|||||||
Cleanup func()
|
Cleanup func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providePrivacyClientFactory() service.PrivacyClientFactory {
|
||||||
|
return repository.CreatePrivacyReqClient
|
||||||
|
}
|
||||||
|
|
||||||
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
|
||||||
return service.BuildInfo{
|
return service.BuildInfo{
|
||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
@@ -279,6 +301,7 @@ func provideCleanup(
|
|||||||
antigravityOAuth *service.AntigravityOAuthService,
|
antigravityOAuth *service.AntigravityOAuthService,
|
||||||
openAIGateway *service.OpenAIGatewayService,
|
openAIGateway *service.OpenAIGatewayService,
|
||||||
scheduledTestRunner *service.ScheduledTestRunnerService,
|
scheduledTestRunner *service.ScheduledTestRunnerService,
|
||||||
|
backupSvc *service.BackupService,
|
||||||
) func() {
|
) func() {
|
||||||
return func() {
|
return func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
@@ -414,6 +437,12 @@ func provideCleanup(
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
|
{"BackupService", func() error {
|
||||||
|
if backupSvc != nil {
|
||||||
|
backupSvc.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
infraSteps := []cleanupStep{
|
infraSteps := []cleanupStep{
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
|
|||||||
antigravityOAuthSvc,
|
antigravityOAuthSvc,
|
||||||
nil, // openAIGateway
|
nil, // openAIGateway
|
||||||
nil, // scheduledTestRunner
|
nil, // scheduledTestRunner
|
||||||
|
nil, // backupSvc
|
||||||
)
|
)
|
||||||
|
|
||||||
require.NotPanics(t, func() {
|
require.NotPanics(t, func() {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ type Account struct {
|
|||||||
ProxyID *int64 `json:"proxy_id,omitempty"`
|
ProxyID *int64 `json:"proxy_id,omitempty"`
|
||||||
// Concurrency holds the value of the "concurrency" field.
|
// Concurrency holds the value of the "concurrency" field.
|
||||||
Concurrency int `json:"concurrency,omitempty"`
|
Concurrency int `json:"concurrency,omitempty"`
|
||||||
|
// LoadFactor holds the value of the "load_factor" field.
|
||||||
|
LoadFactor *int `json:"load_factor,omitempty"`
|
||||||
// Priority holds the value of the "priority" field.
|
// Priority holds the value of the "priority" field.
|
||||||
Priority int `json:"priority,omitempty"`
|
Priority int `json:"priority,omitempty"`
|
||||||
// RateMultiplier holds the value of the "rate_multiplier" field.
|
// RateMultiplier holds the value of the "rate_multiplier" field.
|
||||||
@@ -143,7 +145,7 @@ func (*Account) scanValues(columns []string) ([]any, error) {
|
|||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
case account.FieldRateMultiplier:
|
case account.FieldRateMultiplier:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
|
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
|
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
@@ -243,6 +245,13 @@ func (_m *Account) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.Concurrency = int(value.Int64)
|
_m.Concurrency = int(value.Int64)
|
||||||
}
|
}
|
||||||
|
case account.FieldLoadFactor:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field load_factor", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.LoadFactor = new(int)
|
||||||
|
*_m.LoadFactor = int(value.Int64)
|
||||||
|
}
|
||||||
case account.FieldPriority:
|
case account.FieldPriority:
|
||||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field priority", values[i])
|
return fmt.Errorf("unexpected type %T for field priority", values[i])
|
||||||
@@ -445,6 +454,11 @@ func (_m *Account) String() string {
|
|||||||
builder.WriteString("concurrency=")
|
builder.WriteString("concurrency=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
|
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
if v := _m.LoadFactor; v != nil {
|
||||||
|
builder.WriteString("load_factor=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
builder.WriteString("priority=")
|
builder.WriteString("priority=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
|
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ const (
|
|||||||
FieldProxyID = "proxy_id"
|
FieldProxyID = "proxy_id"
|
||||||
// FieldConcurrency holds the string denoting the concurrency field in the database.
|
// FieldConcurrency holds the string denoting the concurrency field in the database.
|
||||||
FieldConcurrency = "concurrency"
|
FieldConcurrency = "concurrency"
|
||||||
|
// FieldLoadFactor holds the string denoting the load_factor field in the database.
|
||||||
|
FieldLoadFactor = "load_factor"
|
||||||
// FieldPriority holds the string denoting the priority field in the database.
|
// FieldPriority holds the string denoting the priority field in the database.
|
||||||
FieldPriority = "priority"
|
FieldPriority = "priority"
|
||||||
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
// FieldRateMultiplier holds the string denoting the rate_multiplier field in the database.
|
||||||
@@ -121,6 +123,7 @@ var Columns = []string{
|
|||||||
FieldExtra,
|
FieldExtra,
|
||||||
FieldProxyID,
|
FieldProxyID,
|
||||||
FieldConcurrency,
|
FieldConcurrency,
|
||||||
|
FieldLoadFactor,
|
||||||
FieldPriority,
|
FieldPriority,
|
||||||
FieldRateMultiplier,
|
FieldRateMultiplier,
|
||||||
FieldStatus,
|
FieldStatus,
|
||||||
@@ -250,6 +253,11 @@ func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
|
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByLoadFactor orders the results by the load_factor field.
|
||||||
|
func ByLoadFactor(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldLoadFactor, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByPriority orders the results by the priority field.
|
// ByPriority orders the results by the priority field.
|
||||||
func ByPriority(opts ...sql.OrderTermOption) OrderOption {
|
func ByPriority(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldPriority, opts...).ToFunc()
|
return sql.OrderByField(FieldPriority, opts...).ToFunc()
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ func Concurrency(v int) predicate.Account {
|
|||||||
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
|
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFactor applies equality check predicate on the "load_factor" field. It's identical to LoadFactorEQ.
|
||||||
|
func LoadFactor(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
// Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
|
// Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
|
||||||
func Priority(v int) predicate.Account {
|
func Priority(v int) predicate.Account {
|
||||||
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
||||||
@@ -650,6 +655,56 @@ func ConcurrencyLTE(v int) predicate.Account {
|
|||||||
return predicate.Account(sql.FieldLTE(FieldConcurrency, v))
|
return predicate.Account(sql.FieldLTE(FieldConcurrency, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFactorEQ applies the EQ predicate on the "load_factor" field.
|
||||||
|
func LoadFactorEQ(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldEQ(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorNEQ applies the NEQ predicate on the "load_factor" field.
|
||||||
|
func LoadFactorNEQ(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldNEQ(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorIn applies the In predicate on the "load_factor" field.
|
||||||
|
func LoadFactorIn(vs ...int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldIn(FieldLoadFactor, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorNotIn applies the NotIn predicate on the "load_factor" field.
|
||||||
|
func LoadFactorNotIn(vs ...int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldNotIn(FieldLoadFactor, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorGT applies the GT predicate on the "load_factor" field.
|
||||||
|
func LoadFactorGT(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldGT(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorGTE applies the GTE predicate on the "load_factor" field.
|
||||||
|
func LoadFactorGTE(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldGTE(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorLT applies the LT predicate on the "load_factor" field.
|
||||||
|
func LoadFactorLT(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldLT(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorLTE applies the LTE predicate on the "load_factor" field.
|
||||||
|
func LoadFactorLTE(v int) predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldLTE(FieldLoadFactor, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorIsNil applies the IsNil predicate on the "load_factor" field.
|
||||||
|
func LoadFactorIsNil() predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldIsNull(FieldLoadFactor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFactorNotNil applies the NotNil predicate on the "load_factor" field.
|
||||||
|
func LoadFactorNotNil() predicate.Account {
|
||||||
|
return predicate.Account(sql.FieldNotNull(FieldLoadFactor))
|
||||||
|
}
|
||||||
|
|
||||||
// PriorityEQ applies the EQ predicate on the "priority" field.
|
// PriorityEQ applies the EQ predicate on the "priority" field.
|
||||||
func PriorityEQ(v int) predicate.Account {
|
func PriorityEQ(v int) predicate.Account {
|
||||||
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
return predicate.Account(sql.FieldEQ(FieldPriority, v))
|
||||||
|
|||||||
@@ -139,6 +139,20 @@ func (_c *AccountCreate) SetNillableConcurrency(v *int) *AccountCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (_c *AccountCreate) SetLoadFactor(v int) *AccountCreate {
|
||||||
|
_c.mutation.SetLoadFactor(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||||
|
func (_c *AccountCreate) SetNillableLoadFactor(v *int) *AccountCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetLoadFactor(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (_c *AccountCreate) SetPriority(v int) *AccountCreate {
|
func (_c *AccountCreate) SetPriority(v int) *AccountCreate {
|
||||||
_c.mutation.SetPriority(v)
|
_c.mutation.SetPriority(v)
|
||||||
@@ -623,6 +637,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
||||||
_node.Concurrency = value
|
_node.Concurrency = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.LoadFactor(); ok {
|
||||||
|
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||||
|
_node.LoadFactor = &value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.Priority(); ok {
|
if value, ok := _c.mutation.Priority(); ok {
|
||||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||||
_node.Priority = value
|
_node.Priority = value
|
||||||
@@ -936,6 +954,30 @@ func (u *AccountUpsert) AddConcurrency(v int) *AccountUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (u *AccountUpsert) SetLoadFactor(v int) *AccountUpsert {
|
||||||
|
u.Set(account.FieldLoadFactor, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsert) UpdateLoadFactor() *AccountUpsert {
|
||||||
|
u.SetExcluded(account.FieldLoadFactor)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoadFactor adds v to the "load_factor" field.
|
||||||
|
func (u *AccountUpsert) AddLoadFactor(v int) *AccountUpsert {
|
||||||
|
u.Add(account.FieldLoadFactor, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||||
|
func (u *AccountUpsert) ClearLoadFactor() *AccountUpsert {
|
||||||
|
u.SetNull(account.FieldLoadFactor)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (u *AccountUpsert) SetPriority(v int) *AccountUpsert {
|
func (u *AccountUpsert) SetPriority(v int) *AccountUpsert {
|
||||||
u.Set(account.FieldPriority, v)
|
u.Set(account.FieldPriority, v)
|
||||||
@@ -1419,6 +1461,34 @@ func (u *AccountUpsertOne) UpdateConcurrency() *AccountUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (u *AccountUpsertOne) SetLoadFactor(v int) *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.SetLoadFactor(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoadFactor adds v to the "load_factor" field.
|
||||||
|
func (u *AccountUpsertOne) AddLoadFactor(v int) *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.AddLoadFactor(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsertOne) UpdateLoadFactor() *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.UpdateLoadFactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||||
|
func (u *AccountUpsertOne) ClearLoadFactor() *AccountUpsertOne {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.ClearLoadFactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne {
|
func (u *AccountUpsertOne) SetPriority(v int) *AccountUpsertOne {
|
||||||
return u.Update(func(s *AccountUpsert) {
|
return u.Update(func(s *AccountUpsert) {
|
||||||
@@ -2113,6 +2183,34 @@ func (u *AccountUpsertBulk) UpdateConcurrency() *AccountUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (u *AccountUpsertBulk) SetLoadFactor(v int) *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.SetLoadFactor(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoadFactor adds v to the "load_factor" field.
|
||||||
|
func (u *AccountUpsertBulk) AddLoadFactor(v int) *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.AddLoadFactor(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLoadFactor sets the "load_factor" field to the value that was provided on create.
|
||||||
|
func (u *AccountUpsertBulk) UpdateLoadFactor() *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.UpdateLoadFactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||||
|
func (u *AccountUpsertBulk) ClearLoadFactor() *AccountUpsertBulk {
|
||||||
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
s.ClearLoadFactor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk {
|
func (u *AccountUpsertBulk) SetPriority(v int) *AccountUpsertBulk {
|
||||||
return u.Update(func(s *AccountUpsert) {
|
return u.Update(func(s *AccountUpsert) {
|
||||||
|
|||||||
@@ -172,6 +172,33 @@ func (_u *AccountUpdate) AddConcurrency(v int) *AccountUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (_u *AccountUpdate) SetLoadFactor(v int) *AccountUpdate {
|
||||||
|
_u.mutation.ResetLoadFactor()
|
||||||
|
_u.mutation.SetLoadFactor(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||||
|
func (_u *AccountUpdate) SetNillableLoadFactor(v *int) *AccountUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetLoadFactor(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoadFactor adds value to the "load_factor" field.
|
||||||
|
func (_u *AccountUpdate) AddLoadFactor(v int) *AccountUpdate {
|
||||||
|
_u.mutation.AddLoadFactor(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||||
|
func (_u *AccountUpdate) ClearLoadFactor() *AccountUpdate {
|
||||||
|
_u.mutation.ClearLoadFactor()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate {
|
func (_u *AccountUpdate) SetPriority(v int) *AccountUpdate {
|
||||||
_u.mutation.ResetPriority()
|
_u.mutation.ResetPriority()
|
||||||
@@ -684,6 +711,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
||||||
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.LoadFactor(); ok {
|
||||||
|
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedLoadFactor(); ok {
|
||||||
|
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.LoadFactorCleared() {
|
||||||
|
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Priority(); ok {
|
if value, ok := _u.mutation.Priority(); ok {
|
||||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
@@ -1063,6 +1099,33 @@ func (_u *AccountUpdateOne) AddConcurrency(v int) *AccountUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLoadFactor sets the "load_factor" field.
|
||||||
|
func (_u *AccountUpdateOne) SetLoadFactor(v int) *AccountUpdateOne {
|
||||||
|
_u.mutation.ResetLoadFactor()
|
||||||
|
_u.mutation.SetLoadFactor(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableLoadFactor sets the "load_factor" field if the given value is not nil.
|
||||||
|
func (_u *AccountUpdateOne) SetNillableLoadFactor(v *int) *AccountUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetLoadFactor(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoadFactor adds value to the "load_factor" field.
|
||||||
|
func (_u *AccountUpdateOne) AddLoadFactor(v int) *AccountUpdateOne {
|
||||||
|
_u.mutation.AddLoadFactor(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLoadFactor clears the value of the "load_factor" field.
|
||||||
|
func (_u *AccountUpdateOne) ClearLoadFactor() *AccountUpdateOne {
|
||||||
|
_u.mutation.ClearLoadFactor()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetPriority sets the "priority" field.
|
// SetPriority sets the "priority" field.
|
||||||
func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne {
|
func (_u *AccountUpdateOne) SetPriority(v int) *AccountUpdateOne {
|
||||||
_u.mutation.ResetPriority()
|
_u.mutation.ResetPriority()
|
||||||
@@ -1605,6 +1668,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
|
|||||||
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
if value, ok := _u.mutation.AddedConcurrency(); ok {
|
||||||
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
_spec.AddField(account.FieldConcurrency, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.LoadFactor(); ok {
|
||||||
|
_spec.SetField(account.FieldLoadFactor, field.TypeInt, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedLoadFactor(); ok {
|
||||||
|
_spec.AddField(account.FieldLoadFactor, field.TypeInt, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.LoadFactorCleared() {
|
||||||
|
_spec.ClearField(account.FieldLoadFactor, field.TypeInt)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Priority(); ok {
|
if value, ok := _u.mutation.Priority(); ok {
|
||||||
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
_spec.SetField(account.FieldPriority, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ type Announcement struct {
|
|||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
// 状态: draft, active, archived
|
// 状态: draft, active, archived
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
|
// 通知模式: silent(仅铃铛), popup(弹窗提醒)
|
||||||
|
NotifyMode string `json:"notify_mode,omitempty"`
|
||||||
// 展示条件(JSON 规则)
|
// 展示条件(JSON 规则)
|
||||||
Targeting domain.AnnouncementTargeting `json:"targeting,omitempty"`
|
Targeting domain.AnnouncementTargeting `json:"targeting,omitempty"`
|
||||||
// 开始展示时间(为空表示立即生效)
|
// 开始展示时间(为空表示立即生效)
|
||||||
@@ -72,7 +74,7 @@ func (*Announcement) scanValues(columns []string) ([]any, error) {
|
|||||||
values[i] = new([]byte)
|
values[i] = new([]byte)
|
||||||
case announcement.FieldID, announcement.FieldCreatedBy, announcement.FieldUpdatedBy:
|
case announcement.FieldID, announcement.FieldCreatedBy, announcement.FieldUpdatedBy:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case announcement.FieldTitle, announcement.FieldContent, announcement.FieldStatus:
|
case announcement.FieldTitle, announcement.FieldContent, announcement.FieldStatus, announcement.FieldNotifyMode:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
case announcement.FieldStartsAt, announcement.FieldEndsAt, announcement.FieldCreatedAt, announcement.FieldUpdatedAt:
|
case announcement.FieldStartsAt, announcement.FieldEndsAt, announcement.FieldCreatedAt, announcement.FieldUpdatedAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
@@ -115,6 +117,12 @@ func (_m *Announcement) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.Status = value.String
|
_m.Status = value.String
|
||||||
}
|
}
|
||||||
|
case announcement.FieldNotifyMode:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field notify_mode", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.NotifyMode = value.String
|
||||||
|
}
|
||||||
case announcement.FieldTargeting:
|
case announcement.FieldTargeting:
|
||||||
if value, ok := values[i].(*[]byte); !ok {
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field targeting", values[i])
|
return fmt.Errorf("unexpected type %T for field targeting", values[i])
|
||||||
@@ -213,6 +221,9 @@ func (_m *Announcement) String() string {
|
|||||||
builder.WriteString("status=")
|
builder.WriteString("status=")
|
||||||
builder.WriteString(_m.Status)
|
builder.WriteString(_m.Status)
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("notify_mode=")
|
||||||
|
builder.WriteString(_m.NotifyMode)
|
||||||
|
builder.WriteString(", ")
|
||||||
builder.WriteString("targeting=")
|
builder.WriteString("targeting=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.Targeting))
|
builder.WriteString(fmt.Sprintf("%v", _m.Targeting))
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const (
|
|||||||
FieldContent = "content"
|
FieldContent = "content"
|
||||||
// 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"
|
||||||
|
// FieldNotifyMode holds the string denoting the notify_mode field in the database.
|
||||||
|
FieldNotifyMode = "notify_mode"
|
||||||
// FieldTargeting holds the string denoting the targeting field in the database.
|
// FieldTargeting holds the string denoting the targeting field in the database.
|
||||||
FieldTargeting = "targeting"
|
FieldTargeting = "targeting"
|
||||||
// FieldStartsAt holds the string denoting the starts_at field in the database.
|
// FieldStartsAt holds the string denoting the starts_at field in the database.
|
||||||
@@ -53,6 +55,7 @@ var Columns = []string{
|
|||||||
FieldTitle,
|
FieldTitle,
|
||||||
FieldContent,
|
FieldContent,
|
||||||
FieldStatus,
|
FieldStatus,
|
||||||
|
FieldNotifyMode,
|
||||||
FieldTargeting,
|
FieldTargeting,
|
||||||
FieldStartsAt,
|
FieldStartsAt,
|
||||||
FieldEndsAt,
|
FieldEndsAt,
|
||||||
@@ -81,6 +84,10 @@ var (
|
|||||||
DefaultStatus string
|
DefaultStatus string
|
||||||
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||||
StatusValidator func(string) error
|
StatusValidator func(string) error
|
||||||
|
// DefaultNotifyMode holds the default value on creation for the "notify_mode" field.
|
||||||
|
DefaultNotifyMode string
|
||||||
|
// NotifyModeValidator is a validator for the "notify_mode" field. It is called by the builders before save.
|
||||||
|
NotifyModeValidator func(string) error
|
||||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
DefaultCreatedAt func() time.Time
|
DefaultCreatedAt func() time.Time
|
||||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||||
@@ -112,6 +119,11 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByNotifyMode orders the results by the notify_mode field.
|
||||||
|
func ByNotifyMode(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldNotifyMode, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByStartsAt orders the results by the starts_at field.
|
// ByStartsAt orders the results by the starts_at field.
|
||||||
func ByStartsAt(opts ...sql.OrderTermOption) OrderOption {
|
func ByStartsAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldStartsAt, opts...).ToFunc()
|
return sql.OrderByField(FieldStartsAt, opts...).ToFunc()
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ func Status(v string) predicate.Announcement {
|
|||||||
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
|
return predicate.Announcement(sql.FieldEQ(FieldStatus, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyMode applies equality check predicate on the "notify_mode" field. It's identical to NotifyModeEQ.
|
||||||
|
func NotifyMode(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
// StartsAt applies equality check predicate on the "starts_at" field. It's identical to StartsAtEQ.
|
// StartsAt applies equality check predicate on the "starts_at" field. It's identical to StartsAtEQ.
|
||||||
func StartsAt(v time.Time) predicate.Announcement {
|
func StartsAt(v time.Time) predicate.Announcement {
|
||||||
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
|
return predicate.Announcement(sql.FieldEQ(FieldStartsAt, v))
|
||||||
@@ -295,6 +300,71 @@ func StatusContainsFold(v string) predicate.Announcement {
|
|||||||
return predicate.Announcement(sql.FieldContainsFold(FieldStatus, v))
|
return predicate.Announcement(sql.FieldContainsFold(FieldStatus, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyModeEQ applies the EQ predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEQ(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeNEQ applies the NEQ predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeNEQ(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNEQ(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeIn applies the In predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldIn(FieldNotifyMode, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeNotIn applies the NotIn predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeNotIn(vs ...string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldNotIn(FieldNotifyMode, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeGT applies the GT predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeGT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGT(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeGTE applies the GTE predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeGTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldGTE(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeLT applies the LT predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeLT(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLT(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeLTE applies the LTE predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeLTE(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldLTE(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeContains applies the Contains predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeContains(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContains(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeHasPrefix applies the HasPrefix predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeHasPrefix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasPrefix(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeHasSuffix applies the HasSuffix predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeHasSuffix(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldHasSuffix(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeEqualFold applies the EqualFold predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeEqualFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldEqualFold(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyModeContainsFold applies the ContainsFold predicate on the "notify_mode" field.
|
||||||
|
func NotifyModeContainsFold(v string) predicate.Announcement {
|
||||||
|
return predicate.Announcement(sql.FieldContainsFold(FieldNotifyMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
// TargetingIsNil applies the IsNil predicate on the "targeting" field.
|
// TargetingIsNil applies the IsNil predicate on the "targeting" field.
|
||||||
func TargetingIsNil() predicate.Announcement {
|
func TargetingIsNil() predicate.Announcement {
|
||||||
return predicate.Announcement(sql.FieldIsNull(FieldTargeting))
|
return predicate.Announcement(sql.FieldIsNull(FieldTargeting))
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ func (_c *AnnouncementCreate) SetNillableStatus(v *string) *AnnouncementCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (_c *AnnouncementCreate) SetNotifyMode(v string) *AnnouncementCreate {
|
||||||
|
_c.mutation.SetNotifyMode(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
|
||||||
|
func (_c *AnnouncementCreate) SetNillableNotifyMode(v *string) *AnnouncementCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetNotifyMode(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (_c *AnnouncementCreate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementCreate {
|
func (_c *AnnouncementCreate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementCreate {
|
||||||
_c.mutation.SetTargeting(v)
|
_c.mutation.SetTargeting(v)
|
||||||
@@ -202,6 +216,10 @@ func (_c *AnnouncementCreate) defaults() {
|
|||||||
v := announcement.DefaultStatus
|
v := announcement.DefaultStatus
|
||||||
_c.mutation.SetStatus(v)
|
_c.mutation.SetStatus(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.NotifyMode(); !ok {
|
||||||
|
v := announcement.DefaultNotifyMode
|
||||||
|
_c.mutation.SetNotifyMode(v)
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
v := announcement.DefaultCreatedAt()
|
v := announcement.DefaultCreatedAt()
|
||||||
_c.mutation.SetCreatedAt(v)
|
_c.mutation.SetCreatedAt(v)
|
||||||
@@ -238,6 +256,14 @@ func (_c *AnnouncementCreate) check() error {
|
|||||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.NotifyMode(); !ok {
|
||||||
|
return &ValidationError{Name: "notify_mode", err: errors.New(`ent: missing required field "Announcement.notify_mode"`)}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.NotifyMode(); ok {
|
||||||
|
if err := announcement.NotifyModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Announcement.created_at"`)}
|
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Announcement.created_at"`)}
|
||||||
}
|
}
|
||||||
@@ -283,6 +309,10 @@ func (_c *AnnouncementCreate) createSpec() (*Announcement, *sqlgraph.CreateSpec)
|
|||||||
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
||||||
_node.Status = value
|
_node.Status = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.NotifyMode(); ok {
|
||||||
|
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
|
||||||
|
_node.NotifyMode = value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.Targeting(); ok {
|
if value, ok := _c.mutation.Targeting(); ok {
|
||||||
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
||||||
_node.Targeting = value
|
_node.Targeting = value
|
||||||
@@ -415,6 +445,18 @@ func (u *AnnouncementUpsert) UpdateStatus() *AnnouncementUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (u *AnnouncementUpsert) SetNotifyMode(v string) *AnnouncementUpsert {
|
||||||
|
u.Set(announcement.FieldNotifyMode, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementUpsert) UpdateNotifyMode() *AnnouncementUpsert {
|
||||||
|
u.SetExcluded(announcement.FieldNotifyMode)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (u *AnnouncementUpsert) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsert {
|
func (u *AnnouncementUpsert) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsert {
|
||||||
u.Set(announcement.FieldTargeting, v)
|
u.Set(announcement.FieldTargeting, v)
|
||||||
@@ -616,6 +658,20 @@ func (u *AnnouncementUpsertOne) UpdateStatus() *AnnouncementUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (u *AnnouncementUpsertOne) SetNotifyMode(v string) *AnnouncementUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
|
s.SetNotifyMode(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementUpsertOne) UpdateNotifyMode() *AnnouncementUpsertOne {
|
||||||
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
|
s.UpdateNotifyMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (u *AnnouncementUpsertOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertOne {
|
func (u *AnnouncementUpsertOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertOne {
|
||||||
return u.Update(func(s *AnnouncementUpsert) {
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
@@ -1002,6 +1058,20 @@ func (u *AnnouncementUpsertBulk) UpdateStatus() *AnnouncementUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (u *AnnouncementUpsertBulk) SetNotifyMode(v string) *AnnouncementUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
|
s.SetNotifyMode(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNotifyMode sets the "notify_mode" field to the value that was provided on create.
|
||||||
|
func (u *AnnouncementUpsertBulk) UpdateNotifyMode() *AnnouncementUpsertBulk {
|
||||||
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
|
s.UpdateNotifyMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (u *AnnouncementUpsertBulk) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertBulk {
|
func (u *AnnouncementUpsertBulk) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpsertBulk {
|
||||||
return u.Update(func(s *AnnouncementUpsert) {
|
return u.Update(func(s *AnnouncementUpsert) {
|
||||||
|
|||||||
@@ -72,6 +72,20 @@ func (_u *AnnouncementUpdate) SetNillableStatus(v *string) *AnnouncementUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (_u *AnnouncementUpdate) SetNotifyMode(v string) *AnnouncementUpdate {
|
||||||
|
_u.mutation.SetNotifyMode(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdate) SetNillableNotifyMode(v *string) *AnnouncementUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetNotifyMode(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (_u *AnnouncementUpdate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdate {
|
func (_u *AnnouncementUpdate) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdate {
|
||||||
_u.mutation.SetTargeting(v)
|
_u.mutation.SetTargeting(v)
|
||||||
@@ -286,6 +300,11 @@ func (_u *AnnouncementUpdate) check() error {
|
|||||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.NotifyMode(); ok {
|
||||||
|
if err := announcement.NotifyModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +329,9 @@ func (_u *AnnouncementUpdate) sqlSave(ctx context.Context) (_node int, err error
|
|||||||
if value, ok := _u.mutation.Status(); ok {
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.NotifyMode(); ok {
|
||||||
|
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Targeting(); ok {
|
if value, ok := _u.mutation.Targeting(); ok {
|
||||||
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
||||||
}
|
}
|
||||||
@@ -456,6 +478,20 @@ func (_u *AnnouncementUpdateOne) SetNillableStatus(v *string) *AnnouncementUpdat
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifyMode sets the "notify_mode" field.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNotifyMode(v string) *AnnouncementUpdateOne {
|
||||||
|
_u.mutation.SetNotifyMode(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableNotifyMode sets the "notify_mode" field if the given value is not nil.
|
||||||
|
func (_u *AnnouncementUpdateOne) SetNillableNotifyMode(v *string) *AnnouncementUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetNotifyMode(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetTargeting sets the "targeting" field.
|
// SetTargeting sets the "targeting" field.
|
||||||
func (_u *AnnouncementUpdateOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdateOne {
|
func (_u *AnnouncementUpdateOne) SetTargeting(v domain.AnnouncementTargeting) *AnnouncementUpdateOne {
|
||||||
_u.mutation.SetTargeting(v)
|
_u.mutation.SetTargeting(v)
|
||||||
@@ -683,6 +719,11 @@ func (_u *AnnouncementUpdateOne) check() error {
|
|||||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Announcement.status": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.NotifyMode(); ok {
|
||||||
|
if err := announcement.NotifyModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "notify_mode", err: fmt.Errorf(`ent: validator failed for field "Announcement.notify_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,6 +765,9 @@ func (_u *AnnouncementUpdateOne) sqlSave(ctx context.Context) (_node *Announceme
|
|||||||
if value, ok := _u.mutation.Status(); ok {
|
if value, ok := _u.mutation.Status(); ok {
|
||||||
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
_spec.SetField(announcement.FieldStatus, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.NotifyMode(); ok {
|
||||||
|
_spec.SetField(announcement.FieldNotifyMode, field.TypeString, value)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.Targeting(); ok {
|
if value, ok := _u.mutation.Targeting(); ok {
|
||||||
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
_spec.SetField(announcement.FieldTargeting, field.TypeJSON, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
"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"
|
||||||
@@ -73,6 +74,8 @@ type Client struct {
|
|||||||
SecuritySecret *SecuritySecretClient
|
SecuritySecret *SecuritySecretClient
|
||||||
// Setting is the client for interacting with the Setting builders.
|
// Setting is the client for interacting with the Setting builders.
|
||||||
Setting *SettingClient
|
Setting *SettingClient
|
||||||
|
// TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders.
|
||||||
|
TLSFingerprintProfile *TLSFingerprintProfileClient
|
||||||
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||||
UsageCleanupTask *UsageCleanupTaskClient
|
UsageCleanupTask *UsageCleanupTaskClient
|
||||||
// UsageLog is the client for interacting with the UsageLog builders.
|
// UsageLog is the client for interacting with the UsageLog builders.
|
||||||
@@ -112,6 +115,7 @@ func (c *Client) init() {
|
|||||||
c.RedeemCode = NewRedeemCodeClient(c.config)
|
c.RedeemCode = NewRedeemCodeClient(c.config)
|
||||||
c.SecuritySecret = NewSecuritySecretClient(c.config)
|
c.SecuritySecret = NewSecuritySecretClient(c.config)
|
||||||
c.Setting = NewSettingClient(c.config)
|
c.Setting = NewSettingClient(c.config)
|
||||||
|
c.TLSFingerprintProfile = NewTLSFingerprintProfileClient(c.config)
|
||||||
c.UsageCleanupTask = NewUsageCleanupTaskClient(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)
|
||||||
@@ -225,6 +229,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
|||||||
RedeemCode: NewRedeemCodeClient(cfg),
|
RedeemCode: NewRedeemCodeClient(cfg),
|
||||||
SecuritySecret: NewSecuritySecretClient(cfg),
|
SecuritySecret: NewSecuritySecretClient(cfg),
|
||||||
Setting: NewSettingClient(cfg),
|
Setting: NewSettingClient(cfg),
|
||||||
|
TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg),
|
||||||
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||||
UsageLog: NewUsageLogClient(cfg),
|
UsageLog: NewUsageLogClient(cfg),
|
||||||
User: NewUserClient(cfg),
|
User: NewUserClient(cfg),
|
||||||
@@ -265,6 +270,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
|||||||
RedeemCode: NewRedeemCodeClient(cfg),
|
RedeemCode: NewRedeemCodeClient(cfg),
|
||||||
SecuritySecret: NewSecuritySecretClient(cfg),
|
SecuritySecret: NewSecuritySecretClient(cfg),
|
||||||
Setting: NewSettingClient(cfg),
|
Setting: NewSettingClient(cfg),
|
||||||
|
TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg),
|
||||||
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||||
UsageLog: NewUsageLogClient(cfg),
|
UsageLog: NewUsageLogClient(cfg),
|
||||||
User: NewUserClient(cfg),
|
User: NewUserClient(cfg),
|
||||||
@@ -304,8 +310,9 @@ func (c *Client) Use(hooks ...Hook) {
|
|||||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||||
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
||||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||||
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||||
|
c.UserSubscription,
|
||||||
} {
|
} {
|
||||||
n.Use(hooks...)
|
n.Use(hooks...)
|
||||||
}
|
}
|
||||||
@@ -318,8 +325,9 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
|
|||||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||||
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
|
||||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||||
c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup,
|
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||||
c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
|
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||||
|
c.UserSubscription,
|
||||||
} {
|
} {
|
||||||
n.Intercept(interceptors...)
|
n.Intercept(interceptors...)
|
||||||
}
|
}
|
||||||
@@ -356,6 +364,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
|||||||
return c.SecuritySecret.mutate(ctx, m)
|
return c.SecuritySecret.mutate(ctx, m)
|
||||||
case *SettingMutation:
|
case *SettingMutation:
|
||||||
return c.Setting.mutate(ctx, m)
|
return c.Setting.mutate(ctx, m)
|
||||||
|
case *TLSFingerprintProfileMutation:
|
||||||
|
return c.TLSFingerprintProfile.mutate(ctx, m)
|
||||||
case *UsageCleanupTaskMutation:
|
case *UsageCleanupTaskMutation:
|
||||||
return c.UsageCleanupTask.mutate(ctx, m)
|
return c.UsageCleanupTask.mutate(ctx, m)
|
||||||
case *UsageLogMutation:
|
case *UsageLogMutation:
|
||||||
@@ -2612,6 +2622,139 @@ func (c *SettingClient) mutate(ctx context.Context, m *SettingMutation) (Value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfileClient is a client for the TLSFingerprintProfile schema.
|
||||||
|
type TLSFingerprintProfileClient struct {
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSFingerprintProfileClient returns a client for the TLSFingerprintProfile from the given config.
|
||||||
|
func NewTLSFingerprintProfileClient(c config) *TLSFingerprintProfileClient {
|
||||||
|
return &TLSFingerprintProfileClient{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use adds a list of mutation hooks to the hooks stack.
|
||||||
|
// A call to `Use(f, g, h)` equals to `tlsfingerprintprofile.Hooks(f(g(h())))`.
|
||||||
|
func (c *TLSFingerprintProfileClient) Use(hooks ...Hook) {
|
||||||
|
c.hooks.TLSFingerprintProfile = append(c.hooks.TLSFingerprintProfile, hooks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `tlsfingerprintprofile.Intercept(f(g(h())))`.
|
||||||
|
func (c *TLSFingerprintProfileClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.TLSFingerprintProfile = append(c.inters.TLSFingerprintProfile, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a TLSFingerprintProfile entity.
|
||||||
|
func (c *TLSFingerprintProfileClient) Create() *TLSFingerprintProfileCreate {
|
||||||
|
mutation := newTLSFingerprintProfileMutation(c.config, OpCreate)
|
||||||
|
return &TLSFingerprintProfileCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBulk returns a builder for creating a bulk of TLSFingerprintProfile entities.
|
||||||
|
func (c *TLSFingerprintProfileClient) CreateBulk(builders ...*TLSFingerprintProfileCreate) *TLSFingerprintProfileCreateBulk {
|
||||||
|
return &TLSFingerprintProfileCreateBulk{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 *TLSFingerprintProfileClient) MapCreateBulk(slice any, setFunc func(*TLSFingerprintProfileCreate, int)) *TLSFingerprintProfileCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &TLSFingerprintProfileCreateBulk{err: fmt.Errorf("calling to TLSFingerprintProfileClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*TLSFingerprintProfileCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &TLSFingerprintProfileCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns an update builder for TLSFingerprintProfile.
|
||||||
|
func (c *TLSFingerprintProfileClient) Update() *TLSFingerprintProfileUpdate {
|
||||||
|
mutation := newTLSFingerprintProfileMutation(c.config, OpUpdate)
|
||||||
|
return &TLSFingerprintProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOne returns an update builder for the given entity.
|
||||||
|
func (c *TLSFingerprintProfileClient) UpdateOne(_m *TLSFingerprintProfile) *TLSFingerprintProfileUpdateOne {
|
||||||
|
mutation := newTLSFingerprintProfileMutation(c.config, OpUpdateOne, withTLSFingerprintProfile(_m))
|
||||||
|
return &TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOneID returns an update builder for the given id.
|
||||||
|
func (c *TLSFingerprintProfileClient) UpdateOneID(id int64) *TLSFingerprintProfileUpdateOne {
|
||||||
|
mutation := newTLSFingerprintProfileMutation(c.config, OpUpdateOne, withTLSFingerprintProfileID(id))
|
||||||
|
return &TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete returns a delete builder for TLSFingerprintProfile.
|
||||||
|
func (c *TLSFingerprintProfileClient) Delete() *TLSFingerprintProfileDelete {
|
||||||
|
mutation := newTLSFingerprintProfileMutation(c.config, OpDelete)
|
||||||
|
return &TLSFingerprintProfileDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
|
func (c *TLSFingerprintProfileClient) DeleteOne(_m *TLSFingerprintProfile) *TLSFingerprintProfileDeleteOne {
|
||||||
|
return c.DeleteOneID(_m.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
|
func (c *TLSFingerprintProfileClient) DeleteOneID(id int64) *TLSFingerprintProfileDeleteOne {
|
||||||
|
builder := c.Delete().Where(tlsfingerprintprofile.ID(id))
|
||||||
|
builder.mutation.id = &id
|
||||||
|
builder.mutation.op = OpDeleteOne
|
||||||
|
return &TLSFingerprintProfileDeleteOne{builder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns a query builder for TLSFingerprintProfile.
|
||||||
|
func (c *TLSFingerprintProfileClient) Query() *TLSFingerprintProfileQuery {
|
||||||
|
return &TLSFingerprintProfileQuery{
|
||||||
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypeTLSFingerprintProfile},
|
||||||
|
inters: c.Interceptors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a TLSFingerprintProfile entity by its id.
|
||||||
|
func (c *TLSFingerprintProfileClient) Get(ctx context.Context, id int64) (*TLSFingerprintProfile, error) {
|
||||||
|
return c.Query().Where(tlsfingerprintprofile.ID(id)).Only(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetX is like Get, but panics if an error occurs.
|
||||||
|
func (c *TLSFingerprintProfileClient) GetX(ctx context.Context, id int64) *TLSFingerprintProfile {
|
||||||
|
obj, err := c.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks returns the client hooks.
|
||||||
|
func (c *TLSFingerprintProfileClient) Hooks() []Hook {
|
||||||
|
return c.hooks.TLSFingerprintProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *TLSFingerprintProfileClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.TLSFingerprintProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TLSFingerprintProfileClient) mutate(ctx context.Context, m *TLSFingerprintProfileMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&TLSFingerprintProfileCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&TLSFingerprintProfileUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&TLSFingerprintProfileUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&TLSFingerprintProfileDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown TLSFingerprintProfile mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UsageCleanupTaskClient is a client for the UsageCleanupTask schema.
|
// UsageCleanupTaskClient is a client for the UsageCleanupTask schema.
|
||||||
type UsageCleanupTaskClient struct {
|
type UsageCleanupTaskClient struct {
|
||||||
config
|
config
|
||||||
@@ -3889,16 +4032,16 @@ type (
|
|||||||
hooks struct {
|
hooks struct {
|
||||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||||
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
||||||
Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User,
|
Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile,
|
||||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
||||||
UserSubscription []ent.Hook
|
UserAttributeValue, UserSubscription []ent.Hook
|
||||||
}
|
}
|
||||||
inters struct {
|
inters struct {
|
||||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||||
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
|
||||||
Proxy, RedeemCode, SecuritySecret, Setting, UsageCleanupTask, UsageLog, User,
|
Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile,
|
||||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
||||||
UserSubscription []ent.Interceptor
|
UserAttributeValue, UserSubscription []ent.Interceptor
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
"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"
|
||||||
@@ -107,6 +108,7 @@ func checkColumn(t, c string) error {
|
|||||||
redeemcode.Table: redeemcode.ValidColumn,
|
redeemcode.Table: redeemcode.ValidColumn,
|
||||||
securitysecret.Table: securitysecret.ValidColumn,
|
securitysecret.Table: securitysecret.ValidColumn,
|
||||||
setting.Table: setting.ValidColumn,
|
setting.Table: setting.ValidColumn,
|
||||||
|
tlsfingerprintprofile.Table: tlsfingerprintprofile.ValidColumn,
|
||||||
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
|
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
|
||||||
usagelog.Table: usagelog.ValidColumn,
|
usagelog.Table: usagelog.ValidColumn,
|
||||||
user.Table: user.ValidColumn,
|
user.Table: user.ValidColumn,
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ type Group struct {
|
|||||||
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
|
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
|
||||||
// 分组显示排序,数值越小越靠前
|
// 分组显示排序,数值越小越靠前
|
||||||
SortOrder int `json:"sort_order,omitempty"`
|
SortOrder int `json:"sort_order,omitempty"`
|
||||||
|
// 是否允许 /v1/messages 调度到此 OpenAI 分组
|
||||||
|
AllowMessagesDispatch bool `json:"allow_messages_dispatch,omitempty"`
|
||||||
|
// 仅允许非 apikey 类型账号关联到此分组
|
||||||
|
RequireOauthOnly bool `json:"require_oauth_only,omitempty"`
|
||||||
|
// 调度时仅允许 privacy 已成功设置的账号
|
||||||
|
RequirePrivacySet bool `json:"require_privacy_set,omitempty"`
|
||||||
|
// 默认映射模型 ID,当账号级映射找不到时使用此值
|
||||||
|
DefaultMappedModel string `json:"default_mapped_model,omitempty"`
|
||||||
// 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"`
|
||||||
@@ -186,13 +194,13 @@ func (*Group) scanValues(columns []string) ([]any, error) {
|
|||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
case group.FieldModelRouting, group.FieldSupportedModelScopes:
|
case group.FieldModelRouting, group.FieldSupportedModelScopes:
|
||||||
values[i] = new([]byte)
|
values[i] = new([]byte)
|
||||||
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject:
|
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
|
||||||
values[i] = new(sql.NullBool)
|
values[i] = new(sql.NullBool)
|
||||||
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldSoraImagePrice360, group.FieldSoraImagePrice540, group.FieldSoraVideoPricePerRequest, group.FieldSoraVideoPricePerRequestHd:
|
case group.FieldRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldSoraImagePrice360, group.FieldSoraImagePrice540, group.FieldSoraVideoPricePerRequest, group.FieldSoraVideoPricePerRequestHd:
|
||||||
values[i] = new(sql.NullFloat64)
|
values[i] = new(sql.NullFloat64)
|
||||||
case group.FieldID, group.FieldDefaultValidityDays, group.FieldSoraStorageQuotaBytes, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder:
|
case group.FieldID, group.FieldDefaultValidityDays, group.FieldSoraStorageQuotaBytes, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType:
|
case group.FieldName, group.FieldDescription, group.FieldStatus, group.FieldPlatform, group.FieldSubscriptionType, group.FieldDefaultMappedModel:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
case group.FieldCreatedAt, group.FieldUpdatedAt, group.FieldDeletedAt:
|
case group.FieldCreatedAt, group.FieldUpdatedAt, group.FieldDeletedAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
@@ -415,6 +423,30 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.SortOrder = int(value.Int64)
|
_m.SortOrder = int(value.Int64)
|
||||||
}
|
}
|
||||||
|
case group.FieldAllowMessagesDispatch:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field allow_messages_dispatch", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.AllowMessagesDispatch = value.Bool
|
||||||
|
}
|
||||||
|
case group.FieldRequireOauthOnly:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field require_oauth_only", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.RequireOauthOnly = value.Bool
|
||||||
|
}
|
||||||
|
case group.FieldRequirePrivacySet:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field require_privacy_set", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.RequirePrivacySet = value.Bool
|
||||||
|
}
|
||||||
|
case group.FieldDefaultMappedModel:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field default_mapped_model", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.DefaultMappedModel = value.String
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
_m.selectValues.Set(columns[i], values[i])
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
@@ -608,6 +640,18 @@ func (_m *Group) String() string {
|
|||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
builder.WriteString("sort_order=")
|
builder.WriteString("sort_order=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
|
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("allow_messages_dispatch=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.AllowMessagesDispatch))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("require_oauth_only=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.RequireOauthOnly))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("require_privacy_set=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.RequirePrivacySet))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("default_mapped_model=")
|
||||||
|
builder.WriteString(_m.DefaultMappedModel)
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ const (
|
|||||||
FieldSupportedModelScopes = "supported_model_scopes"
|
FieldSupportedModelScopes = "supported_model_scopes"
|
||||||
// FieldSortOrder holds the string denoting the sort_order field in the database.
|
// FieldSortOrder holds the string denoting the sort_order field in the database.
|
||||||
FieldSortOrder = "sort_order"
|
FieldSortOrder = "sort_order"
|
||||||
|
// FieldAllowMessagesDispatch holds the string denoting the allow_messages_dispatch field in the database.
|
||||||
|
FieldAllowMessagesDispatch = "allow_messages_dispatch"
|
||||||
|
// FieldRequireOauthOnly holds the string denoting the require_oauth_only field in the database.
|
||||||
|
FieldRequireOauthOnly = "require_oauth_only"
|
||||||
|
// FieldRequirePrivacySet holds the string denoting the require_privacy_set field in the database.
|
||||||
|
FieldRequirePrivacySet = "require_privacy_set"
|
||||||
|
// FieldDefaultMappedModel holds the string denoting the default_mapped_model field in the database.
|
||||||
|
FieldDefaultMappedModel = "default_mapped_model"
|
||||||
// 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.
|
||||||
@@ -180,6 +188,10 @@ var Columns = []string{
|
|||||||
FieldMcpXMLInject,
|
FieldMcpXMLInject,
|
||||||
FieldSupportedModelScopes,
|
FieldSupportedModelScopes,
|
||||||
FieldSortOrder,
|
FieldSortOrder,
|
||||||
|
FieldAllowMessagesDispatch,
|
||||||
|
FieldRequireOauthOnly,
|
||||||
|
FieldRequirePrivacySet,
|
||||||
|
FieldDefaultMappedModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -247,6 +259,16 @@ var (
|
|||||||
DefaultSupportedModelScopes []string
|
DefaultSupportedModelScopes []string
|
||||||
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
|
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
|
||||||
DefaultSortOrder int
|
DefaultSortOrder int
|
||||||
|
// DefaultAllowMessagesDispatch holds the default value on creation for the "allow_messages_dispatch" field.
|
||||||
|
DefaultAllowMessagesDispatch bool
|
||||||
|
// DefaultRequireOauthOnly holds the default value on creation for the "require_oauth_only" field.
|
||||||
|
DefaultRequireOauthOnly bool
|
||||||
|
// DefaultRequirePrivacySet holds the default value on creation for the "require_privacy_set" field.
|
||||||
|
DefaultRequirePrivacySet bool
|
||||||
|
// DefaultDefaultMappedModel holds the default value on creation for the "default_mapped_model" field.
|
||||||
|
DefaultDefaultMappedModel string
|
||||||
|
// DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
|
||||||
|
DefaultMappedModelValidator func(string) error
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderOption defines the ordering options for the Group queries.
|
// OrderOption defines the ordering options for the Group queries.
|
||||||
@@ -397,6 +419,26 @@ func BySortOrder(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
|
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByAllowMessagesDispatch orders the results by the allow_messages_dispatch field.
|
||||||
|
func ByAllowMessagesDispatch(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldAllowMessagesDispatch, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByRequireOauthOnly orders the results by the require_oauth_only field.
|
||||||
|
func ByRequireOauthOnly(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldRequireOauthOnly, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByRequirePrivacySet orders the results by the require_privacy_set field.
|
||||||
|
func ByRequirePrivacySet(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldRequirePrivacySet, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByDefaultMappedModel orders the results by the default_mapped_model field.
|
||||||
|
func ByDefaultMappedModel(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldDefaultMappedModel, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByAPIKeysCount orders the results by api_keys count.
|
// 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) {
|
||||||
|
|||||||
@@ -195,6 +195,26 @@ func SortOrder(v int) predicate.Group {
|
|||||||
return predicate.Group(sql.FieldEQ(FieldSortOrder, v))
|
return predicate.Group(sql.FieldEQ(FieldSortOrder, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowMessagesDispatch applies equality check predicate on the "allow_messages_dispatch" field. It's identical to AllowMessagesDispatchEQ.
|
||||||
|
func AllowMessagesDispatch(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldAllowMessagesDispatch, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireOauthOnly applies equality check predicate on the "require_oauth_only" field. It's identical to RequireOauthOnlyEQ.
|
||||||
|
func RequireOauthOnly(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirePrivacySet applies equality check predicate on the "require_privacy_set" field. It's identical to RequirePrivacySetEQ.
|
||||||
|
func RequirePrivacySet(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldRequirePrivacySet, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModel applies equality check predicate on the "default_mapped_model" field. It's identical to DefaultMappedModelEQ.
|
||||||
|
func DefaultMappedModel(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// 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))
|
||||||
@@ -1470,6 +1490,101 @@ func SortOrderLTE(v int) predicate.Group {
|
|||||||
return predicate.Group(sql.FieldLTE(FieldSortOrder, v))
|
return predicate.Group(sql.FieldLTE(FieldSortOrder, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowMessagesDispatchEQ applies the EQ predicate on the "allow_messages_dispatch" field.
|
||||||
|
func AllowMessagesDispatchEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldAllowMessagesDispatch, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowMessagesDispatchNEQ applies the NEQ predicate on the "allow_messages_dispatch" field.
|
||||||
|
func AllowMessagesDispatchNEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNEQ(FieldAllowMessagesDispatch, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireOauthOnlyEQ applies the EQ predicate on the "require_oauth_only" field.
|
||||||
|
func RequireOauthOnlyEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireOauthOnlyNEQ applies the NEQ predicate on the "require_oauth_only" field.
|
||||||
|
func RequireOauthOnlyNEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNEQ(FieldRequireOauthOnly, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirePrivacySetEQ applies the EQ predicate on the "require_privacy_set" field.
|
||||||
|
func RequirePrivacySetEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldRequirePrivacySet, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirePrivacySetNEQ applies the NEQ predicate on the "require_privacy_set" field.
|
||||||
|
func RequirePrivacySetNEQ(v bool) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNEQ(FieldRequirePrivacySet, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelEQ applies the EQ predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelEQ(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEQ(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelNEQ applies the NEQ predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelNEQ(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNEQ(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelIn applies the In predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelIn(vs ...string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldIn(FieldDefaultMappedModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelNotIn applies the NotIn predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelNotIn(vs ...string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldNotIn(FieldDefaultMappedModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelGT applies the GT predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelGT(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldGT(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelGTE applies the GTE predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelGTE(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldGTE(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelLT applies the LT predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelLT(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldLT(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelLTE applies the LTE predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelLTE(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldLTE(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelContains applies the Contains predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelContains(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldContains(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelHasPrefix applies the HasPrefix predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelHasPrefix(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldHasPrefix(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelHasSuffix applies the HasSuffix predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelHasSuffix(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldHasSuffix(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelEqualFold applies the EqualFold predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelEqualFold(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldEqualFold(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMappedModelContainsFold applies the ContainsFold predicate on the "default_mapped_model" field.
|
||||||
|
func DefaultMappedModelContainsFold(v string) predicate.Group {
|
||||||
|
return predicate.Group(sql.FieldContainsFold(FieldDefaultMappedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
// HasAPIKeys applies the HasEdge predicate on the "api_keys" edge.
|
// 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) {
|
||||||
|
|||||||
@@ -424,6 +424,62 @@ func (_c *GroupCreate) SetNillableSortOrder(v *int) *GroupCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (_c *GroupCreate) SetAllowMessagesDispatch(v bool) *GroupCreate {
|
||||||
|
_c.mutation.SetAllowMessagesDispatch(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
|
||||||
|
func (_c *GroupCreate) SetNillableAllowMessagesDispatch(v *bool) *GroupCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetAllowMessagesDispatch(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (_c *GroupCreate) SetRequireOauthOnly(v bool) *GroupCreate {
|
||||||
|
_c.mutation.SetRequireOauthOnly(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
|
||||||
|
func (_c *GroupCreate) SetNillableRequireOauthOnly(v *bool) *GroupCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetRequireOauthOnly(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (_c *GroupCreate) SetRequirePrivacySet(v bool) *GroupCreate {
|
||||||
|
_c.mutation.SetRequirePrivacySet(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
|
||||||
|
func (_c *GroupCreate) SetNillableRequirePrivacySet(v *bool) *GroupCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetRequirePrivacySet(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (_c *GroupCreate) SetDefaultMappedModel(v string) *GroupCreate {
|
||||||
|
_c.mutation.SetDefaultMappedModel(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
|
||||||
|
func (_c *GroupCreate) SetNillableDefaultMappedModel(v *string) *GroupCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetDefaultMappedModel(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// 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...)
|
||||||
@@ -613,6 +669,22 @@ func (_c *GroupCreate) defaults() error {
|
|||||||
v := group.DefaultSortOrder
|
v := group.DefaultSortOrder
|
||||||
_c.mutation.SetSortOrder(v)
|
_c.mutation.SetSortOrder(v)
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.AllowMessagesDispatch(); !ok {
|
||||||
|
v := group.DefaultAllowMessagesDispatch
|
||||||
|
_c.mutation.SetAllowMessagesDispatch(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
|
||||||
|
v := group.DefaultRequireOauthOnly
|
||||||
|
_c.mutation.SetRequireOauthOnly(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.RequirePrivacySet(); !ok {
|
||||||
|
v := group.DefaultRequirePrivacySet
|
||||||
|
_c.mutation.SetRequirePrivacySet(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.DefaultMappedModel(); !ok {
|
||||||
|
v := group.DefaultDefaultMappedModel
|
||||||
|
_c.mutation.SetDefaultMappedModel(v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,6 +755,23 @@ func (_c *GroupCreate) check() error {
|
|||||||
if _, ok := _c.mutation.SortOrder(); !ok {
|
if _, ok := _c.mutation.SortOrder(); !ok {
|
||||||
return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "Group.sort_order"`)}
|
return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "Group.sort_order"`)}
|
||||||
}
|
}
|
||||||
|
if _, ok := _c.mutation.AllowMessagesDispatch(); !ok {
|
||||||
|
return &ValidationError{Name: "allow_messages_dispatch", err: errors.New(`ent: missing required field "Group.allow_messages_dispatch"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
|
||||||
|
return &ValidationError{Name: "require_oauth_only", err: errors.New(`ent: missing required field "Group.require_oauth_only"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.RequirePrivacySet(); !ok {
|
||||||
|
return &ValidationError{Name: "require_privacy_set", err: errors.New(`ent: missing required field "Group.require_privacy_set"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.DefaultMappedModel(); !ok {
|
||||||
|
return &ValidationError{Name: "default_mapped_model", err: errors.New(`ent: missing required field "Group.default_mapped_model"`)}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.DefaultMappedModel(); ok {
|
||||||
|
if err := group.DefaultMappedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,6 +919,22 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(group.FieldSortOrder, field.TypeInt, value)
|
_spec.SetField(group.FieldSortOrder, field.TypeInt, value)
|
||||||
_node.SortOrder = value
|
_node.SortOrder = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.AllowMessagesDispatch(); ok {
|
||||||
|
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
|
||||||
|
_node.AllowMessagesDispatch = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.RequireOauthOnly(); ok {
|
||||||
|
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||||
|
_node.RequireOauthOnly = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.RequirePrivacySet(); ok {
|
||||||
|
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
|
||||||
|
_node.RequirePrivacySet = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.DefaultMappedModel(); ok {
|
||||||
|
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
|
||||||
|
_node.DefaultMappedModel = value
|
||||||
|
}
|
||||||
if 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,
|
||||||
@@ -1520,6 +1625,54 @@ func (u *GroupUpsert) AddSortOrder(v int) *GroupUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (u *GroupUpsert) SetAllowMessagesDispatch(v bool) *GroupUpsert {
|
||||||
|
u.Set(group.FieldAllowMessagesDispatch, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateAllowMessagesDispatch() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldAllowMessagesDispatch)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (u *GroupUpsert) SetRequireOauthOnly(v bool) *GroupUpsert {
|
||||||
|
u.Set(group.FieldRequireOauthOnly, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateRequireOauthOnly() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldRequireOauthOnly)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (u *GroupUpsert) SetRequirePrivacySet(v bool) *GroupUpsert {
|
||||||
|
u.Set(group.FieldRequirePrivacySet, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateRequirePrivacySet() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldRequirePrivacySet)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (u *GroupUpsert) SetDefaultMappedModel(v string) *GroupUpsert {
|
||||||
|
u.Set(group.FieldDefaultMappedModel, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsert) UpdateDefaultMappedModel() *GroupUpsert {
|
||||||
|
u.SetExcluded(group.FieldDefaultMappedModel)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// 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:
|
||||||
//
|
//
|
||||||
@@ -2188,6 +2341,62 @@ func (u *GroupUpsertOne) UpdateSortOrder() *GroupUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (u *GroupUpsertOne) SetAllowMessagesDispatch(v bool) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetAllowMessagesDispatch(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateAllowMessagesDispatch() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateAllowMessagesDispatch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (u *GroupUpsertOne) SetRequireOauthOnly(v bool) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetRequireOauthOnly(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateRequireOauthOnly() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateRequireOauthOnly()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (u *GroupUpsertOne) SetRequirePrivacySet(v bool) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetRequirePrivacySet(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateRequirePrivacySet() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateRequirePrivacySet()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (u *GroupUpsertOne) SetDefaultMappedModel(v string) *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetDefaultMappedModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertOne) UpdateDefaultMappedModel() *GroupUpsertOne {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateDefaultMappedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -3022,6 +3231,62 @@ func (u *GroupUpsertBulk) UpdateSortOrder() *GroupUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (u *GroupUpsertBulk) SetAllowMessagesDispatch(v bool) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetAllowMessagesDispatch(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAllowMessagesDispatch sets the "allow_messages_dispatch" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateAllowMessagesDispatch() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateAllowMessagesDispatch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (u *GroupUpsertBulk) SetRequireOauthOnly(v bool) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetRequireOauthOnly(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequireOauthOnly sets the "require_oauth_only" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateRequireOauthOnly() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateRequireOauthOnly()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (u *GroupUpsertBulk) SetRequirePrivacySet(v bool) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetRequirePrivacySet(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequirePrivacySet sets the "require_privacy_set" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateRequirePrivacySet() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateRequirePrivacySet()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (u *GroupUpsertBulk) SetDefaultMappedModel(v string) *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.SetDefaultMappedModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDefaultMappedModel sets the "default_mapped_model" field to the value that was provided on create.
|
||||||
|
func (u *GroupUpsertBulk) UpdateDefaultMappedModel() *GroupUpsertBulk {
|
||||||
|
return u.Update(func(s *GroupUpsert) {
|
||||||
|
s.UpdateDefaultMappedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|||||||
@@ -625,6 +625,62 @@ func (_u *GroupUpdate) AddSortOrder(v int) *GroupUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (_u *GroupUpdate) SetAllowMessagesDispatch(v bool) *GroupUpdate {
|
||||||
|
_u.mutation.SetAllowMessagesDispatch(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdate) SetNillableAllowMessagesDispatch(v *bool) *GroupUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAllowMessagesDispatch(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (_u *GroupUpdate) SetRequireOauthOnly(v bool) *GroupUpdate {
|
||||||
|
_u.mutation.SetRequireOauthOnly(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdate) SetNillableRequireOauthOnly(v *bool) *GroupUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequireOauthOnly(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (_u *GroupUpdate) SetRequirePrivacySet(v bool) *GroupUpdate {
|
||||||
|
_u.mutation.SetRequirePrivacySet(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdate) SetNillableRequirePrivacySet(v *bool) *GroupUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequirePrivacySet(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (_u *GroupUpdate) SetDefaultMappedModel(v string) *GroupUpdate {
|
||||||
|
_u.mutation.SetDefaultMappedModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdate) SetNillableDefaultMappedModel(v *string) *GroupUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDefaultMappedModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// 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...)
|
||||||
@@ -910,6 +966,11 @@ func (_u *GroupUpdate) check() error {
|
|||||||
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
|
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.DefaultMappedModel(); ok {
|
||||||
|
if err := group.DefaultMappedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1110,6 +1171,18 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.AddedSortOrder(); ok {
|
if value, ok := _u.mutation.AddedSortOrder(); ok {
|
||||||
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
|
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.AllowMessagesDispatch(); ok {
|
||||||
|
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.RequireOauthOnly(); ok {
|
||||||
|
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.RequirePrivacySet(); ok {
|
||||||
|
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.DefaultMappedModel(); ok {
|
||||||
|
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
@@ -2014,6 +2087,62 @@ func (_u *GroupUpdateOne) AddSortOrder(v int) *GroupUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAllowMessagesDispatch sets the "allow_messages_dispatch" field.
|
||||||
|
func (_u *GroupUpdateOne) SetAllowMessagesDispatch(v bool) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetAllowMessagesDispatch(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAllowMessagesDispatch sets the "allow_messages_dispatch" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdateOne) SetNillableAllowMessagesDispatch(v *bool) *GroupUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAllowMessagesDispatch(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||||
|
func (_u *GroupUpdateOne) SetRequireOauthOnly(v bool) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetRequireOauthOnly(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequireOauthOnly sets the "require_oauth_only" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdateOne) SetNillableRequireOauthOnly(v *bool) *GroupUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequireOauthOnly(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirePrivacySet sets the "require_privacy_set" field.
|
||||||
|
func (_u *GroupUpdateOne) SetRequirePrivacySet(v bool) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetRequirePrivacySet(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequirePrivacySet sets the "require_privacy_set" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdateOne) SetNillableRequirePrivacySet(v *bool) *GroupUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequirePrivacySet(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultMappedModel sets the "default_mapped_model" field.
|
||||||
|
func (_u *GroupUpdateOne) SetDefaultMappedModel(v string) *GroupUpdateOne {
|
||||||
|
_u.mutation.SetDefaultMappedModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDefaultMappedModel sets the "default_mapped_model" field if the given value is not nil.
|
||||||
|
func (_u *GroupUpdateOne) SetNillableDefaultMappedModel(v *string) *GroupUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDefaultMappedModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// 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...)
|
||||||
@@ -2312,6 +2441,11 @@ func (_u *GroupUpdateOne) check() error {
|
|||||||
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
|
return &ValidationError{Name: "subscription_type", err: fmt.Errorf(`ent: validator failed for field "Group.subscription_type": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.DefaultMappedModel(); ok {
|
||||||
|
if err := group.DefaultMappedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2529,6 +2663,18 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
|||||||
if value, ok := _u.mutation.AddedSortOrder(); ok {
|
if value, ok := _u.mutation.AddedSortOrder(); ok {
|
||||||
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
|
_spec.AddField(group.FieldSortOrder, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.AllowMessagesDispatch(); ok {
|
||||||
|
_spec.SetField(group.FieldAllowMessagesDispatch, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.RequireOauthOnly(); ok {
|
||||||
|
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.RequirePrivacySet(); ok {
|
||||||
|
_spec.SetField(group.FieldRequirePrivacySet, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.DefaultMappedModel(); ok {
|
||||||
|
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
|
||||||
|
}
|
||||||
if _u.mutation.APIKeysCleared() {
|
if _u.mutation.APIKeysCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
|
|||||||
@@ -177,6 +177,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 TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary
|
||||||
|
// function as TLSFingerprintProfile mutator.
|
||||||
|
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileMutation) (ent.Value, error)
|
||||||
|
|
||||||
|
// Mutate calls f(ctx, m).
|
||||||
|
func (f TLSFingerprintProfileFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
|
if mv, ok := m.(*ent.TLSFingerprintProfileMutation); ok {
|
||||||
|
return f(ctx, mv)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.TLSFingerprintProfileMutation", m)
|
||||||
|
}
|
||||||
|
|
||||||
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary
|
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary
|
||||||
// function as UsageCleanupTask mutator.
|
// function as UsageCleanupTask mutator.
|
||||||
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskMutation) (ent.Value, error)
|
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskMutation) (ent.Value, error)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
"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"
|
||||||
@@ -466,6 +467,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 TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||||
|
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileQuery) (ent.Value, error)
|
||||||
|
|
||||||
|
// Query calls f(ctx, q).
|
||||||
|
func (f TLSFingerprintProfileFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||||
|
if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TraverseTLSFingerprintProfile type is an adapter to allow the use of ordinary function as Traverser.
|
||||||
|
type TraverseTLSFingerprintProfile func(context.Context, *ent.TLSFingerprintProfileQuery) error
|
||||||
|
|
||||||
|
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||||
|
func (f TraverseTLSFingerprintProfile) Intercept(next ent.Querier) ent.Querier {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse calls f(ctx, q).
|
||||||
|
func (f TraverseTLSFingerprintProfile) Traverse(ctx context.Context, q ent.Query) error {
|
||||||
|
if q, ok := q.(*ent.TLSFingerprintProfileQuery); ok {
|
||||||
|
return f(ctx, q)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected query type %T. expect *ent.TLSFingerprintProfileQuery", q)
|
||||||
|
}
|
||||||
|
|
||||||
// The UsageCleanupTaskFunc type is an adapter to allow the use of ordinary function as a Querier.
|
// 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)
|
type UsageCleanupTaskFunc func(context.Context, *ent.UsageCleanupTaskQuery) (ent.Value, error)
|
||||||
|
|
||||||
@@ -686,6 +714,8 @@ func NewQuery(q ent.Query) (Query, error) {
|
|||||||
return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil
|
return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, 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.TLSFingerprintProfileQuery:
|
||||||
|
return &query[*ent.TLSFingerprintProfileQuery, predicate.TLSFingerprintProfile, tlsfingerprintprofile.OrderOption]{typ: ent.TypeTLSFingerprintProfile, tq: q}, nil
|
||||||
case *ent.UsageCleanupTaskQuery:
|
case *ent.UsageCleanupTaskQuery:
|
||||||
return &query[*ent.UsageCleanupTaskQuery, predicate.UsageCleanupTask, usagecleanuptask.OrderOption]{typ: ent.TypeUsageCleanupTask, tq: q}, nil
|
return &query[*ent.UsageCleanupTaskQuery, predicate.UsageCleanupTask, usagecleanuptask.OrderOption]{typ: ent.TypeUsageCleanupTask, tq: q}, nil
|
||||||
case *ent.UsageLogQuery:
|
case *ent.UsageLogQuery:
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ var (
|
|||||||
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
{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: "load_factor", Type: field.TypeInt, Nullable: true},
|
||||||
{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: "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"},
|
||||||
@@ -132,7 +133,7 @@ var (
|
|||||||
ForeignKeys: []*schema.ForeignKey{
|
ForeignKeys: []*schema.ForeignKey{
|
||||||
{
|
{
|
||||||
Symbol: "accounts_proxies_proxy",
|
Symbol: "accounts_proxies_proxy",
|
||||||
Columns: []*schema.Column{AccountsColumns[27]},
|
Columns: []*schema.Column{AccountsColumns[28]},
|
||||||
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
@@ -151,52 +152,52 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "account_status",
|
Name: "account_status",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[13]},
|
Columns: []*schema.Column{AccountsColumns[14]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_proxy_id",
|
Name: "account_proxy_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[27]},
|
Columns: []*schema.Column{AccountsColumns[28]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_priority",
|
Name: "account_priority",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[11]},
|
Columns: []*schema.Column{AccountsColumns[12]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_last_used_at",
|
Name: "account_last_used_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[15]},
|
Columns: []*schema.Column{AccountsColumns[16]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_schedulable",
|
Name: "account_schedulable",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[18]},
|
Columns: []*schema.Column{AccountsColumns[19]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_rate_limited_at",
|
Name: "account_rate_limited_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[19]},
|
Columns: []*schema.Column{AccountsColumns[20]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_rate_limit_reset_at",
|
Name: "account_rate_limit_reset_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[20]},
|
Columns: []*schema.Column{AccountsColumns[21]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_overload_until",
|
Name: "account_overload_until",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[21]},
|
Columns: []*schema.Column{AccountsColumns[22]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_platform_priority",
|
Name: "account_platform_priority",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[11]},
|
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[12]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_priority_status",
|
Name: "account_priority_status",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AccountsColumns[11], AccountsColumns[13]},
|
Columns: []*schema.Column{AccountsColumns[12], AccountsColumns[14]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "account_deleted_at",
|
Name: "account_deleted_at",
|
||||||
@@ -250,6 +251,7 @@ var (
|
|||||||
{Name: "title", Type: field.TypeString, Size: 200},
|
{Name: "title", Type: field.TypeString, Size: 200},
|
||||||
{Name: "content", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
{Name: "content", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||||
{Name: "status", Type: field.TypeString, Size: 20, Default: "draft"},
|
{Name: "status", Type: field.TypeString, Size: 20, Default: "draft"},
|
||||||
|
{Name: "notify_mode", Type: field.TypeString, Size: 20, Default: "silent"},
|
||||||
{Name: "targeting", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
{Name: "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: "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: "ends_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
@@ -272,17 +274,17 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "announcement_created_at",
|
Name: "announcement_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AnnouncementsColumns[9]},
|
Columns: []*schema.Column{AnnouncementsColumns[10]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "announcement_starts_at",
|
Name: "announcement_starts_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AnnouncementsColumns[5]},
|
Columns: []*schema.Column{AnnouncementsColumns[6]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "announcement_ends_at",
|
Name: "announcement_ends_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{AnnouncementsColumns[6]},
|
Columns: []*schema.Column{AnnouncementsColumns[7]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -406,6 +408,10 @@ var (
|
|||||||
{Name: "mcp_xml_inject", Type: field.TypeBool, Default: true},
|
{Name: "mcp_xml_inject", Type: field.TypeBool, Default: true},
|
||||||
{Name: "supported_model_scopes", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
{Name: "supported_model_scopes", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
{Name: "sort_order", Type: field.TypeInt, Default: 0},
|
{Name: "sort_order", Type: field.TypeInt, Default: 0},
|
||||||
|
{Name: "allow_messages_dispatch", Type: field.TypeBool, Default: false},
|
||||||
|
{Name: "require_oauth_only", Type: field.TypeBool, Default: false},
|
||||||
|
{Name: "require_privacy_set", Type: field.TypeBool, Default: false},
|
||||||
|
{Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""},
|
||||||
}
|
}
|
||||||
// GroupsTable holds the schema information for the "groups" table.
|
// GroupsTable holds the schema information for the "groups" table.
|
||||||
GroupsTable = &schema.Table{
|
GroupsTable = &schema.Table{
|
||||||
@@ -669,6 +675,30 @@ var (
|
|||||||
Columns: SettingsColumns,
|
Columns: SettingsColumns,
|
||||||
PrimaryKey: []*schema.Column{SettingsColumns[0]},
|
PrimaryKey: []*schema.Column{SettingsColumns[0]},
|
||||||
}
|
}
|
||||||
|
// TLSFingerprintProfilesColumns holds the columns for the "tls_fingerprint_profiles" table.
|
||||||
|
TLSFingerprintProfilesColumns = []*schema.Column{
|
||||||
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
|
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||||
|
{Name: "name", Type: field.TypeString, Unique: true, Size: 100},
|
||||||
|
{Name: "description", Type: field.TypeString, Nullable: true, Size: 2147483647},
|
||||||
|
{Name: "enable_grease", Type: field.TypeBool, Default: false},
|
||||||
|
{Name: "cipher_suites", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "curves", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "point_formats", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "signature_algorithms", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "alpn_protocols", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "supported_versions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "key_share_groups", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "psk_modes", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
{Name: "extensions", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||||
|
}
|
||||||
|
// TLSFingerprintProfilesTable holds the schema information for the "tls_fingerprint_profiles" table.
|
||||||
|
TLSFingerprintProfilesTable = &schema.Table{
|
||||||
|
Name: "tls_fingerprint_profiles",
|
||||||
|
Columns: TLSFingerprintProfilesColumns,
|
||||||
|
PrimaryKey: []*schema.Column{TLSFingerprintProfilesColumns[0]},
|
||||||
|
}
|
||||||
// UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table.
|
// UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table.
|
||||||
UsageCleanupTasksColumns = []*schema.Column{
|
UsageCleanupTasksColumns = []*schema.Column{
|
||||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
@@ -712,6 +742,12 @@ var (
|
|||||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||||
{Name: "request_id", Type: field.TypeString, Size: 64},
|
{Name: "request_id", Type: field.TypeString, Size: 64},
|
||||||
{Name: "model", Type: field.TypeString, Size: 100},
|
{Name: "model", Type: field.TypeString, Size: 100},
|
||||||
|
{Name: "requested_model", Type: field.TypeString, Nullable: true, Size: 100},
|
||||||
|
{Name: "upstream_model", Type: field.TypeString, Nullable: true, Size: 100},
|
||||||
|
{Name: "channel_id", Type: field.TypeInt64, Nullable: true},
|
||||||
|
{Name: "model_mapping_chain", Type: field.TypeString, Nullable: true, Size: 500},
|
||||||
|
{Name: "billing_tier", Type: field.TypeString, Nullable: true, Size: 50},
|
||||||
|
{Name: "billing_mode", Type: field.TypeString, Nullable: true, Size: 20},
|
||||||
{Name: "input_tokens", Type: field.TypeInt, Default: 0},
|
{Name: "input_tokens", Type: field.TypeInt, Default: 0},
|
||||||
{Name: "output_tokens", Type: field.TypeInt, Default: 0},
|
{Name: "output_tokens", Type: field.TypeInt, Default: 0},
|
||||||
{Name: "cache_creation_tokens", Type: field.TypeInt, Default: 0},
|
{Name: "cache_creation_tokens", Type: field.TypeInt, Default: 0},
|
||||||
@@ -751,31 +787,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[28]},
|
Columns: []*schema.Column{UsageLogsColumns[34]},
|
||||||
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[29]},
|
Columns: []*schema.Column{UsageLogsColumns[35]},
|
||||||
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[30]},
|
Columns: []*schema.Column{UsageLogsColumns[36]},
|
||||||
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[31]},
|
Columns: []*schema.Column{UsageLogsColumns[37]},
|
||||||
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[32]},
|
Columns: []*schema.Column{UsageLogsColumns[38]},
|
||||||
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.SetNull,
|
||||||
},
|
},
|
||||||
@@ -784,38 +820,43 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id",
|
Name: "usagelog_user_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[31]},
|
Columns: []*schema.Column{UsageLogsColumns[37]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id",
|
Name: "usagelog_api_key_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28]},
|
Columns: []*schema.Column{UsageLogsColumns[34]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_account_id",
|
Name: "usagelog_account_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[29]},
|
Columns: []*schema.Column{UsageLogsColumns[35]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_group_id",
|
Name: "usagelog_group_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[30]},
|
Columns: []*schema.Column{UsageLogsColumns[36]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_subscription_id",
|
Name: "usagelog_subscription_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[32]},
|
Columns: []*schema.Column{UsageLogsColumns[38]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_created_at",
|
Name: "usagelog_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[33]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_model",
|
Name: "usagelog_model",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[2]},
|
Columns: []*schema.Column{UsageLogsColumns[2]},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "usagelog_requested_model",
|
||||||
|
Unique: false,
|
||||||
|
Columns: []*schema.Column{UsageLogsColumns[3]},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_request_id",
|
Name: "usagelog_request_id",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
@@ -824,17 +865,17 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "usagelog_user_id_created_at",
|
Name: "usagelog_user_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[31], UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[37], UsageLogsColumns[33]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_api_key_id_created_at",
|
Name: "usagelog_api_key_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[28], UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[34], UsageLogsColumns[33]},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "usagelog_group_id_created_at",
|
Name: "usagelog_group_id_created_at",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UsageLogsColumns[30], UsageLogsColumns[27]},
|
Columns: []*schema.Column{UsageLogsColumns[36], UsageLogsColumns[33]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1100,6 +1141,7 @@ var (
|
|||||||
RedeemCodesTable,
|
RedeemCodesTable,
|
||||||
SecuritySecretsTable,
|
SecuritySecretsTable,
|
||||||
SettingsTable,
|
SettingsTable,
|
||||||
|
TLSFingerprintProfilesTable,
|
||||||
UsageCleanupTasksTable,
|
UsageCleanupTasksTable,
|
||||||
UsageLogsTable,
|
UsageLogsTable,
|
||||||
UsersTable,
|
UsersTable,
|
||||||
@@ -1164,6 +1206,9 @@ func init() {
|
|||||||
SettingsTable.Annotation = &entsql.Annotation{
|
SettingsTable.Annotation = &entsql.Annotation{
|
||||||
Table: "settings",
|
Table: "settings",
|
||||||
}
|
}
|
||||||
|
TLSFingerprintProfilesTable.Annotation = &entsql.Annotation{
|
||||||
|
Table: "tls_fingerprint_profiles",
|
||||||
|
}
|
||||||
UsageCleanupTasksTable.Annotation = &entsql.Annotation{
|
UsageCleanupTasksTable.Annotation = &entsql.Annotation{
|
||||||
Table: "usage_cleanup_tasks",
|
Table: "usage_cleanup_tasks",
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,9 @@ type SecuritySecret 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)
|
||||||
|
|
||||||
|
// TLSFingerprintProfile is the predicate function for tlsfingerprintprofile builders.
|
||||||
|
type TLSFingerprintProfile func(*sql.Selector)
|
||||||
|
|
||||||
// UsageCleanupTask is the predicate function for usagecleanuptask builders.
|
// UsageCleanupTask is the predicate function for usagecleanuptask builders.
|
||||||
type UsageCleanupTask func(*sql.Selector)
|
type UsageCleanupTask func(*sql.Selector)
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/ent/schema"
|
"github.com/Wei-Shaw/sub2api/ent/schema"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
"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"
|
||||||
@@ -212,29 +213,29 @@ func init() {
|
|||||||
// account.DefaultConcurrency holds the default value on creation for the concurrency field.
|
// account.DefaultConcurrency holds the default value on creation for the concurrency field.
|
||||||
account.DefaultConcurrency = accountDescConcurrency.Default.(int)
|
account.DefaultConcurrency = accountDescConcurrency.Default.(int)
|
||||||
// accountDescPriority is the schema descriptor for priority field.
|
// accountDescPriority is the schema descriptor for priority field.
|
||||||
accountDescPriority := accountFields[8].Descriptor()
|
accountDescPriority := accountFields[9].Descriptor()
|
||||||
// account.DefaultPriority holds the default value on creation for the priority field.
|
// account.DefaultPriority 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 is the schema descriptor for rate_multiplier field.
|
||||||
accountDescRateMultiplier := accountFields[9].Descriptor()
|
accountDescRateMultiplier := accountFields[10].Descriptor()
|
||||||
// account.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
// account.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
||||||
account.DefaultRateMultiplier = accountDescRateMultiplier.Default.(float64)
|
account.DefaultRateMultiplier = accountDescRateMultiplier.Default.(float64)
|
||||||
// accountDescStatus is the schema descriptor for status field.
|
// accountDescStatus is the schema descriptor for status field.
|
||||||
accountDescStatus := accountFields[10].Descriptor()
|
accountDescStatus := accountFields[11].Descriptor()
|
||||||
// account.DefaultStatus holds the default value on creation for the status field.
|
// account.DefaultStatus 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[14].Descriptor()
|
accountDescAutoPauseOnExpired := accountFields[15].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[15].Descriptor()
|
accountDescSchedulable := accountFields[16].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[23].Descriptor()
|
accountDescSessionWindowStatus := accountFields[24].Descriptor()
|
||||||
// account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
|
// account.SessionWindowStatusValidator 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()
|
||||||
@@ -277,12 +278,18 @@ func init() {
|
|||||||
announcement.DefaultStatus = announcementDescStatus.Default.(string)
|
announcement.DefaultStatus = announcementDescStatus.Default.(string)
|
||||||
// announcement.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
// 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)
|
announcement.StatusValidator = announcementDescStatus.Validators[0].(func(string) error)
|
||||||
|
// announcementDescNotifyMode is the schema descriptor for notify_mode field.
|
||||||
|
announcementDescNotifyMode := announcementFields[3].Descriptor()
|
||||||
|
// announcement.DefaultNotifyMode holds the default value on creation for the notify_mode field.
|
||||||
|
announcement.DefaultNotifyMode = announcementDescNotifyMode.Default.(string)
|
||||||
|
// announcement.NotifyModeValidator is a validator for the "notify_mode" field. It is called by the builders before save.
|
||||||
|
announcement.NotifyModeValidator = announcementDescNotifyMode.Validators[0].(func(string) error)
|
||||||
// announcementDescCreatedAt is the schema descriptor for created_at field.
|
// announcementDescCreatedAt is the schema descriptor for created_at field.
|
||||||
announcementDescCreatedAt := announcementFields[8].Descriptor()
|
announcementDescCreatedAt := announcementFields[9].Descriptor()
|
||||||
// announcement.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// announcement.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
announcement.DefaultCreatedAt = announcementDescCreatedAt.Default.(func() time.Time)
|
announcement.DefaultCreatedAt = announcementDescCreatedAt.Default.(func() time.Time)
|
||||||
// announcementDescUpdatedAt is the schema descriptor for updated_at field.
|
// announcementDescUpdatedAt is the schema descriptor for updated_at field.
|
||||||
announcementDescUpdatedAt := announcementFields[9].Descriptor()
|
announcementDescUpdatedAt := announcementFields[10].Descriptor()
|
||||||
// announcement.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
// announcement.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||||
announcement.DefaultUpdatedAt = announcementDescUpdatedAt.Default.(func() time.Time)
|
announcement.DefaultUpdatedAt = announcementDescUpdatedAt.Default.(func() time.Time)
|
||||||
// announcement.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
// announcement.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||||
@@ -447,6 +454,24 @@ func init() {
|
|||||||
groupDescSortOrder := groupFields[26].Descriptor()
|
groupDescSortOrder := groupFields[26].Descriptor()
|
||||||
// group.DefaultSortOrder holds the default value on creation for the sort_order field.
|
// group.DefaultSortOrder holds the default value on creation for the sort_order field.
|
||||||
group.DefaultSortOrder = groupDescSortOrder.Default.(int)
|
group.DefaultSortOrder = groupDescSortOrder.Default.(int)
|
||||||
|
// groupDescAllowMessagesDispatch is the schema descriptor for allow_messages_dispatch field.
|
||||||
|
groupDescAllowMessagesDispatch := groupFields[27].Descriptor()
|
||||||
|
// group.DefaultAllowMessagesDispatch holds the default value on creation for the allow_messages_dispatch field.
|
||||||
|
group.DefaultAllowMessagesDispatch = groupDescAllowMessagesDispatch.Default.(bool)
|
||||||
|
// groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field.
|
||||||
|
groupDescRequireOauthOnly := groupFields[28].Descriptor()
|
||||||
|
// group.DefaultRequireOauthOnly holds the default value on creation for the require_oauth_only field.
|
||||||
|
group.DefaultRequireOauthOnly = groupDescRequireOauthOnly.Default.(bool)
|
||||||
|
// groupDescRequirePrivacySet is the schema descriptor for require_privacy_set field.
|
||||||
|
groupDescRequirePrivacySet := groupFields[29].Descriptor()
|
||||||
|
// group.DefaultRequirePrivacySet holds the default value on creation for the require_privacy_set field.
|
||||||
|
group.DefaultRequirePrivacySet = groupDescRequirePrivacySet.Default.(bool)
|
||||||
|
// groupDescDefaultMappedModel is the schema descriptor for default_mapped_model field.
|
||||||
|
groupDescDefaultMappedModel := groupFields[30].Descriptor()
|
||||||
|
// group.DefaultDefaultMappedModel holds the default value on creation for the default_mapped_model field.
|
||||||
|
group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string)
|
||||||
|
// group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
|
||||||
|
group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error)
|
||||||
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
|
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
|
||||||
idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields()
|
idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields()
|
||||||
_ = idempotencyrecordMixinFields0
|
_ = idempotencyrecordMixinFields0
|
||||||
@@ -730,6 +755,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)
|
||||||
|
tlsfingerprintprofileMixin := schema.TLSFingerprintProfile{}.Mixin()
|
||||||
|
tlsfingerprintprofileMixinFields0 := tlsfingerprintprofileMixin[0].Fields()
|
||||||
|
_ = tlsfingerprintprofileMixinFields0
|
||||||
|
tlsfingerprintprofileFields := schema.TLSFingerprintProfile{}.Fields()
|
||||||
|
_ = tlsfingerprintprofileFields
|
||||||
|
// tlsfingerprintprofileDescCreatedAt is the schema descriptor for created_at field.
|
||||||
|
tlsfingerprintprofileDescCreatedAt := tlsfingerprintprofileMixinFields0[0].Descriptor()
|
||||||
|
// tlsfingerprintprofile.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
|
tlsfingerprintprofile.DefaultCreatedAt = tlsfingerprintprofileDescCreatedAt.Default.(func() time.Time)
|
||||||
|
// tlsfingerprintprofileDescUpdatedAt is the schema descriptor for updated_at field.
|
||||||
|
tlsfingerprintprofileDescUpdatedAt := tlsfingerprintprofileMixinFields0[1].Descriptor()
|
||||||
|
// tlsfingerprintprofile.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||||
|
tlsfingerprintprofile.DefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.Default.(func() time.Time)
|
||||||
|
// tlsfingerprintprofile.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||||
|
tlsfingerprintprofile.UpdateDefaultUpdatedAt = tlsfingerprintprofileDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||||
|
// tlsfingerprintprofileDescName is the schema descriptor for name field.
|
||||||
|
tlsfingerprintprofileDescName := tlsfingerprintprofileFields[0].Descriptor()
|
||||||
|
// tlsfingerprintprofile.NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||||
|
tlsfingerprintprofile.NameValidator = func() func(string) error {
|
||||||
|
validators := tlsfingerprintprofileDescName.Validators
|
||||||
|
fns := [...]func(string) error{
|
||||||
|
validators[0].(func(string) error),
|
||||||
|
validators[1].(func(string) error),
|
||||||
|
}
|
||||||
|
return func(name string) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// tlsfingerprintprofileDescEnableGrease is the schema descriptor for enable_grease field.
|
||||||
|
tlsfingerprintprofileDescEnableGrease := tlsfingerprintprofileFields[2].Descriptor()
|
||||||
|
// tlsfingerprintprofile.DefaultEnableGrease holds the default value on creation for the enable_grease field.
|
||||||
|
tlsfingerprintprofile.DefaultEnableGrease = tlsfingerprintprofileDescEnableGrease.Default.(bool)
|
||||||
usagecleanuptaskMixin := schema.UsageCleanupTask{}.Mixin()
|
usagecleanuptaskMixin := schema.UsageCleanupTask{}.Mixin()
|
||||||
usagecleanuptaskMixinFields0 := usagecleanuptaskMixin[0].Fields()
|
usagecleanuptaskMixinFields0 := usagecleanuptaskMixin[0].Fields()
|
||||||
_ = usagecleanuptaskMixinFields0
|
_ = usagecleanuptaskMixinFields0
|
||||||
@@ -805,92 +867,112 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
// usagelogDescRequestedModel is the schema descriptor for requested_model field.
|
||||||
|
usagelogDescRequestedModel := usagelogFields[5].Descriptor()
|
||||||
|
// usagelog.RequestedModelValidator is a validator for the "requested_model" field. It is called by the builders before save.
|
||||||
|
usagelog.RequestedModelValidator = usagelogDescRequestedModel.Validators[0].(func(string) error)
|
||||||
|
// usagelogDescUpstreamModel is the schema descriptor for upstream_model field.
|
||||||
|
usagelogDescUpstreamModel := usagelogFields[6].Descriptor()
|
||||||
|
// usagelog.UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
|
||||||
|
usagelog.UpstreamModelValidator = usagelogDescUpstreamModel.Validators[0].(func(string) error)
|
||||||
|
// usagelogDescModelMappingChain is the schema descriptor for model_mapping_chain field.
|
||||||
|
usagelogDescModelMappingChain := usagelogFields[8].Descriptor()
|
||||||
|
// usagelog.ModelMappingChainValidator is a validator for the "model_mapping_chain" field. It is called by the builders before save.
|
||||||
|
usagelog.ModelMappingChainValidator = usagelogDescModelMappingChain.Validators[0].(func(string) error)
|
||||||
|
// usagelogDescBillingTier is the schema descriptor for billing_tier field.
|
||||||
|
usagelogDescBillingTier := usagelogFields[9].Descriptor()
|
||||||
|
// usagelog.BillingTierValidator is a validator for the "billing_tier" field. It is called by the builders before save.
|
||||||
|
usagelog.BillingTierValidator = usagelogDescBillingTier.Validators[0].(func(string) error)
|
||||||
|
// usagelogDescBillingMode is the schema descriptor for billing_mode field.
|
||||||
|
usagelogDescBillingMode := usagelogFields[10].Descriptor()
|
||||||
|
// usagelog.BillingModeValidator is a validator for the "billing_mode" field. It is called by the builders before save.
|
||||||
|
usagelog.BillingModeValidator = usagelogDescBillingMode.Validators[0].(func(string) error)
|
||||||
// usagelogDescInputTokens is the schema descriptor for input_tokens field.
|
// usagelogDescInputTokens is the schema descriptor for input_tokens field.
|
||||||
usagelogDescInputTokens := usagelogFields[7].Descriptor()
|
usagelogDescInputTokens := usagelogFields[13].Descriptor()
|
||||||
// usagelog.DefaultInputTokens holds the default value on creation for the input_tokens field.
|
// usagelog.DefaultInputTokens holds the default value on creation for the input_tokens field.
|
||||||
usagelog.DefaultInputTokens = usagelogDescInputTokens.Default.(int)
|
usagelog.DefaultInputTokens = usagelogDescInputTokens.Default.(int)
|
||||||
// usagelogDescOutputTokens is the schema descriptor for output_tokens field.
|
// usagelogDescOutputTokens is the schema descriptor for output_tokens field.
|
||||||
usagelogDescOutputTokens := usagelogFields[8].Descriptor()
|
usagelogDescOutputTokens := usagelogFields[14].Descriptor()
|
||||||
// usagelog.DefaultOutputTokens holds the default value on creation for the output_tokens field.
|
// usagelog.DefaultOutputTokens holds the default value on creation for the output_tokens field.
|
||||||
usagelog.DefaultOutputTokens = usagelogDescOutputTokens.Default.(int)
|
usagelog.DefaultOutputTokens = usagelogDescOutputTokens.Default.(int)
|
||||||
// usagelogDescCacheCreationTokens is the schema descriptor for cache_creation_tokens field.
|
// usagelogDescCacheCreationTokens is the schema descriptor for cache_creation_tokens field.
|
||||||
usagelogDescCacheCreationTokens := usagelogFields[9].Descriptor()
|
usagelogDescCacheCreationTokens := usagelogFields[15].Descriptor()
|
||||||
// usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field.
|
// usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field.
|
||||||
usagelog.DefaultCacheCreationTokens = usagelogDescCacheCreationTokens.Default.(int)
|
usagelog.DefaultCacheCreationTokens = usagelogDescCacheCreationTokens.Default.(int)
|
||||||
// usagelogDescCacheReadTokens is the schema descriptor for cache_read_tokens field.
|
// usagelogDescCacheReadTokens is the schema descriptor for cache_read_tokens field.
|
||||||
usagelogDescCacheReadTokens := usagelogFields[10].Descriptor()
|
usagelogDescCacheReadTokens := usagelogFields[16].Descriptor()
|
||||||
// usagelog.DefaultCacheReadTokens holds the default value on creation for the cache_read_tokens field.
|
// usagelog.DefaultCacheReadTokens holds the default value on creation for the cache_read_tokens field.
|
||||||
usagelog.DefaultCacheReadTokens = usagelogDescCacheReadTokens.Default.(int)
|
usagelog.DefaultCacheReadTokens = usagelogDescCacheReadTokens.Default.(int)
|
||||||
// usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field.
|
// usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field.
|
||||||
usagelogDescCacheCreation5mTokens := usagelogFields[11].Descriptor()
|
usagelogDescCacheCreation5mTokens := usagelogFields[17].Descriptor()
|
||||||
// usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field.
|
// usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field.
|
||||||
usagelog.DefaultCacheCreation5mTokens = usagelogDescCacheCreation5mTokens.Default.(int)
|
usagelog.DefaultCacheCreation5mTokens = usagelogDescCacheCreation5mTokens.Default.(int)
|
||||||
// usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field.
|
// usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field.
|
||||||
usagelogDescCacheCreation1hTokens := usagelogFields[12].Descriptor()
|
usagelogDescCacheCreation1hTokens := usagelogFields[18].Descriptor()
|
||||||
// usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field.
|
// usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field.
|
||||||
usagelog.DefaultCacheCreation1hTokens = usagelogDescCacheCreation1hTokens.Default.(int)
|
usagelog.DefaultCacheCreation1hTokens = usagelogDescCacheCreation1hTokens.Default.(int)
|
||||||
// usagelogDescInputCost is the schema descriptor for input_cost field.
|
// usagelogDescInputCost is the schema descriptor for input_cost field.
|
||||||
usagelogDescInputCost := usagelogFields[13].Descriptor()
|
usagelogDescInputCost := usagelogFields[19].Descriptor()
|
||||||
// usagelog.DefaultInputCost holds the default value on creation for the input_cost field.
|
// usagelog.DefaultInputCost holds the default value on creation for the input_cost field.
|
||||||
usagelog.DefaultInputCost = usagelogDescInputCost.Default.(float64)
|
usagelog.DefaultInputCost = usagelogDescInputCost.Default.(float64)
|
||||||
// usagelogDescOutputCost is the schema descriptor for output_cost field.
|
// usagelogDescOutputCost is the schema descriptor for output_cost field.
|
||||||
usagelogDescOutputCost := usagelogFields[14].Descriptor()
|
usagelogDescOutputCost := usagelogFields[20].Descriptor()
|
||||||
// usagelog.DefaultOutputCost holds the default value on creation for the output_cost field.
|
// usagelog.DefaultOutputCost holds the default value on creation for the output_cost field.
|
||||||
usagelog.DefaultOutputCost = usagelogDescOutputCost.Default.(float64)
|
usagelog.DefaultOutputCost = usagelogDescOutputCost.Default.(float64)
|
||||||
// usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field.
|
// usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field.
|
||||||
usagelogDescCacheCreationCost := usagelogFields[15].Descriptor()
|
usagelogDescCacheCreationCost := usagelogFields[21].Descriptor()
|
||||||
// usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field.
|
// usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field.
|
||||||
usagelog.DefaultCacheCreationCost = usagelogDescCacheCreationCost.Default.(float64)
|
usagelog.DefaultCacheCreationCost = usagelogDescCacheCreationCost.Default.(float64)
|
||||||
// usagelogDescCacheReadCost is the schema descriptor for cache_read_cost field.
|
// usagelogDescCacheReadCost is the schema descriptor for cache_read_cost field.
|
||||||
usagelogDescCacheReadCost := usagelogFields[16].Descriptor()
|
usagelogDescCacheReadCost := usagelogFields[22].Descriptor()
|
||||||
// usagelog.DefaultCacheReadCost holds the default value on creation for the cache_read_cost field.
|
// usagelog.DefaultCacheReadCost holds the default value on creation for the cache_read_cost field.
|
||||||
usagelog.DefaultCacheReadCost = usagelogDescCacheReadCost.Default.(float64)
|
usagelog.DefaultCacheReadCost = usagelogDescCacheReadCost.Default.(float64)
|
||||||
// usagelogDescTotalCost is the schema descriptor for total_cost field.
|
// usagelogDescTotalCost is the schema descriptor for total_cost field.
|
||||||
usagelogDescTotalCost := usagelogFields[17].Descriptor()
|
usagelogDescTotalCost := usagelogFields[23].Descriptor()
|
||||||
// usagelog.DefaultTotalCost holds the default value on creation for the total_cost field.
|
// usagelog.DefaultTotalCost holds the default value on creation for the total_cost field.
|
||||||
usagelog.DefaultTotalCost = usagelogDescTotalCost.Default.(float64)
|
usagelog.DefaultTotalCost = usagelogDescTotalCost.Default.(float64)
|
||||||
// usagelogDescActualCost is the schema descriptor for actual_cost field.
|
// usagelogDescActualCost is the schema descriptor for actual_cost field.
|
||||||
usagelogDescActualCost := usagelogFields[18].Descriptor()
|
usagelogDescActualCost := usagelogFields[24].Descriptor()
|
||||||
// usagelog.DefaultActualCost holds the default value on creation for the actual_cost field.
|
// usagelog.DefaultActualCost holds the default value on creation for the actual_cost field.
|
||||||
usagelog.DefaultActualCost = usagelogDescActualCost.Default.(float64)
|
usagelog.DefaultActualCost = usagelogDescActualCost.Default.(float64)
|
||||||
// usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field.
|
// usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field.
|
||||||
usagelogDescRateMultiplier := usagelogFields[19].Descriptor()
|
usagelogDescRateMultiplier := usagelogFields[25].Descriptor()
|
||||||
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
// usagelog.DefaultRateMultiplier 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[21].Descriptor()
|
usagelogDescBillingType := usagelogFields[27].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[22].Descriptor()
|
usagelogDescStream := usagelogFields[28].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[25].Descriptor()
|
usagelogDescUserAgent := usagelogFields[31].Descriptor()
|
||||||
// usagelog.UserAgentValidator is a validator for the "user_agent" field. It is called by the builders before save.
|
// usagelog.UserAgentValidator 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[26].Descriptor()
|
usagelogDescIPAddress := usagelogFields[32].Descriptor()
|
||||||
// usagelog.IPAddressValidator is a validator for the "ip_address" field. It is called by the builders before save.
|
// usagelog.IPAddressValidator 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[27].Descriptor()
|
usagelogDescImageCount := usagelogFields[33].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[28].Descriptor()
|
usagelogDescImageSize := usagelogFields[34].Descriptor()
|
||||||
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
|
// usagelog.ImageSizeValidator 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)
|
||||||
// usagelogDescMediaType is the schema descriptor for media_type field.
|
// usagelogDescMediaType is the schema descriptor for media_type field.
|
||||||
usagelogDescMediaType := usagelogFields[29].Descriptor()
|
usagelogDescMediaType := usagelogFields[35].Descriptor()
|
||||||
// usagelog.MediaTypeValidator is a validator for the "media_type" field. It is called by the builders before save.
|
// usagelog.MediaTypeValidator is a validator for the "media_type" field. It is called by the builders before save.
|
||||||
usagelog.MediaTypeValidator = usagelogDescMediaType.Validators[0].(func(string) error)
|
usagelog.MediaTypeValidator = usagelogDescMediaType.Validators[0].(func(string) error)
|
||||||
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
|
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
|
||||||
usagelogDescCacheTTLOverridden := usagelogFields[30].Descriptor()
|
usagelogDescCacheTTLOverridden := usagelogFields[36].Descriptor()
|
||||||
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
|
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
|
||||||
usagelog.DefaultCacheTTLOverridden = usagelogDescCacheTTLOverridden.Default.(bool)
|
usagelog.DefaultCacheTTLOverridden = usagelogDescCacheTTLOverridden.Default.(bool)
|
||||||
// usagelogDescCreatedAt is the schema descriptor for created_at field.
|
// usagelogDescCreatedAt is the schema descriptor for created_at field.
|
||||||
usagelogDescCreatedAt := usagelogFields[31].Descriptor()
|
usagelogDescCreatedAt := usagelogFields[37].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()
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ func (Account) Fields() []ent.Field {
|
|||||||
field.Int("concurrency").
|
field.Int("concurrency").
|
||||||
Default(3),
|
Default(3),
|
||||||
|
|
||||||
|
field.Int("load_factor").Optional().Nillable(),
|
||||||
|
|
||||||
// priority: 账户优先级,数值越小优先级越高
|
// priority: 账户优先级,数值越小优先级越高
|
||||||
// 调度器会优先使用高优先级的账户
|
// 调度器会优先使用高优先级的账户
|
||||||
field.Int("priority").
|
field.Int("priority").
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ func (Announcement) Fields() []ent.Field {
|
|||||||
MaxLen(20).
|
MaxLen(20).
|
||||||
Default(domain.AnnouncementStatusDraft).
|
Default(domain.AnnouncementStatusDraft).
|
||||||
Comment("状态: draft, active, archived"),
|
Comment("状态: draft, active, archived"),
|
||||||
|
field.String("notify_mode").
|
||||||
|
MaxLen(20).
|
||||||
|
Default(domain.AnnouncementNotifyModeSilent).
|
||||||
|
Comment("通知模式: silent(仅铃铛), popup(弹窗提醒)"),
|
||||||
field.JSON("targeting", domain.AnnouncementTargeting{}).
|
field.JSON("targeting", domain.AnnouncementTargeting{}).
|
||||||
Optional().
|
Optional().
|
||||||
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
|
||||||
|
|||||||
@@ -148,6 +148,21 @@ func (Group) Fields() []ent.Field {
|
|||||||
field.Int("sort_order").
|
field.Int("sort_order").
|
||||||
Default(0).
|
Default(0).
|
||||||
Comment("分组显示排序,数值越小越靠前"),
|
Comment("分组显示排序,数值越小越靠前"),
|
||||||
|
|
||||||
|
// OpenAI Messages 调度配置 (added by migration 069)
|
||||||
|
field.Bool("allow_messages_dispatch").
|
||||||
|
Default(false).
|
||||||
|
Comment("是否允许 /v1/messages 调度到此 OpenAI 分组"),
|
||||||
|
field.Bool("require_oauth_only").
|
||||||
|
Default(false).
|
||||||
|
Comment("仅允许非 apikey 类型账号关联到此分组"),
|
||||||
|
field.Bool("require_privacy_set").
|
||||||
|
Default(false).
|
||||||
|
Comment("调度时仅允许 privacy 已成功设置的账号"),
|
||||||
|
field.String("default_mapped_model").
|
||||||
|
MaxLen(100).
|
||||||
|
Default("").
|
||||||
|
Comment("默认映射模型 ID,当账号级映射找不到时使用此值"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
100
backend/ent/schema/tls_fingerprint_profile.go
Normal file
100
backend/ent/schema/tls_fingerprint_profile.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Package schema 定义 Ent ORM 的数据库 schema。
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
"entgo.io/ent/dialect/entsql"
|
||||||
|
"entgo.io/ent/schema"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfile 定义 TLS 指纹配置模板的 schema。
|
||||||
|
//
|
||||||
|
// TLS 指纹模板用于模拟特定客户端(如 Claude Code / Node.js)的 TLS 握手特征。
|
||||||
|
// 每个模板包含完整的 ClientHello 参数:加密套件、曲线、扩展等。
|
||||||
|
// 通过 Account.Extra.tls_fingerprint_profile_id 绑定到具体账号。
|
||||||
|
type TLSFingerprintProfile struct {
|
||||||
|
ent.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotations 返回 schema 的注解配置。
|
||||||
|
func (TLSFingerprintProfile) Annotations() []schema.Annotation {
|
||||||
|
return []schema.Annotation{
|
||||||
|
entsql.Annotation{Table: "tls_fingerprint_profiles"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin 返回该 schema 使用的混入组件。
|
||||||
|
func (TLSFingerprintProfile) Mixin() []ent.Mixin {
|
||||||
|
return []ent.Mixin{
|
||||||
|
mixins.TimeMixin{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields 定义 TLS 指纹模板实体的所有字段。
|
||||||
|
func (TLSFingerprintProfile) Fields() []ent.Field {
|
||||||
|
return []ent.Field{
|
||||||
|
// name: 模板名称,唯一标识
|
||||||
|
field.String("name").
|
||||||
|
MaxLen(100).
|
||||||
|
NotEmpty().
|
||||||
|
Unique(),
|
||||||
|
|
||||||
|
// description: 模板描述
|
||||||
|
field.Text("description").
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
|
||||||
|
// enable_grease: 是否启用 GREASE 扩展(Chrome 使用,Node.js 不使用)
|
||||||
|
field.Bool("enable_grease").
|
||||||
|
Default(false),
|
||||||
|
|
||||||
|
// cipher_suites: TLS 加密套件列表(顺序敏感,影响 JA3)
|
||||||
|
field.JSON("cipher_suites", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// curves: 椭圆曲线/支持的组列表
|
||||||
|
field.JSON("curves", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// point_formats: EC 点格式列表
|
||||||
|
field.JSON("point_formats", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// signature_algorithms: 签名算法列表
|
||||||
|
field.JSON("signature_algorithms", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// alpn_protocols: ALPN 协议列表(如 ["http/1.1"])
|
||||||
|
field.JSON("alpn_protocols", []string{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// supported_versions: 支持的 TLS 版本列表(如 [0x0304, 0x0303])
|
||||||
|
field.JSON("supported_versions", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// key_share_groups: Key Share 中发送的曲线组(如 [29] 即 X25519)
|
||||||
|
field.JSON("key_share_groups", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// psk_modes: PSK 密钥交换模式(如 [1] 即 psk_dhe_ke)
|
||||||
|
field.JSON("psk_modes", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
|
||||||
|
// extensions: TLS 扩展类型 ID 列表,按发送顺序排列
|
||||||
|
field.JSON("extensions", []uint16{}).
|
||||||
|
Optional().
|
||||||
|
SchemaType(map[string]string{dialect.Postgres: "jsonb"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,22 @@ func (UsageLog) Fields() []ent.Field {
|
|||||||
field.String("model").
|
field.String("model").
|
||||||
MaxLen(100).
|
MaxLen(100).
|
||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
|
// RequestedModel stores the client-requested model name for stable display and analytics.
|
||||||
|
// NULL means historical rows written before requested_model dual-write was introduced.
|
||||||
|
field.String("requested_model").
|
||||||
|
MaxLen(100).
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
// UpstreamModel stores the actual upstream model name when model mapping
|
||||||
|
// is applied. NULL means no mapping — the requested model was used as-is.
|
||||||
|
field.String("upstream_model").
|
||||||
|
MaxLen(100).
|
||||||
|
Optional().
|
||||||
|
Nillable(),
|
||||||
|
field.Int64("channel_id").Optional().Nillable().Comment("渠道 ID"),
|
||||||
|
field.String("model_mapping_chain").MaxLen(500).Optional().Nillable().Comment("模型映射链"),
|
||||||
|
field.String("billing_tier").MaxLen(50).Optional().Nillable().Comment("计费层级标签"),
|
||||||
|
field.String("billing_mode").MaxLen(20).Optional().Nillable().Comment("计费模式:token/per_request/image"),
|
||||||
field.Int64("group_id").
|
field.Int64("group_id").
|
||||||
Optional().
|
Optional().
|
||||||
Nillable(),
|
Nillable(),
|
||||||
@@ -175,6 +191,7 @@ func (UsageLog) Indexes() []ent.Index {
|
|||||||
index.Fields("subscription_id"),
|
index.Fields("subscription_id"),
|
||||||
index.Fields("created_at"),
|
index.Fields("created_at"),
|
||||||
index.Fields("model"),
|
index.Fields("model"),
|
||||||
|
index.Fields("requested_model"),
|
||||||
index.Fields("request_id"),
|
index.Fields("request_id"),
|
||||||
// 复合索引用于时间范围查询
|
// 复合索引用于时间范围查询
|
||||||
index.Fields("user_id", "created_at"),
|
index.Fields("user_id", "created_at"),
|
||||||
|
|||||||
275
backend/ent/tlsfingerprintprofile.go
Normal file
275
backend/ent/tlsfingerprintprofile.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
// 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/tlsfingerprintprofile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfile is the model entity for the TLSFingerprintProfile schema.
|
||||||
|
type TLSFingerprintProfile struct {
|
||||||
|
config `json:"-"`
|
||||||
|
// ID of the ent.
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
// UpdatedAt holds the value of the "updated_at" field.
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
// Name holds the value of the "name" field.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
// Description holds the value of the "description" field.
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
// EnableGrease holds the value of the "enable_grease" field.
|
||||||
|
EnableGrease bool `json:"enable_grease,omitempty"`
|
||||||
|
// CipherSuites holds the value of the "cipher_suites" field.
|
||||||
|
CipherSuites []uint16 `json:"cipher_suites,omitempty"`
|
||||||
|
// Curves holds the value of the "curves" field.
|
||||||
|
Curves []uint16 `json:"curves,omitempty"`
|
||||||
|
// PointFormats holds the value of the "point_formats" field.
|
||||||
|
PointFormats []uint16 `json:"point_formats,omitempty"`
|
||||||
|
// SignatureAlgorithms holds the value of the "signature_algorithms" field.
|
||||||
|
SignatureAlgorithms []uint16 `json:"signature_algorithms,omitempty"`
|
||||||
|
// AlpnProtocols holds the value of the "alpn_protocols" field.
|
||||||
|
AlpnProtocols []string `json:"alpn_protocols,omitempty"`
|
||||||
|
// SupportedVersions holds the value of the "supported_versions" field.
|
||||||
|
SupportedVersions []uint16 `json:"supported_versions,omitempty"`
|
||||||
|
// KeyShareGroups holds the value of the "key_share_groups" field.
|
||||||
|
KeyShareGroups []uint16 `json:"key_share_groups,omitempty"`
|
||||||
|
// PskModes holds the value of the "psk_modes" field.
|
||||||
|
PskModes []uint16 `json:"psk_modes,omitempty"`
|
||||||
|
// Extensions holds the value of the "extensions" field.
|
||||||
|
Extensions []uint16 `json:"extensions,omitempty"`
|
||||||
|
selectValues sql.SelectValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
|
func (*TLSFingerprintProfile) scanValues(columns []string) ([]any, error) {
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
for i := range columns {
|
||||||
|
switch columns[i] {
|
||||||
|
case tlsfingerprintprofile.FieldCipherSuites, tlsfingerprintprofile.FieldCurves, tlsfingerprintprofile.FieldPointFormats, tlsfingerprintprofile.FieldSignatureAlgorithms, tlsfingerprintprofile.FieldAlpnProtocols, tlsfingerprintprofile.FieldSupportedVersions, tlsfingerprintprofile.FieldKeyShareGroups, tlsfingerprintprofile.FieldPskModes, tlsfingerprintprofile.FieldExtensions:
|
||||||
|
values[i] = new([]byte)
|
||||||
|
case tlsfingerprintprofile.FieldEnableGrease:
|
||||||
|
values[i] = new(sql.NullBool)
|
||||||
|
case tlsfingerprintprofile.FieldID:
|
||||||
|
values[i] = new(sql.NullInt64)
|
||||||
|
case tlsfingerprintprofile.FieldName, tlsfingerprintprofile.FieldDescription:
|
||||||
|
values[i] = new(sql.NullString)
|
||||||
|
case tlsfingerprintprofile.FieldCreatedAt, tlsfingerprintprofile.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 TLSFingerprintProfile fields.
|
||||||
|
func (_m *TLSFingerprintProfile) 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 tlsfingerprintprofile.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 tlsfingerprintprofile.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 tlsfingerprintprofile.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 tlsfingerprintprofile.FieldName:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field name", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Name = value.String
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldDescription:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field description", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Description = new(string)
|
||||||
|
*_m.Description = value.String
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldEnableGrease:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field enable_grease", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.EnableGrease = value.Bool
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldCipherSuites:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field cipher_suites", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.CipherSuites); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field cipher_suites: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldCurves:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field curves", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.Curves); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field curves: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldPointFormats:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field point_formats", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.PointFormats); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field point_formats: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldSignatureAlgorithms:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field signature_algorithms", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.SignatureAlgorithms); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field signature_algorithms: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldAlpnProtocols:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field alpn_protocols", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.AlpnProtocols); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field alpn_protocols: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldSupportedVersions:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field supported_versions", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.SupportedVersions); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field supported_versions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldKeyShareGroups:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field key_share_groups", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.KeyShareGroups); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field key_share_groups: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldPskModes:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field psk_modes", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.PskModes); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field psk_modes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tlsfingerprintprofile.FieldExtensions:
|
||||||
|
if value, ok := values[i].(*[]byte); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field extensions", values[i])
|
||||||
|
} else if value != nil && len(*value) > 0 {
|
||||||
|
if err := json.Unmarshal(*value, &_m.Extensions); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal field extensions: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the TLSFingerprintProfile.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *TLSFingerprintProfile) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns a builder for updating this TLSFingerprintProfile.
|
||||||
|
// Note that you need to call TLSFingerprintProfile.Unwrap() before calling this method if this TLSFingerprintProfile
|
||||||
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
|
func (_m *TLSFingerprintProfile) Update() *TLSFingerprintProfileUpdateOne {
|
||||||
|
return NewTLSFingerprintProfileClient(_m.config).UpdateOne(_m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the TLSFingerprintProfile 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 *TLSFingerprintProfile) Unwrap() *TLSFingerprintProfile {
|
||||||
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
|
if !ok {
|
||||||
|
panic("ent: TLSFingerprintProfile is not a transactional entity")
|
||||||
|
}
|
||||||
|
_m.config.driver = _tx.drv
|
||||||
|
return _m
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer.
|
||||||
|
func (_m *TLSFingerprintProfile) String() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString("TLSFingerprintProfile(")
|
||||||
|
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||||
|
builder.WriteString("created_at=")
|
||||||
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("updated_at=")
|
||||||
|
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("name=")
|
||||||
|
builder.WriteString(_m.Name)
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.Description; v != nil {
|
||||||
|
builder.WriteString("description=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("enable_grease=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.EnableGrease))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("cipher_suites=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.CipherSuites))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("curves=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Curves))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("point_formats=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.PointFormats))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("signature_algorithms=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.SignatureAlgorithms))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("alpn_protocols=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.AlpnProtocols))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("supported_versions=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.SupportedVersions))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("key_share_groups=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.KeyShareGroups))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("psk_modes=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.PskModes))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("extensions=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Extensions))
|
||||||
|
builder.WriteByte(')')
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfiles is a parsable slice of TLSFingerprintProfile.
|
||||||
|
type TLSFingerprintProfiles []*TLSFingerprintProfile
|
||||||
121
backend/ent/tlsfingerprintprofile/tlsfingerprintprofile.go
Normal file
121
backend/ent/tlsfingerprintprofile/tlsfingerprintprofile.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package tlsfingerprintprofile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Label holds the string label denoting the tlsfingerprintprofile type in the database.
|
||||||
|
Label = "tls_fingerprint_profile"
|
||||||
|
// FieldID holds the string denoting the id field in the database.
|
||||||
|
FieldID = "id"
|
||||||
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
|
FieldCreatedAt = "created_at"
|
||||||
|
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||||
|
FieldUpdatedAt = "updated_at"
|
||||||
|
// FieldName holds the string denoting the name field in the database.
|
||||||
|
FieldName = "name"
|
||||||
|
// FieldDescription holds the string denoting the description field in the database.
|
||||||
|
FieldDescription = "description"
|
||||||
|
// FieldEnableGrease holds the string denoting the enable_grease field in the database.
|
||||||
|
FieldEnableGrease = "enable_grease"
|
||||||
|
// FieldCipherSuites holds the string denoting the cipher_suites field in the database.
|
||||||
|
FieldCipherSuites = "cipher_suites"
|
||||||
|
// FieldCurves holds the string denoting the curves field in the database.
|
||||||
|
FieldCurves = "curves"
|
||||||
|
// FieldPointFormats holds the string denoting the point_formats field in the database.
|
||||||
|
FieldPointFormats = "point_formats"
|
||||||
|
// FieldSignatureAlgorithms holds the string denoting the signature_algorithms field in the database.
|
||||||
|
FieldSignatureAlgorithms = "signature_algorithms"
|
||||||
|
// FieldAlpnProtocols holds the string denoting the alpn_protocols field in the database.
|
||||||
|
FieldAlpnProtocols = "alpn_protocols"
|
||||||
|
// FieldSupportedVersions holds the string denoting the supported_versions field in the database.
|
||||||
|
FieldSupportedVersions = "supported_versions"
|
||||||
|
// FieldKeyShareGroups holds the string denoting the key_share_groups field in the database.
|
||||||
|
FieldKeyShareGroups = "key_share_groups"
|
||||||
|
// FieldPskModes holds the string denoting the psk_modes field in the database.
|
||||||
|
FieldPskModes = "psk_modes"
|
||||||
|
// FieldExtensions holds the string denoting the extensions field in the database.
|
||||||
|
FieldExtensions = "extensions"
|
||||||
|
// Table holds the table name of the tlsfingerprintprofile in the database.
|
||||||
|
Table = "tls_fingerprint_profiles"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Columns holds all SQL columns for tlsfingerprintprofile fields.
|
||||||
|
var Columns = []string{
|
||||||
|
FieldID,
|
||||||
|
FieldCreatedAt,
|
||||||
|
FieldUpdatedAt,
|
||||||
|
FieldName,
|
||||||
|
FieldDescription,
|
||||||
|
FieldEnableGrease,
|
||||||
|
FieldCipherSuites,
|
||||||
|
FieldCurves,
|
||||||
|
FieldPointFormats,
|
||||||
|
FieldSignatureAlgorithms,
|
||||||
|
FieldAlpnProtocols,
|
||||||
|
FieldSupportedVersions,
|
||||||
|
FieldKeyShareGroups,
|
||||||
|
FieldPskModes,
|
||||||
|
FieldExtensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||||
|
func ValidColumn(column string) bool {
|
||||||
|
for i := range Columns {
|
||||||
|
if column == Columns[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
|
DefaultCreatedAt func() time.Time
|
||||||
|
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||||
|
DefaultUpdatedAt func() time.Time
|
||||||
|
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||||
|
UpdateDefaultUpdatedAt func() time.Time
|
||||||
|
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||||
|
NameValidator func(string) error
|
||||||
|
// DefaultEnableGrease holds the default value on creation for the "enable_grease" field.
|
||||||
|
DefaultEnableGrease bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the TLSFingerprintProfile queries.
|
||||||
|
type OrderOption func(*sql.Selector)
|
||||||
|
|
||||||
|
// ByID orders the results by the id field.
|
||||||
|
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUpdatedAt orders the results by the updated_at field.
|
||||||
|
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName orders the results by the name field.
|
||||||
|
func ByName(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldName, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByDescription orders the results by the description field.
|
||||||
|
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldDescription, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByEnableGrease orders the results by the enable_grease field.
|
||||||
|
func ByEnableGrease(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldEnableGrease, opts...).ToFunc()
|
||||||
|
}
|
||||||
415
backend/ent/tlsfingerprintprofile/where.go
Normal file
415
backend/ent/tlsfingerprintprofile/where.go
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
|
package tlsfingerprintprofile
|
||||||
|
|
||||||
|
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.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
|
func IDEQ(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
|
func IDNEQ(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDIn applies the In predicate on the ID field.
|
||||||
|
func IDIn(ids ...int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
|
func IDNotIn(ids ...int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldID, ids...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGT applies the GT predicate on the ID field.
|
||||||
|
func IDGT(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
|
func IDGTE(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLT applies the LT predicate on the ID field.
|
||||||
|
func IDLT(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLT(FieldID, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
|
func IDLTE(id int64) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(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.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(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.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
|
||||||
|
func Name(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
|
||||||
|
func Description(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableGrease applies equality check predicate on the "enable_grease" field. It's identical to EnableGreaseEQ.
|
||||||
|
func EnableGrease(v bool) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldEnableGrease, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtEQ(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
|
func CreatedAtNEQ(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
|
func CreatedAtIn(vs ...time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
|
func CreatedAtNotIn(vs ...time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
|
func CreatedAtGT(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtGTE(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
|
func CreatedAtLT(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLT(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
|
func CreatedAtLTE(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtEQ(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNEQ(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtIn(vs ...time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtNotIn(vs ...time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGT(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtGTE(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLT(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLT(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||||
|
func UpdatedAtLTE(v time.Time) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldUpdatedAt, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameEQ applies the EQ predicate on the "name" field.
|
||||||
|
func NameEQ(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameNEQ applies the NEQ predicate on the "name" field.
|
||||||
|
func NameNEQ(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameIn applies the In predicate on the "name" field.
|
||||||
|
func NameIn(vs ...string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIn(FieldName, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameNotIn applies the NotIn predicate on the "name" field.
|
||||||
|
func NameNotIn(vs ...string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldName, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameGT applies the GT predicate on the "name" field.
|
||||||
|
func NameGT(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGT(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameGTE applies the GTE predicate on the "name" field.
|
||||||
|
func NameGTE(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameLT applies the LT predicate on the "name" field.
|
||||||
|
func NameLT(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLT(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameLTE applies the LTE predicate on the "name" field.
|
||||||
|
func NameLTE(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameContains applies the Contains predicate on the "name" field.
|
||||||
|
func NameContains(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldContains(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
|
||||||
|
func NameHasPrefix(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldHasPrefix(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
|
||||||
|
func NameHasSuffix(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldHasSuffix(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameEqualFold applies the EqualFold predicate on the "name" field.
|
||||||
|
func NameEqualFold(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEqualFold(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameContainsFold applies the ContainsFold predicate on the "name" field.
|
||||||
|
func NameContainsFold(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldContainsFold(FieldName, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionEQ applies the EQ predicate on the "description" field.
|
||||||
|
func DescriptionEQ(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionNEQ applies the NEQ predicate on the "description" field.
|
||||||
|
func DescriptionNEQ(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionIn applies the In predicate on the "description" field.
|
||||||
|
func DescriptionIn(vs ...string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIn(FieldDescription, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionNotIn applies the NotIn predicate on the "description" field.
|
||||||
|
func DescriptionNotIn(vs ...string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotIn(FieldDescription, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionGT applies the GT predicate on the "description" field.
|
||||||
|
func DescriptionGT(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGT(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionGTE applies the GTE predicate on the "description" field.
|
||||||
|
func DescriptionGTE(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldGTE(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionLT applies the LT predicate on the "description" field.
|
||||||
|
func DescriptionLT(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLT(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionLTE applies the LTE predicate on the "description" field.
|
||||||
|
func DescriptionLTE(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldLTE(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionContains applies the Contains predicate on the "description" field.
|
||||||
|
func DescriptionContains(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldContains(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
|
||||||
|
func DescriptionHasPrefix(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldHasPrefix(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
|
||||||
|
func DescriptionHasSuffix(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldHasSuffix(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionIsNil applies the IsNil predicate on the "description" field.
|
||||||
|
func DescriptionIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldDescription))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionNotNil applies the NotNil predicate on the "description" field.
|
||||||
|
func DescriptionNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldDescription))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
|
||||||
|
func DescriptionEqualFold(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEqualFold(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
|
||||||
|
func DescriptionContainsFold(v string) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldContainsFold(FieldDescription, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableGreaseEQ applies the EQ predicate on the "enable_grease" field.
|
||||||
|
func EnableGreaseEQ(v bool) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldEQ(FieldEnableGrease, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableGreaseNEQ applies the NEQ predicate on the "enable_grease" field.
|
||||||
|
func EnableGreaseNEQ(v bool) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNEQ(FieldEnableGrease, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CipherSuitesIsNil applies the IsNil predicate on the "cipher_suites" field.
|
||||||
|
func CipherSuitesIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldCipherSuites))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CipherSuitesNotNil applies the NotNil predicate on the "cipher_suites" field.
|
||||||
|
func CipherSuitesNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldCipherSuites))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurvesIsNil applies the IsNil predicate on the "curves" field.
|
||||||
|
func CurvesIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldCurves))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurvesNotNil applies the NotNil predicate on the "curves" field.
|
||||||
|
func CurvesNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldCurves))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PointFormatsIsNil applies the IsNil predicate on the "point_formats" field.
|
||||||
|
func PointFormatsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldPointFormats))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PointFormatsNotNil applies the NotNil predicate on the "point_formats" field.
|
||||||
|
func PointFormatsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldPointFormats))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithmsIsNil applies the IsNil predicate on the "signature_algorithms" field.
|
||||||
|
func SignatureAlgorithmsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldSignatureAlgorithms))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureAlgorithmsNotNil applies the NotNil predicate on the "signature_algorithms" field.
|
||||||
|
func SignatureAlgorithmsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldSignatureAlgorithms))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlpnProtocolsIsNil applies the IsNil predicate on the "alpn_protocols" field.
|
||||||
|
func AlpnProtocolsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldAlpnProtocols))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlpnProtocolsNotNil applies the NotNil predicate on the "alpn_protocols" field.
|
||||||
|
func AlpnProtocolsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldAlpnProtocols))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedVersionsIsNil applies the IsNil predicate on the "supported_versions" field.
|
||||||
|
func SupportedVersionsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldSupportedVersions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedVersionsNotNil applies the NotNil predicate on the "supported_versions" field.
|
||||||
|
func SupportedVersionsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldSupportedVersions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyShareGroupsIsNil applies the IsNil predicate on the "key_share_groups" field.
|
||||||
|
func KeyShareGroupsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldKeyShareGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyShareGroupsNotNil applies the NotNil predicate on the "key_share_groups" field.
|
||||||
|
func KeyShareGroupsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldKeyShareGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PskModesIsNil applies the IsNil predicate on the "psk_modes" field.
|
||||||
|
func PskModesIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldPskModes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PskModesNotNil applies the NotNil predicate on the "psk_modes" field.
|
||||||
|
func PskModesNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldPskModes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionsIsNil applies the IsNil predicate on the "extensions" field.
|
||||||
|
func ExtensionsIsNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldIsNull(FieldExtensions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionsNotNil applies the NotNil predicate on the "extensions" field.
|
||||||
|
func ExtensionsNotNil() predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.FieldNotNull(FieldExtensions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// And groups predicates with the AND operator between them.
|
||||||
|
func And(predicates ...predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.AndPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or groups predicates with the OR operator between them.
|
||||||
|
func Or(predicates ...predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.OrPredicates(predicates...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not applies the not operator on the given predicate.
|
||||||
|
func Not(p predicate.TLSFingerprintProfile) predicate.TLSFingerprintProfile {
|
||||||
|
return predicate.TLSFingerprintProfile(sql.NotPredicates(p))
|
||||||
|
}
|
||||||
1341
backend/ent/tlsfingerprintprofile_create.go
Normal file
1341
backend/ent/tlsfingerprintprofile_create.go
Normal file
File diff suppressed because it is too large
Load Diff
88
backend/ent/tlsfingerprintprofile_delete.go
Normal file
88
backend/ent/tlsfingerprintprofile_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/tlsfingerprintprofile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfileDelete is the builder for deleting a TLSFingerprintProfile entity.
|
||||||
|
type TLSFingerprintProfileDelete struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *TLSFingerprintProfileMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the TLSFingerprintProfileDelete builder.
|
||||||
|
func (_d *TLSFingerprintProfileDelete) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileDelete {
|
||||||
|
_d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
|
func (_d *TLSFingerprintProfileDelete) 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 *TLSFingerprintProfileDelete) ExecX(ctx context.Context) int {
|
||||||
|
n, err := _d.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_d *TLSFingerprintProfileDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
|
_spec := sqlgraph.NewDeleteSpec(tlsfingerprintprofile.Table, sqlgraph.NewFieldSpec(tlsfingerprintprofile.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfileDeleteOne is the builder for deleting a single TLSFingerprintProfile entity.
|
||||||
|
type TLSFingerprintProfileDeleteOne struct {
|
||||||
|
_d *TLSFingerprintProfileDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the TLSFingerprintProfileDelete builder.
|
||||||
|
func (_d *TLSFingerprintProfileDeleteOne) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the deletion query.
|
||||||
|
func (_d *TLSFingerprintProfileDeleteOne) Exec(ctx context.Context) error {
|
||||||
|
n, err := _d._d.Exec(ctx)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case n == 0:
|
||||||
|
return &NotFoundError{tlsfingerprintprofile.Label}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_d *TLSFingerprintProfileDeleteOne) ExecX(ctx context.Context) {
|
||||||
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
564
backend/ent/tlsfingerprintprofile_query.go
Normal file
564
backend/ent/tlsfingerprintprofile_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/tlsfingerprintprofile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfileQuery is the builder for querying TLSFingerprintProfile entities.
|
||||||
|
type TLSFingerprintProfileQuery struct {
|
||||||
|
config
|
||||||
|
ctx *QueryContext
|
||||||
|
order []tlsfingerprintprofile.OrderOption
|
||||||
|
inters []Interceptor
|
||||||
|
predicates []predicate.TLSFingerprintProfile
|
||||||
|
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 TLSFingerprintProfileQuery builder.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileQuery {
|
||||||
|
_q.predicates = append(_q.predicates, ps...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the number of records to be returned by this query.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Limit(limit int) *TLSFingerprintProfileQuery {
|
||||||
|
_q.ctx.Limit = &limit
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset to start from.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Offset(offset int) *TLSFingerprintProfileQuery {
|
||||||
|
_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 *TLSFingerprintProfileQuery) Unique(unique bool) *TLSFingerprintProfileQuery {
|
||||||
|
_q.ctx.Unique = &unique
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order specifies how the records should be ordered.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Order(o ...tlsfingerprintprofile.OrderOption) *TLSFingerprintProfileQuery {
|
||||||
|
_q.order = append(_q.order, o...)
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first TLSFingerprintProfile entity from the query.
|
||||||
|
// Returns a *NotFoundError when no TLSFingerprintProfile was found.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) First(ctx context.Context) (*TLSFingerprintProfile, 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{tlsfingerprintprofile.Label}
|
||||||
|
}
|
||||||
|
return nodes[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstX is like First, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) FirstX(ctx context.Context) *TLSFingerprintProfile {
|
||||||
|
node, err := _q.First(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstID returns the first TLSFingerprintProfile ID from the query.
|
||||||
|
// Returns a *NotFoundError when no TLSFingerprintProfile ID was found.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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{tlsfingerprintprofile.Label}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ids[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) FirstIDX(ctx context.Context) int64 {
|
||||||
|
id, err := _q.FirstID(ctx)
|
||||||
|
if err != nil && !IsNotFound(err) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only returns a single TLSFingerprintProfile entity found by the query, ensuring it only returns one.
|
||||||
|
// Returns a *NotSingularError when more than one TLSFingerprintProfile entity is found.
|
||||||
|
// Returns a *NotFoundError when no TLSFingerprintProfile entities are found.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Only(ctx context.Context) (*TLSFingerprintProfile, 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{tlsfingerprintprofile.Label}
|
||||||
|
default:
|
||||||
|
return nil, &NotSingularError{tlsfingerprintprofile.Label}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyX is like Only, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) OnlyX(ctx context.Context) *TLSFingerprintProfile {
|
||||||
|
node, err := _q.Only(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyID is like Only, but returns the only TLSFingerprintProfile ID in the query.
|
||||||
|
// Returns a *NotSingularError when more than one TLSFingerprintProfile ID is found.
|
||||||
|
// Returns a *NotFoundError when no entities are found.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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{tlsfingerprintprofile.Label}
|
||||||
|
default:
|
||||||
|
err = &NotSingularError{tlsfingerprintprofile.Label}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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 TLSFingerprintProfiles.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) All(ctx context.Context) ([]*TLSFingerprintProfile, error) {
|
||||||
|
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||||
|
if err := _q.prepareQuery(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qr := querierAll[[]*TLSFingerprintProfile, *TLSFingerprintProfileQuery]()
|
||||||
|
return withInterceptors[[]*TLSFingerprintProfile](ctx, _q, qr, _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllX is like All, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) AllX(ctx context.Context) []*TLSFingerprintProfile {
|
||||||
|
nodes, err := _q.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs executes the query and returns a list of TLSFingerprintProfile IDs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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(tlsfingerprintprofile.FieldID).Scan(ctx, &ids); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDsX is like IDs, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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 *TLSFingerprintProfileQuery) 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[*TLSFingerprintProfileQuery](), _q.inters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountX is like Count, but panics if an error occurs.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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 *TLSFingerprintProfileQuery) 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 *TLSFingerprintProfileQuery) ExistX(ctx context.Context) bool {
|
||||||
|
exist, err := _q.Exist(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the TLSFingerprintProfileQuery 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 *TLSFingerprintProfileQuery) Clone() *TLSFingerprintProfileQuery {
|
||||||
|
if _q == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &TLSFingerprintProfileQuery{
|
||||||
|
config: _q.config,
|
||||||
|
ctx: _q.ctx.Clone(),
|
||||||
|
order: append([]tlsfingerprintprofile.OrderOption{}, _q.order...),
|
||||||
|
inters: append([]Interceptor{}, _q.inters...),
|
||||||
|
predicates: append([]predicate.TLSFingerprintProfile{}, _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.TLSFingerprintProfile.Query().
|
||||||
|
// GroupBy(tlsfingerprintprofile.FieldCreatedAt).
|
||||||
|
// Aggregate(ent.Count()).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *TLSFingerprintProfileQuery) GroupBy(field string, fields ...string) *TLSFingerprintProfileGroupBy {
|
||||||
|
_q.ctx.Fields = append([]string{field}, fields...)
|
||||||
|
grbuild := &TLSFingerprintProfileGroupBy{build: _q}
|
||||||
|
grbuild.flds = &_q.ctx.Fields
|
||||||
|
grbuild.label = tlsfingerprintprofile.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.TLSFingerprintProfile.Query().
|
||||||
|
// Select(tlsfingerprintprofile.FieldCreatedAt).
|
||||||
|
// Scan(ctx, &v)
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Select(fields ...string) *TLSFingerprintProfileSelect {
|
||||||
|
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||||
|
sbuild := &TLSFingerprintProfileSelect{TLSFingerprintProfileQuery: _q}
|
||||||
|
sbuild.label = tlsfingerprintprofile.Label
|
||||||
|
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||||
|
return sbuild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate returns a TLSFingerprintProfileSelect configured with the given aggregations.
|
||||||
|
func (_q *TLSFingerprintProfileQuery) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileSelect {
|
||||||
|
return _q.Select().Aggregate(fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_q *TLSFingerprintProfileQuery) 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 !tlsfingerprintprofile.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 *TLSFingerprintProfileQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*TLSFingerprintProfile, error) {
|
||||||
|
var (
|
||||||
|
nodes = []*TLSFingerprintProfile{}
|
||||||
|
_spec = _q.querySpec()
|
||||||
|
)
|
||||||
|
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||||
|
return (*TLSFingerprintProfile).scanValues(nil, columns)
|
||||||
|
}
|
||||||
|
_spec.Assign = func(columns []string, values []any) error {
|
||||||
|
node := &TLSFingerprintProfile{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 *TLSFingerprintProfileQuery) 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 *TLSFingerprintProfileQuery) querySpec() *sqlgraph.QuerySpec {
|
||||||
|
_spec := sqlgraph.NewQuerySpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.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, tlsfingerprintprofile.FieldID)
|
||||||
|
for i := range fields {
|
||||||
|
if fields[i] != tlsfingerprintprofile.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 *TLSFingerprintProfileQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||||
|
builder := sql.Dialect(_q.driver.Dialect())
|
||||||
|
t1 := builder.Table(tlsfingerprintprofile.Table)
|
||||||
|
columns := _q.ctx.Fields
|
||||||
|
if len(columns) == 0 {
|
||||||
|
columns = tlsfingerprintprofile.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 *TLSFingerprintProfileQuery) ForUpdate(opts ...sql.LockOption) *TLSFingerprintProfileQuery {
|
||||||
|
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 *TLSFingerprintProfileQuery) ForShare(opts ...sql.LockOption) *TLSFingerprintProfileQuery {
|
||||||
|
if _q.driver.Dialect() == dialect.Postgres {
|
||||||
|
_q.Unique(false)
|
||||||
|
}
|
||||||
|
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||||
|
s.ForShare(opts...)
|
||||||
|
})
|
||||||
|
return _q
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfileGroupBy is the group-by builder for TLSFingerprintProfile entities.
|
||||||
|
type TLSFingerprintProfileGroupBy struct {
|
||||||
|
selector
|
||||||
|
build *TLSFingerprintProfileQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the group-by query.
|
||||||
|
func (_g *TLSFingerprintProfileGroupBy) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileGroupBy {
|
||||||
|
_g.fns = append(_g.fns, fns...)
|
||||||
|
return _g
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_g *TLSFingerprintProfileGroupBy) 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[*TLSFingerprintProfileQuery, *TLSFingerprintProfileGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_g *TLSFingerprintProfileGroupBy) sqlScan(ctx context.Context, root *TLSFingerprintProfileQuery, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfileSelect is the builder for selecting fields of TLSFingerprintProfile entities.
|
||||||
|
type TLSFingerprintProfileSelect struct {
|
||||||
|
*TLSFingerprintProfileQuery
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate adds the given aggregation functions to the selector query.
|
||||||
|
func (_s *TLSFingerprintProfileSelect) Aggregate(fns ...AggregateFunc) *TLSFingerprintProfileSelect {
|
||||||
|
_s.fns = append(_s.fns, fns...)
|
||||||
|
return _s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan applies the selector query and scans the result into the given value.
|
||||||
|
func (_s *TLSFingerprintProfileSelect) 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[*TLSFingerprintProfileQuery, *TLSFingerprintProfileSelect](ctx, _s.TLSFingerprintProfileQuery, _s, _s.inters, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_s *TLSFingerprintProfileSelect) sqlScan(ctx context.Context, root *TLSFingerprintProfileQuery, 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)
|
||||||
|
}
|
||||||
881
backend/ent/tlsfingerprintprofile_update.go
Normal file
881
backend/ent/tlsfingerprintprofile_update.go
Normal file
@@ -0,0 +1,881 @@
|
|||||||
|
// 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/dialect/sql/sqljson"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfileUpdate is the builder for updating TLSFingerprintProfile entities.
|
||||||
|
type TLSFingerprintProfileUpdate struct {
|
||||||
|
config
|
||||||
|
hooks []Hook
|
||||||
|
mutation *TLSFingerprintProfileMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the TLSFingerprintProfileUpdate builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName sets the "name" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetName(v string) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetName(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableName sets the "name" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetNillableName(v *string) *TLSFingerprintProfileUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetName(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDescription sets the "description" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetDescription(v string) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetDescription(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDescription sets the "description" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetNillableDescription(v *string) *TLSFingerprintProfileUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDescription(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDescription clears the value of the "description" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearDescription() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearDescription()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnableGrease sets the "enable_grease" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetEnableGrease(v bool) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetEnableGrease(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEnableGrease sets the "enable_grease" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetNillableEnableGrease(v *bool) *TLSFingerprintProfileUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEnableGrease(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCipherSuites sets the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetCipherSuites(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendCipherSuites appends value to the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendCipherSuites(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendCipherSuites(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCipherSuites clears the value of the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearCipherSuites() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearCipherSuites()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCurves sets the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetCurves(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetCurves(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendCurves appends value to the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendCurves(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendCurves(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCurves clears the value of the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearCurves() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearCurves()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPointFormats sets the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetPointFormats(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetPointFormats(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPointFormats appends value to the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendPointFormats(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendPointFormats(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPointFormats clears the value of the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearPointFormats() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearPointFormats()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSignatureAlgorithms sets the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetSignatureAlgorithms(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSignatureAlgorithms appends value to the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendSignatureAlgorithms(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearSignatureAlgorithms() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearSignatureAlgorithms()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAlpnProtocols sets the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetAlpnProtocols(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendAlpnProtocols appends value to the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendAlpnProtocols(v []string) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendAlpnProtocols(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAlpnProtocols clears the value of the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearAlpnProtocols() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearAlpnProtocols()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSupportedVersions sets the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetSupportedVersions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSupportedVersions appends value to the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendSupportedVersions(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendSupportedVersions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSupportedVersions clears the value of the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearSupportedVersions() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearSupportedVersions()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKeyShareGroups sets the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetKeyShareGroups(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendKeyShareGroups appends value to the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendKeyShareGroups(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearKeyShareGroups clears the value of the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearKeyShareGroups() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearKeyShareGroups()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPskModes sets the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetPskModes(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetPskModes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPskModes appends value to the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendPskModes(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendPskModes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPskModes clears the value of the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearPskModes() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearPskModes()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtensions sets the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) SetExtensions(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.SetExtensions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendExtensions appends value to the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) AppendExtensions(v []uint16) *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.AppendExtensions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtensions clears the value of the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) ClearExtensions() *TLSFingerprintProfileUpdate {
|
||||||
|
_u.mutation.ClearExtensions()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the TLSFingerprintProfileMutation object of the builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) Mutation() *TLSFingerprintProfileMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) 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 *TLSFingerprintProfileUpdate) SaveX(ctx context.Context) int {
|
||||||
|
affected, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return affected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) 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 *TLSFingerprintProfileUpdate) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := tlsfingerprintprofile.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) check() error {
|
||||||
|
if v, ok := _u.mutation.Name(); ok {
|
||||||
|
if err := tlsfingerprintprofile.NameValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "TLSFingerprintProfile.name": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *TLSFingerprintProfileUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.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(tlsfingerprintprofile.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Name(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldName, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Description(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldDescription, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.DescriptionCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldDescription, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.EnableGrease(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldEnableGrease, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CipherSuites(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedCipherSuites(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldCipherSuites, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.CipherSuitesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Curves(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldCurves, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedCurves(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldCurves, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.CurvesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldCurves, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.PointFormats(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedPointFormats(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldPointFormats, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.PointFormatsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.SignatureAlgorithms(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSignatureAlgorithms(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldSignatureAlgorithms, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.SignatureAlgorithmsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AlpnProtocols(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedAlpnProtocols(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldAlpnProtocols, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.AlpnProtocolsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.SupportedVersions(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSupportedVersions(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldSupportedVersions, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.SupportedVersionsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.KeyShareGroups(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedKeyShareGroups(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldKeyShareGroups, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.KeyShareGroupsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.PskModes(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedPskModes(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldPskModes, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.PskModesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Extensions(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedExtensions(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldExtensions, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.ExtensionsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
|
err = &NotFoundError{tlsfingerprintprofile.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSFingerprintProfileUpdateOne is the builder for updating a single TLSFingerprintProfile entity.
|
||||||
|
type TLSFingerprintProfileUpdateOne struct {
|
||||||
|
config
|
||||||
|
fields []string
|
||||||
|
hooks []Hook
|
||||||
|
mutation *TLSFingerprintProfileMutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdatedAt sets the "updated_at" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetUpdatedAt(v time.Time) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetName sets the "name" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetName(v string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetName(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableName sets the "name" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetNillableName(v *string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetName(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDescription sets the "description" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetDescription(v string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetDescription(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableDescription sets the "description" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetNillableDescription(v *string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetDescription(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDescription clears the value of the "description" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearDescription() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearDescription()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnableGrease sets the "enable_grease" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetEnableGrease(v bool) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetEnableGrease(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEnableGrease sets the "enable_grease" field if the given value is not nil.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetNillableEnableGrease(v *bool) *TLSFingerprintProfileUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEnableGrease(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCipherSuites sets the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetCipherSuites(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetCipherSuites(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendCipherSuites appends value to the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendCipherSuites(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendCipherSuites(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCipherSuites clears the value of the "cipher_suites" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearCipherSuites() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearCipherSuites()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCurves sets the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetCurves(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetCurves(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendCurves appends value to the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendCurves(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendCurves(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCurves clears the value of the "curves" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearCurves() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearCurves()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPointFormats sets the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetPointFormats(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetPointFormats(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPointFormats appends value to the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendPointFormats(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendPointFormats(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPointFormats clears the value of the "point_formats" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearPointFormats() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearPointFormats()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSignatureAlgorithms sets the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetSignatureAlgorithms(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSignatureAlgorithms appends value to the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendSignatureAlgorithms(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendSignatureAlgorithms(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSignatureAlgorithms clears the value of the "signature_algorithms" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearSignatureAlgorithms() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearSignatureAlgorithms()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAlpnProtocols sets the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetAlpnProtocols(v []string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetAlpnProtocols(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendAlpnProtocols appends value to the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendAlpnProtocols(v []string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendAlpnProtocols(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAlpnProtocols clears the value of the "alpn_protocols" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearAlpnProtocols() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearAlpnProtocols()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSupportedVersions sets the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetSupportedVersions(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetSupportedVersions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendSupportedVersions appends value to the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendSupportedVersions(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendSupportedVersions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSupportedVersions clears the value of the "supported_versions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearSupportedVersions() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearSupportedVersions()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKeyShareGroups sets the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetKeyShareGroups(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendKeyShareGroups appends value to the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendKeyShareGroups(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendKeyShareGroups(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearKeyShareGroups clears the value of the "key_share_groups" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearKeyShareGroups() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearKeyShareGroups()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPskModes sets the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetPskModes(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetPskModes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendPskModes appends value to the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendPskModes(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendPskModes(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearPskModes clears the value of the "psk_modes" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearPskModes() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearPskModes()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtensions sets the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SetExtensions(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.SetExtensions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendExtensions appends value to the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) AppendExtensions(v []uint16) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.AppendExtensions(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtensions clears the value of the "extensions" field.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) ClearExtensions() *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.mutation.ClearExtensions()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns the TLSFingerprintProfileMutation object of the builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) Mutation() *TLSFingerprintProfileMutation {
|
||||||
|
return _u.mutation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the TLSFingerprintProfileUpdate builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) Where(ps ...predicate.TLSFingerprintProfile) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_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 *TLSFingerprintProfileUpdateOne) Select(field string, fields ...string) *TLSFingerprintProfileUpdateOne {
|
||||||
|
_u.fields = append([]string{field}, fields...)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save executes the query and returns the updated TLSFingerprintProfile entity.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) Save(ctx context.Context) (*TLSFingerprintProfile, error) {
|
||||||
|
_u.defaults()
|
||||||
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) SaveX(ctx context.Context) *TLSFingerprintProfile {
|
||||||
|
node, err := _u.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes the query on the entity.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) Exec(ctx context.Context) error {
|
||||||
|
_, err := _u.Save(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) 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 *TLSFingerprintProfileUpdateOne) defaults() {
|
||||||
|
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||||
|
v := tlsfingerprintprofile.UpdateDefaultUpdatedAt()
|
||||||
|
_u.mutation.SetUpdatedAt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runs all checks and user-defined validators on the builder.
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) check() error {
|
||||||
|
if v, ok := _u.mutation.Name(); ok {
|
||||||
|
if err := tlsfingerprintprofile.NameValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "TLSFingerprintProfile.name": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_u *TLSFingerprintProfileUpdateOne) sqlSave(ctx context.Context) (_node *TLSFingerprintProfile, err error) {
|
||||||
|
if err := _u.check(); err != nil {
|
||||||
|
return _node, err
|
||||||
|
}
|
||||||
|
_spec := sqlgraph.NewUpdateSpec(tlsfingerprintprofile.Table, tlsfingerprintprofile.Columns, sqlgraph.NewFieldSpec(tlsfingerprintprofile.FieldID, field.TypeInt64))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
|
if !ok {
|
||||||
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "TLSFingerprintProfile.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, tlsfingerprintprofile.FieldID)
|
||||||
|
for _, f := range fields {
|
||||||
|
if !tlsfingerprintprofile.ValidColumn(f) {
|
||||||
|
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||||
|
}
|
||||||
|
if f != tlsfingerprintprofile.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(tlsfingerprintprofile.FieldUpdatedAt, field.TypeTime, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Name(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldName, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Description(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldDescription, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.DescriptionCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldDescription, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.EnableGrease(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldEnableGrease, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.CipherSuites(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedCipherSuites(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldCipherSuites, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.CipherSuitesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldCipherSuites, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Curves(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldCurves, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedCurves(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldCurves, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.CurvesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldCurves, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.PointFormats(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedPointFormats(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldPointFormats, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.PointFormatsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldPointFormats, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.SignatureAlgorithms(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSignatureAlgorithms(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldSignatureAlgorithms, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.SignatureAlgorithmsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldSignatureAlgorithms, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AlpnProtocols(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedAlpnProtocols(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldAlpnProtocols, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.AlpnProtocolsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldAlpnProtocols, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.SupportedVersions(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedSupportedVersions(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldSupportedVersions, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.SupportedVersionsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldSupportedVersions, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.KeyShareGroups(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedKeyShareGroups(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldKeyShareGroups, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.KeyShareGroupsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldKeyShareGroups, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.PskModes(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedPskModes(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldPskModes, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.PskModesCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldPskModes, field.TypeJSON)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Extensions(); ok {
|
||||||
|
_spec.SetField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AppendedExtensions(); ok {
|
||||||
|
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||||
|
sqljson.Append(u, tlsfingerprintprofile.FieldExtensions, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _u.mutation.ExtensionsCleared() {
|
||||||
|
_spec.ClearField(tlsfingerprintprofile.FieldExtensions, field.TypeJSON)
|
||||||
|
}
|
||||||
|
_node = &TLSFingerprintProfile{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{tlsfingerprintprofile.Label}
|
||||||
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ type Tx struct {
|
|||||||
SecuritySecret *SecuritySecretClient
|
SecuritySecret *SecuritySecretClient
|
||||||
// Setting is the client for interacting with the Setting builders.
|
// Setting is the client for interacting with the Setting builders.
|
||||||
Setting *SettingClient
|
Setting *SettingClient
|
||||||
|
// TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders.
|
||||||
|
TLSFingerprintProfile *TLSFingerprintProfileClient
|
||||||
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||||
UsageCleanupTask *UsageCleanupTaskClient
|
UsageCleanupTask *UsageCleanupTaskClient
|
||||||
// UsageLog is the client for interacting with the UsageLog builders.
|
// UsageLog is the client for interacting with the UsageLog builders.
|
||||||
@@ -201,6 +203,7 @@ func (tx *Tx) init() {
|
|||||||
tx.RedeemCode = NewRedeemCodeClient(tx.config)
|
tx.RedeemCode = NewRedeemCodeClient(tx.config)
|
||||||
tx.SecuritySecret = NewSecuritySecretClient(tx.config)
|
tx.SecuritySecret = NewSecuritySecretClient(tx.config)
|
||||||
tx.Setting = NewSettingClient(tx.config)
|
tx.Setting = NewSettingClient(tx.config)
|
||||||
|
tx.TLSFingerprintProfile = NewTLSFingerprintProfileClient(tx.config)
|
||||||
tx.UsageCleanupTask = NewUsageCleanupTaskClient(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)
|
||||||
|
|||||||
@@ -32,6 +32,18 @@ type UsageLog struct {
|
|||||||
RequestID string `json:"request_id,omitempty"`
|
RequestID string `json:"request_id,omitempty"`
|
||||||
// Model holds the value of the "model" field.
|
// Model holds the value of the "model" field.
|
||||||
Model string `json:"model,omitempty"`
|
Model string `json:"model,omitempty"`
|
||||||
|
// RequestedModel holds the value of the "requested_model" field.
|
||||||
|
RequestedModel *string `json:"requested_model,omitempty"`
|
||||||
|
// UpstreamModel holds the value of the "upstream_model" field.
|
||||||
|
UpstreamModel *string `json:"upstream_model,omitempty"`
|
||||||
|
// 渠道 ID
|
||||||
|
ChannelID *int64 `json:"channel_id,omitempty"`
|
||||||
|
// 模型映射链
|
||||||
|
ModelMappingChain *string `json:"model_mapping_chain,omitempty"`
|
||||||
|
// 计费层级标签
|
||||||
|
BillingTier *string `json:"billing_tier,omitempty"`
|
||||||
|
// 计费模式:token/per_request/image
|
||||||
|
BillingMode *string `json:"billing_mode,omitempty"`
|
||||||
// GroupID holds the value of the "group_id" field.
|
// GroupID holds the value of the "group_id" field.
|
||||||
GroupID *int64 `json:"group_id,omitempty"`
|
GroupID *int64 `json:"group_id,omitempty"`
|
||||||
// SubscriptionID holds the value of the "subscription_id" field.
|
// SubscriptionID holds the value of the "subscription_id" field.
|
||||||
@@ -173,9 +185,9 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
|
|||||||
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, usagelog.FieldAccountRateMultiplier:
|
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.FieldChannelID, 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)
|
||||||
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldMediaType:
|
case usagelog.FieldRequestID, usagelog.FieldModel, usagelog.FieldRequestedModel, usagelog.FieldUpstreamModel, usagelog.FieldModelMappingChain, usagelog.FieldBillingTier, usagelog.FieldBillingMode, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldImageSize, usagelog.FieldMediaType:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
case usagelog.FieldCreatedAt:
|
case usagelog.FieldCreatedAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
@@ -230,6 +242,48 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
|
|||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
_m.Model = value.String
|
_m.Model = value.String
|
||||||
}
|
}
|
||||||
|
case usagelog.FieldRequestedModel:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field requested_model", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.RequestedModel = new(string)
|
||||||
|
*_m.RequestedModel = value.String
|
||||||
|
}
|
||||||
|
case usagelog.FieldUpstreamModel:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field upstream_model", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.UpstreamModel = new(string)
|
||||||
|
*_m.UpstreamModel = value.String
|
||||||
|
}
|
||||||
|
case usagelog.FieldChannelID:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field channel_id", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.ChannelID = new(int64)
|
||||||
|
*_m.ChannelID = value.Int64
|
||||||
|
}
|
||||||
|
case usagelog.FieldModelMappingChain:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field model_mapping_chain", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.ModelMappingChain = new(string)
|
||||||
|
*_m.ModelMappingChain = value.String
|
||||||
|
}
|
||||||
|
case usagelog.FieldBillingTier:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field billing_tier", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.BillingTier = new(string)
|
||||||
|
*_m.BillingTier = value.String
|
||||||
|
}
|
||||||
|
case usagelog.FieldBillingMode:
|
||||||
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field billing_mode", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.BillingMode = new(string)
|
||||||
|
*_m.BillingMode = value.String
|
||||||
|
}
|
||||||
case usagelog.FieldGroupID:
|
case usagelog.FieldGroupID:
|
||||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field group_id", values[i])
|
return fmt.Errorf("unexpected type %T for field group_id", values[i])
|
||||||
@@ -477,6 +531,36 @@ func (_m *UsageLog) String() string {
|
|||||||
builder.WriteString("model=")
|
builder.WriteString("model=")
|
||||||
builder.WriteString(_m.Model)
|
builder.WriteString(_m.Model)
|
||||||
builder.WriteString(", ")
|
builder.WriteString(", ")
|
||||||
|
if v := _m.RequestedModel; v != nil {
|
||||||
|
builder.WriteString("requested_model=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.UpstreamModel; v != nil {
|
||||||
|
builder.WriteString("upstream_model=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.ChannelID; v != nil {
|
||||||
|
builder.WriteString("channel_id=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.ModelMappingChain; v != nil {
|
||||||
|
builder.WriteString("model_mapping_chain=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.BillingTier; v != nil {
|
||||||
|
builder.WriteString("billing_tier=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
|
if v := _m.BillingMode; v != nil {
|
||||||
|
builder.WriteString("billing_mode=")
|
||||||
|
builder.WriteString(*v)
|
||||||
|
}
|
||||||
|
builder.WriteString(", ")
|
||||||
if v := _m.GroupID; v != nil {
|
if v := _m.GroupID; v != nil {
|
||||||
builder.WriteString("group_id=")
|
builder.WriteString("group_id=")
|
||||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ const (
|
|||||||
FieldRequestID = "request_id"
|
FieldRequestID = "request_id"
|
||||||
// FieldModel holds the string denoting the model field in the database.
|
// FieldModel holds the string denoting the model field in the database.
|
||||||
FieldModel = "model"
|
FieldModel = "model"
|
||||||
|
// FieldRequestedModel holds the string denoting the requested_model field in the database.
|
||||||
|
FieldRequestedModel = "requested_model"
|
||||||
|
// FieldUpstreamModel holds the string denoting the upstream_model field in the database.
|
||||||
|
FieldUpstreamModel = "upstream_model"
|
||||||
|
// FieldChannelID holds the string denoting the channel_id field in the database.
|
||||||
|
FieldChannelID = "channel_id"
|
||||||
|
// FieldModelMappingChain holds the string denoting the model_mapping_chain field in the database.
|
||||||
|
FieldModelMappingChain = "model_mapping_chain"
|
||||||
|
// FieldBillingTier holds the string denoting the billing_tier field in the database.
|
||||||
|
FieldBillingTier = "billing_tier"
|
||||||
|
// FieldBillingMode holds the string denoting the billing_mode field in the database.
|
||||||
|
FieldBillingMode = "billing_mode"
|
||||||
// FieldGroupID holds the string denoting the group_id field in the database.
|
// FieldGroupID holds the string denoting the group_id field in the database.
|
||||||
FieldGroupID = "group_id"
|
FieldGroupID = "group_id"
|
||||||
// FieldSubscriptionID holds the string denoting the subscription_id field in the database.
|
// FieldSubscriptionID holds the string denoting the subscription_id field in the database.
|
||||||
@@ -135,6 +147,12 @@ var Columns = []string{
|
|||||||
FieldAccountID,
|
FieldAccountID,
|
||||||
FieldRequestID,
|
FieldRequestID,
|
||||||
FieldModel,
|
FieldModel,
|
||||||
|
FieldRequestedModel,
|
||||||
|
FieldUpstreamModel,
|
||||||
|
FieldChannelID,
|
||||||
|
FieldModelMappingChain,
|
||||||
|
FieldBillingTier,
|
||||||
|
FieldBillingMode,
|
||||||
FieldGroupID,
|
FieldGroupID,
|
||||||
FieldSubscriptionID,
|
FieldSubscriptionID,
|
||||||
FieldInputTokens,
|
FieldInputTokens,
|
||||||
@@ -179,6 +197,16 @@ var (
|
|||||||
RequestIDValidator func(string) error
|
RequestIDValidator func(string) error
|
||||||
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
|
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
|
||||||
ModelValidator func(string) error
|
ModelValidator func(string) error
|
||||||
|
// RequestedModelValidator is a validator for the "requested_model" field. It is called by the builders before save.
|
||||||
|
RequestedModelValidator func(string) error
|
||||||
|
// UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
|
||||||
|
UpstreamModelValidator func(string) error
|
||||||
|
// ModelMappingChainValidator is a validator for the "model_mapping_chain" field. It is called by the builders before save.
|
||||||
|
ModelMappingChainValidator func(string) error
|
||||||
|
// BillingTierValidator is a validator for the "billing_tier" field. It is called by the builders before save.
|
||||||
|
BillingTierValidator func(string) error
|
||||||
|
// BillingModeValidator is a validator for the "billing_mode" field. It is called by the builders before save.
|
||||||
|
BillingModeValidator func(string) error
|
||||||
// DefaultInputTokens holds the default value on creation for the "input_tokens" field.
|
// DefaultInputTokens holds the default value on creation for the "input_tokens" field.
|
||||||
DefaultInputTokens int
|
DefaultInputTokens int
|
||||||
// DefaultOutputTokens holds the default value on creation for the "output_tokens" field.
|
// DefaultOutputTokens holds the default value on creation for the "output_tokens" field.
|
||||||
@@ -258,6 +286,36 @@ func ByModel(opts ...sql.OrderTermOption) OrderOption {
|
|||||||
return sql.OrderByField(FieldModel, opts...).ToFunc()
|
return sql.OrderByField(FieldModel, opts...).ToFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByRequestedModel orders the results by the requested_model field.
|
||||||
|
func ByRequestedModel(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldRequestedModel, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUpstreamModel orders the results by the upstream_model field.
|
||||||
|
func ByUpstreamModel(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUpstreamModel, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByChannelID orders the results by the channel_id field.
|
||||||
|
func ByChannelID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldChannelID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByModelMappingChain orders the results by the model_mapping_chain field.
|
||||||
|
func ByModelMappingChain(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldModelMappingChain, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByBillingTier orders the results by the billing_tier field.
|
||||||
|
func ByBillingTier(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldBillingTier, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByBillingMode orders the results by the billing_mode field.
|
||||||
|
func ByBillingMode(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldBillingMode, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
// ByGroupID orders the results by the group_id field.
|
// ByGroupID orders the results by the group_id field.
|
||||||
func ByGroupID(opts ...sql.OrderTermOption) OrderOption {
|
func ByGroupID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
return sql.OrderByField(FieldGroupID, opts...).ToFunc()
|
return sql.OrderByField(FieldGroupID, opts...).ToFunc()
|
||||||
|
|||||||
@@ -80,6 +80,36 @@ func Model(v string) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldEQ(FieldModel, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldModel, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestedModel applies equality check predicate on the "requested_model" field. It's identical to RequestedModelEQ.
|
||||||
|
func RequestedModel(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModel applies equality check predicate on the "upstream_model" field. It's identical to UpstreamModelEQ.
|
||||||
|
func UpstreamModel(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelID applies equality check predicate on the "channel_id" field. It's identical to ChannelIDEQ.
|
||||||
|
func ChannelID(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChain applies equality check predicate on the "model_mapping_chain" field. It's identical to ModelMappingChainEQ.
|
||||||
|
func ModelMappingChain(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTier applies equality check predicate on the "billing_tier" field. It's identical to BillingTierEQ.
|
||||||
|
func BillingTier(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingMode applies equality check predicate on the "billing_mode" field. It's identical to BillingModeEQ.
|
||||||
|
func BillingMode(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
// GroupID applies equality check predicate on the "group_id" field. It's identical to GroupIDEQ.
|
// GroupID applies equality check predicate on the "group_id" field. It's identical to GroupIDEQ.
|
||||||
func GroupID(v int64) predicate.UsageLog {
|
func GroupID(v int64) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldGroupID, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldGroupID, v))
|
||||||
@@ -405,6 +435,431 @@ func ModelContainsFold(v string) predicate.UsageLog {
|
|||||||
return predicate.UsageLog(sql.FieldContainsFold(FieldModel, v))
|
return predicate.UsageLog(sql.FieldContainsFold(FieldModel, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestedModelEQ applies the EQ predicate on the "requested_model" field.
|
||||||
|
func RequestedModelEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelNEQ applies the NEQ predicate on the "requested_model" field.
|
||||||
|
func RequestedModelNEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelIn applies the In predicate on the "requested_model" field.
|
||||||
|
func RequestedModelIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldRequestedModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelNotIn applies the NotIn predicate on the "requested_model" field.
|
||||||
|
func RequestedModelNotIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldRequestedModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelGT applies the GT predicate on the "requested_model" field.
|
||||||
|
func RequestedModelGT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelGTE applies the GTE predicate on the "requested_model" field.
|
||||||
|
func RequestedModelGTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelLT applies the LT predicate on the "requested_model" field.
|
||||||
|
func RequestedModelLT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelLTE applies the LTE predicate on the "requested_model" field.
|
||||||
|
func RequestedModelLTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelContains applies the Contains predicate on the "requested_model" field.
|
||||||
|
func RequestedModelContains(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContains(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelHasPrefix applies the HasPrefix predicate on the "requested_model" field.
|
||||||
|
func RequestedModelHasPrefix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasPrefix(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelHasSuffix applies the HasSuffix predicate on the "requested_model" field.
|
||||||
|
func RequestedModelHasSuffix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasSuffix(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelIsNil applies the IsNil predicate on the "requested_model" field.
|
||||||
|
func RequestedModelIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldRequestedModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelNotNil applies the NotNil predicate on the "requested_model" field.
|
||||||
|
func RequestedModelNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldRequestedModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelEqualFold applies the EqualFold predicate on the "requested_model" field.
|
||||||
|
func RequestedModelEqualFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEqualFold(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestedModelContainsFold applies the ContainsFold predicate on the "requested_model" field.
|
||||||
|
func RequestedModelContainsFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContainsFold(FieldRequestedModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelEQ applies the EQ predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelNEQ applies the NEQ predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelNEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelIn applies the In predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldUpstreamModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelNotIn applies the NotIn predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelNotIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldUpstreamModel, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelGT applies the GT predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelGT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelGTE applies the GTE predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelGTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelLT applies the LT predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelLT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelLTE applies the LTE predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelLTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelContains applies the Contains predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelContains(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContains(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelHasPrefix applies the HasPrefix predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelHasPrefix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasPrefix(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelHasSuffix applies the HasSuffix predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelHasSuffix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasSuffix(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelIsNil applies the IsNil predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldUpstreamModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelNotNil applies the NotNil predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldUpstreamModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelEqualFold applies the EqualFold predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelEqualFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEqualFold(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamModelContainsFold applies the ContainsFold predicate on the "upstream_model" field.
|
||||||
|
func UpstreamModelContainsFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContainsFold(FieldUpstreamModel, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDEQ applies the EQ predicate on the "channel_id" field.
|
||||||
|
func ChannelIDEQ(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDNEQ applies the NEQ predicate on the "channel_id" field.
|
||||||
|
func ChannelIDNEQ(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDIn applies the In predicate on the "channel_id" field.
|
||||||
|
func ChannelIDIn(vs ...int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldChannelID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDNotIn applies the NotIn predicate on the "channel_id" field.
|
||||||
|
func ChannelIDNotIn(vs ...int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldChannelID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDGT applies the GT predicate on the "channel_id" field.
|
||||||
|
func ChannelIDGT(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDGTE applies the GTE predicate on the "channel_id" field.
|
||||||
|
func ChannelIDGTE(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDLT applies the LT predicate on the "channel_id" field.
|
||||||
|
func ChannelIDLT(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDLTE applies the LTE predicate on the "channel_id" field.
|
||||||
|
func ChannelIDLTE(v int64) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldChannelID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDIsNil applies the IsNil predicate on the "channel_id" field.
|
||||||
|
func ChannelIDIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldChannelID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelIDNotNil applies the NotNil predicate on the "channel_id" field.
|
||||||
|
func ChannelIDNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldChannelID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainEQ applies the EQ predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainNEQ applies the NEQ predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainNEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainIn applies the In predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldModelMappingChain, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainNotIn applies the NotIn predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainNotIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldModelMappingChain, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainGT applies the GT predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainGT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainGTE applies the GTE predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainGTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainLT applies the LT predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainLT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainLTE applies the LTE predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainLTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainContains applies the Contains predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainContains(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContains(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainHasPrefix applies the HasPrefix predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainHasPrefix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasPrefix(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainHasSuffix applies the HasSuffix predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainHasSuffix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasSuffix(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainIsNil applies the IsNil predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldModelMappingChain))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainNotNil applies the NotNil predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldModelMappingChain))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainEqualFold applies the EqualFold predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainEqualFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEqualFold(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMappingChainContainsFold applies the ContainsFold predicate on the "model_mapping_chain" field.
|
||||||
|
func ModelMappingChainContainsFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContainsFold(FieldModelMappingChain, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierEQ applies the EQ predicate on the "billing_tier" field.
|
||||||
|
func BillingTierEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierNEQ applies the NEQ predicate on the "billing_tier" field.
|
||||||
|
func BillingTierNEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierIn applies the In predicate on the "billing_tier" field.
|
||||||
|
func BillingTierIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldBillingTier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierNotIn applies the NotIn predicate on the "billing_tier" field.
|
||||||
|
func BillingTierNotIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldBillingTier, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierGT applies the GT predicate on the "billing_tier" field.
|
||||||
|
func BillingTierGT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierGTE applies the GTE predicate on the "billing_tier" field.
|
||||||
|
func BillingTierGTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierLT applies the LT predicate on the "billing_tier" field.
|
||||||
|
func BillingTierLT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierLTE applies the LTE predicate on the "billing_tier" field.
|
||||||
|
func BillingTierLTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierContains applies the Contains predicate on the "billing_tier" field.
|
||||||
|
func BillingTierContains(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContains(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierHasPrefix applies the HasPrefix predicate on the "billing_tier" field.
|
||||||
|
func BillingTierHasPrefix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasPrefix(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierHasSuffix applies the HasSuffix predicate on the "billing_tier" field.
|
||||||
|
func BillingTierHasSuffix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasSuffix(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierIsNil applies the IsNil predicate on the "billing_tier" field.
|
||||||
|
func BillingTierIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldBillingTier))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierNotNil applies the NotNil predicate on the "billing_tier" field.
|
||||||
|
func BillingTierNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldBillingTier))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierEqualFold applies the EqualFold predicate on the "billing_tier" field.
|
||||||
|
func BillingTierEqualFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEqualFold(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingTierContainsFold applies the ContainsFold predicate on the "billing_tier" field.
|
||||||
|
func BillingTierContainsFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContainsFold(FieldBillingTier, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeEQ applies the EQ predicate on the "billing_mode" field.
|
||||||
|
func BillingModeEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEQ(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeNEQ applies the NEQ predicate on the "billing_mode" field.
|
||||||
|
func BillingModeNEQ(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNEQ(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeIn applies the In predicate on the "billing_mode" field.
|
||||||
|
func BillingModeIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIn(FieldBillingMode, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeNotIn applies the NotIn predicate on the "billing_mode" field.
|
||||||
|
func BillingModeNotIn(vs ...string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotIn(FieldBillingMode, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeGT applies the GT predicate on the "billing_mode" field.
|
||||||
|
func BillingModeGT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGT(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeGTE applies the GTE predicate on the "billing_mode" field.
|
||||||
|
func BillingModeGTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldGTE(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeLT applies the LT predicate on the "billing_mode" field.
|
||||||
|
func BillingModeLT(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLT(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeLTE applies the LTE predicate on the "billing_mode" field.
|
||||||
|
func BillingModeLTE(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldLTE(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeContains applies the Contains predicate on the "billing_mode" field.
|
||||||
|
func BillingModeContains(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContains(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeHasPrefix applies the HasPrefix predicate on the "billing_mode" field.
|
||||||
|
func BillingModeHasPrefix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasPrefix(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeHasSuffix applies the HasSuffix predicate on the "billing_mode" field.
|
||||||
|
func BillingModeHasSuffix(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldHasSuffix(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeIsNil applies the IsNil predicate on the "billing_mode" field.
|
||||||
|
func BillingModeIsNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldIsNull(FieldBillingMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeNotNil applies the NotNil predicate on the "billing_mode" field.
|
||||||
|
func BillingModeNotNil() predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldNotNull(FieldBillingMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeEqualFold applies the EqualFold predicate on the "billing_mode" field.
|
||||||
|
func BillingModeEqualFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldEqualFold(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BillingModeContainsFold applies the ContainsFold predicate on the "billing_mode" field.
|
||||||
|
func BillingModeContainsFold(v string) predicate.UsageLog {
|
||||||
|
return predicate.UsageLog(sql.FieldContainsFold(FieldBillingMode, v))
|
||||||
|
}
|
||||||
|
|
||||||
// GroupIDEQ applies the EQ predicate on the "group_id" field.
|
// GroupIDEQ applies the EQ predicate on the "group_id" field.
|
||||||
func GroupIDEQ(v int64) predicate.UsageLog {
|
func GroupIDEQ(v int64) predicate.UsageLog {
|
||||||
return predicate.UsageLog(sql.FieldEQ(FieldGroupID, v))
|
return predicate.UsageLog(sql.FieldEQ(FieldGroupID, v))
|
||||||
|
|||||||
@@ -57,6 +57,90 @@ func (_c *UsageLogCreate) SetModel(v string) *UsageLogCreate {
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (_c *UsageLogCreate) SetRequestedModel(v string) *UsageLogCreate {
|
||||||
|
_c.mutation.SetRequestedModel(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableRequestedModel(v *string) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetRequestedModel(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (_c *UsageLogCreate) SetUpstreamModel(v string) *UsageLogCreate {
|
||||||
|
_c.mutation.SetUpstreamModel(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUpstreamModel sets the "upstream_model" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableUpstreamModel(v *string) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetUpstreamModel(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (_c *UsageLogCreate) SetChannelID(v int64) *UsageLogCreate {
|
||||||
|
_c.mutation.SetChannelID(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableChannelID sets the "channel_id" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableChannelID(v *int64) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetChannelID(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (_c *UsageLogCreate) SetModelMappingChain(v string) *UsageLogCreate {
|
||||||
|
_c.mutation.SetModelMappingChain(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelMappingChain sets the "model_mapping_chain" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableModelMappingChain(v *string) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetModelMappingChain(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (_c *UsageLogCreate) SetBillingTier(v string) *UsageLogCreate {
|
||||||
|
_c.mutation.SetBillingTier(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingTier sets the "billing_tier" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableBillingTier(v *string) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetBillingTier(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (_c *UsageLogCreate) SetBillingMode(v string) *UsageLogCreate {
|
||||||
|
_c.mutation.SetBillingMode(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingMode sets the "billing_mode" field if the given value is not nil.
|
||||||
|
func (_c *UsageLogCreate) SetNillableBillingMode(v *string) *UsageLogCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetBillingMode(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (_c *UsageLogCreate) SetGroupID(v int64) *UsageLogCreate {
|
func (_c *UsageLogCreate) SetGroupID(v int64) *UsageLogCreate {
|
||||||
_c.mutation.SetGroupID(v)
|
_c.mutation.SetGroupID(v)
|
||||||
@@ -596,6 +680,31 @@ func (_c *UsageLogCreate) check() error {
|
|||||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _c.mutation.RequestedModel(); ok {
|
||||||
|
if err := usagelog.RequestedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "requested_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.requested_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.UpstreamModel(); ok {
|
||||||
|
if err := usagelog.UpstreamModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "upstream_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.upstream_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.ModelMappingChain(); ok {
|
||||||
|
if err := usagelog.ModelMappingChainValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "model_mapping_chain", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model_mapping_chain": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.BillingTier(); ok {
|
||||||
|
if err := usagelog.BillingTierValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_tier", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_tier": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.BillingMode(); ok {
|
||||||
|
if err := usagelog.BillingModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_mode", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := _c.mutation.InputTokens(); !ok {
|
if _, ok := _c.mutation.InputTokens(); !ok {
|
||||||
return &ValidationError{Name: "input_tokens", err: errors.New(`ent: missing required field "UsageLog.input_tokens"`)}
|
return &ValidationError{Name: "input_tokens", err: errors.New(`ent: missing required field "UsageLog.input_tokens"`)}
|
||||||
}
|
}
|
||||||
@@ -714,6 +823,30 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
|
|||||||
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
||||||
_node.Model = value
|
_node.Model = value
|
||||||
}
|
}
|
||||||
|
if value, ok := _c.mutation.RequestedModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldRequestedModel, field.TypeString, value)
|
||||||
|
_node.RequestedModel = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.UpstreamModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldUpstreamModel, field.TypeString, value)
|
||||||
|
_node.UpstreamModel = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.ChannelID(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldChannelID, field.TypeInt64, value)
|
||||||
|
_node.ChannelID = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.ModelMappingChain(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldModelMappingChain, field.TypeString, value)
|
||||||
|
_node.ModelMappingChain = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.BillingTier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingTier, field.TypeString, value)
|
||||||
|
_node.BillingTier = &value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.BillingMode(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingMode, field.TypeString, value)
|
||||||
|
_node.BillingMode = &value
|
||||||
|
}
|
||||||
if value, ok := _c.mutation.InputTokens(); ok {
|
if value, ok := _c.mutation.InputTokens(); ok {
|
||||||
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
||||||
_node.InputTokens = value
|
_node.InputTokens = value
|
||||||
@@ -1011,6 +1144,120 @@ func (u *UsageLogUpsert) UpdateModel() *UsageLogUpsert {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsert) SetRequestedModel(v string) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldRequestedModel, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateRequestedModel() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldRequestedModel)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestedModel clears the value of the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsert) ClearRequestedModel() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldRequestedModel)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsert) SetUpstreamModel(v string) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldUpstreamModel, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUpstreamModel sets the "upstream_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateUpstreamModel() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldUpstreamModel)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpstreamModel clears the value of the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsert) ClearUpstreamModel() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldUpstreamModel)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsert) SetChannelID(v int64) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldChannelID, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateChannelID sets the "channel_id" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateChannelID() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldChannelID)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChannelID adds v to the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsert) AddChannelID(v int64) *UsageLogUpsert {
|
||||||
|
u.Add(usagelog.FieldChannelID, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChannelID clears the value of the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsert) ClearChannelID() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldChannelID)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsert) SetModelMappingChain(v string) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldModelMappingChain, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelMappingChain sets the "model_mapping_chain" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateModelMappingChain() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldModelMappingChain)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelMappingChain clears the value of the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsert) ClearModelMappingChain() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldModelMappingChain)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsert) SetBillingTier(v string) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldBillingTier, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingTier sets the "billing_tier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateBillingTier() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldBillingTier)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingTier clears the value of the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsert) ClearBillingTier() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldBillingTier)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsert) SetBillingMode(v string) *UsageLogUpsert {
|
||||||
|
u.Set(usagelog.FieldBillingMode, v)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingMode sets the "billing_mode" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsert) UpdateBillingMode() *UsageLogUpsert {
|
||||||
|
u.SetExcluded(usagelog.FieldBillingMode)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingMode clears the value of the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsert) ClearBillingMode() *UsageLogUpsert {
|
||||||
|
u.SetNull(usagelog.FieldBillingMode)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (u *UsageLogUpsert) SetGroupID(v int64) *UsageLogUpsert {
|
func (u *UsageLogUpsert) SetGroupID(v int64) *UsageLogUpsert {
|
||||||
u.Set(usagelog.FieldGroupID, v)
|
u.Set(usagelog.FieldGroupID, v)
|
||||||
@@ -1600,6 +1847,139 @@ func (u *UsageLogUpsertOne) UpdateModel() *UsageLogUpsertOne {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetRequestedModel(v string) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetRequestedModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateRequestedModel() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateRequestedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestedModel clears the value of the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearRequestedModel() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearRequestedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetUpstreamModel(v string) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetUpstreamModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUpstreamModel sets the "upstream_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateUpstreamModel() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateUpstreamModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpstreamModel clears the value of the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearUpstreamModel() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearUpstreamModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetChannelID(v int64) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetChannelID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChannelID adds v to the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertOne) AddChannelID(v int64) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.AddChannelID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateChannelID sets the "channel_id" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateChannelID() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateChannelID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChannelID clears the value of the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearChannelID() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearChannelID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetModelMappingChain(v string) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetModelMappingChain(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelMappingChain sets the "model_mapping_chain" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateModelMappingChain() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateModelMappingChain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelMappingChain clears the value of the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearModelMappingChain() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearModelMappingChain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetBillingTier(v string) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetBillingTier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingTier sets the "billing_tier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateBillingTier() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateBillingTier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingTier clears the value of the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearBillingTier() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearBillingTier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsertOne) SetBillingMode(v string) *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetBillingMode(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingMode sets the "billing_mode" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertOne) UpdateBillingMode() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateBillingMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingMode clears the value of the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsertOne) ClearBillingMode() *UsageLogUpsertOne {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearBillingMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (u *UsageLogUpsertOne) SetGroupID(v int64) *UsageLogUpsertOne {
|
func (u *UsageLogUpsertOne) SetGroupID(v int64) *UsageLogUpsertOne {
|
||||||
return u.Update(func(s *UsageLogUpsert) {
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
@@ -2434,6 +2814,139 @@ func (u *UsageLogUpsertBulk) UpdateModel() *UsageLogUpsertBulk {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetRequestedModel(v string) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetRequestedModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRequestedModel sets the "requested_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateRequestedModel() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateRequestedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestedModel clears the value of the "requested_model" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearRequestedModel() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearRequestedModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetUpstreamModel(v string) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetUpstreamModel(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUpstreamModel sets the "upstream_model" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateUpstreamModel() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateUpstreamModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpstreamModel clears the value of the "upstream_model" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearUpstreamModel() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearUpstreamModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetChannelID(v int64) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetChannelID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChannelID adds v to the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertBulk) AddChannelID(v int64) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.AddChannelID(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateChannelID sets the "channel_id" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateChannelID() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateChannelID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChannelID clears the value of the "channel_id" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearChannelID() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearChannelID()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetModelMappingChain(v string) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetModelMappingChain(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModelMappingChain sets the "model_mapping_chain" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateModelMappingChain() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateModelMappingChain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelMappingChain clears the value of the "model_mapping_chain" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearModelMappingChain() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearModelMappingChain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetBillingTier(v string) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetBillingTier(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingTier sets the "billing_tier" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateBillingTier() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateBillingTier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingTier clears the value of the "billing_tier" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearBillingTier() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearBillingTier()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsertBulk) SetBillingMode(v string) *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.SetBillingMode(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBillingMode sets the "billing_mode" field to the value that was provided on create.
|
||||||
|
func (u *UsageLogUpsertBulk) UpdateBillingMode() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.UpdateBillingMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingMode clears the value of the "billing_mode" field.
|
||||||
|
func (u *UsageLogUpsertBulk) ClearBillingMode() *UsageLogUpsertBulk {
|
||||||
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
s.ClearBillingMode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (u *UsageLogUpsertBulk) SetGroupID(v int64) *UsageLogUpsertBulk {
|
func (u *UsageLogUpsertBulk) SetGroupID(v int64) *UsageLogUpsertBulk {
|
||||||
return u.Update(func(s *UsageLogUpsert) {
|
return u.Update(func(s *UsageLogUpsert) {
|
||||||
|
|||||||
@@ -102,6 +102,133 @@ func (_u *UsageLogUpdate) SetNillableModel(v *string) *UsageLogUpdate {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (_u *UsageLogUpdate) SetRequestedModel(v string) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetRequestedModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableRequestedModel(v *string) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequestedModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestedModel clears the value of the "requested_model" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearRequestedModel() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearRequestedModel()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (_u *UsageLogUpdate) SetUpstreamModel(v string) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetUpstreamModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUpstreamModel sets the "upstream_model" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableUpstreamModel(v *string) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUpstreamModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpstreamModel clears the value of the "upstream_model" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearUpstreamModel() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearUpstreamModel()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdate) SetChannelID(v int64) *UsageLogUpdate {
|
||||||
|
_u.mutation.ResetChannelID()
|
||||||
|
_u.mutation.SetChannelID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableChannelID sets the "channel_id" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableChannelID(v *int64) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetChannelID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChannelID adds value to the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdate) AddChannelID(v int64) *UsageLogUpdate {
|
||||||
|
_u.mutation.AddChannelID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChannelID clears the value of the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearChannelID() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearChannelID()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (_u *UsageLogUpdate) SetModelMappingChain(v string) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetModelMappingChain(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelMappingChain sets the "model_mapping_chain" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableModelMappingChain(v *string) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetModelMappingChain(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelMappingChain clears the value of the "model_mapping_chain" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearModelMappingChain() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearModelMappingChain()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (_u *UsageLogUpdate) SetBillingTier(v string) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetBillingTier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingTier sets the "billing_tier" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableBillingTier(v *string) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetBillingTier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingTier clears the value of the "billing_tier" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearBillingTier() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearBillingTier()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (_u *UsageLogUpdate) SetBillingMode(v string) *UsageLogUpdate {
|
||||||
|
_u.mutation.SetBillingMode(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingMode sets the "billing_mode" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdate) SetNillableBillingMode(v *string) *UsageLogUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetBillingMode(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingMode clears the value of the "billing_mode" field.
|
||||||
|
func (_u *UsageLogUpdate) ClearBillingMode() *UsageLogUpdate {
|
||||||
|
_u.mutation.ClearBillingMode()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (_u *UsageLogUpdate) SetGroupID(v int64) *UsageLogUpdate {
|
func (_u *UsageLogUpdate) SetGroupID(v int64) *UsageLogUpdate {
|
||||||
_u.mutation.SetGroupID(v)
|
_u.mutation.SetGroupID(v)
|
||||||
@@ -745,6 +872,31 @@ func (_u *UsageLogUpdate) check() error {
|
|||||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.RequestedModel(); ok {
|
||||||
|
if err := usagelog.RequestedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "requested_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.requested_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.UpstreamModel(); ok {
|
||||||
|
if err := usagelog.UpstreamModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "upstream_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.upstream_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.ModelMappingChain(); ok {
|
||||||
|
if err := usagelog.ModelMappingChainValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "model_mapping_chain", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model_mapping_chain": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.BillingTier(); ok {
|
||||||
|
if err := usagelog.BillingTierValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_tier", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_tier": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.BillingMode(); ok {
|
||||||
|
if err := usagelog.BillingModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_mode", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
if v, ok := _u.mutation.UserAgent(); ok {
|
if v, ok := _u.mutation.UserAgent(); ok {
|
||||||
if err := usagelog.UserAgentValidator(v); err != nil {
|
if err := usagelog.UserAgentValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
||||||
@@ -795,6 +947,45 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
|||||||
if value, ok := _u.mutation.Model(); ok {
|
if value, ok := _u.mutation.Model(); ok {
|
||||||
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.RequestedModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldRequestedModel, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.RequestedModelCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldRequestedModel, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpstreamModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldUpstreamModel, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.UpstreamModelCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldUpstreamModel, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ChannelID(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldChannelID, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedChannelID(); ok {
|
||||||
|
_spec.AddField(usagelog.FieldChannelID, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ChannelIDCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldChannelID, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ModelMappingChain(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldModelMappingChain, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ModelMappingChainCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldModelMappingChain, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.BillingTier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingTier, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.BillingTierCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldBillingTier, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.BillingMode(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingMode, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.BillingModeCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldBillingMode, field.TypeString)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.InputTokens(); ok {
|
if value, ok := _u.mutation.InputTokens(); ok {
|
||||||
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
@@ -1177,6 +1368,133 @@ func (_u *UsageLogUpdateOne) SetNillableModel(v *string) *UsageLogUpdateOne {
|
|||||||
return _u
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRequestedModel sets the "requested_model" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetRequestedModel(v string) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetRequestedModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableRequestedModel sets the "requested_model" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableRequestedModel(v *string) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetRequestedModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestedModel clears the value of the "requested_model" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearRequestedModel() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearRequestedModel()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpstreamModel sets the "upstream_model" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetUpstreamModel(v string) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetUpstreamModel(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUpstreamModel sets the "upstream_model" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableUpstreamModel(v *string) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUpstreamModel(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUpstreamModel clears the value of the "upstream_model" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearUpstreamModel() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearUpstreamModel()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelID sets the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetChannelID(v int64) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ResetChannelID()
|
||||||
|
_u.mutation.SetChannelID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableChannelID sets the "channel_id" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableChannelID(v *int64) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetChannelID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChannelID adds value to the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdateOne) AddChannelID(v int64) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.AddChannelID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChannelID clears the value of the "channel_id" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearChannelID() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearChannelID()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModelMappingChain sets the "model_mapping_chain" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetModelMappingChain(v string) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetModelMappingChain(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableModelMappingChain sets the "model_mapping_chain" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableModelMappingChain(v *string) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetModelMappingChain(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearModelMappingChain clears the value of the "model_mapping_chain" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearModelMappingChain() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearModelMappingChain()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingTier sets the "billing_tier" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetBillingTier(v string) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetBillingTier(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingTier sets the "billing_tier" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableBillingTier(v *string) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetBillingTier(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingTier clears the value of the "billing_tier" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearBillingTier() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearBillingTier()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBillingMode sets the "billing_mode" field.
|
||||||
|
func (_u *UsageLogUpdateOne) SetBillingMode(v string) *UsageLogUpdateOne {
|
||||||
|
_u.mutation.SetBillingMode(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableBillingMode sets the "billing_mode" field if the given value is not nil.
|
||||||
|
func (_u *UsageLogUpdateOne) SetNillableBillingMode(v *string) *UsageLogUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetBillingMode(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBillingMode clears the value of the "billing_mode" field.
|
||||||
|
func (_u *UsageLogUpdateOne) ClearBillingMode() *UsageLogUpdateOne {
|
||||||
|
_u.mutation.ClearBillingMode()
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
// SetGroupID sets the "group_id" field.
|
// SetGroupID sets the "group_id" field.
|
||||||
func (_u *UsageLogUpdateOne) SetGroupID(v int64) *UsageLogUpdateOne {
|
func (_u *UsageLogUpdateOne) SetGroupID(v int64) *UsageLogUpdateOne {
|
||||||
_u.mutation.SetGroupID(v)
|
_u.mutation.SetGroupID(v)
|
||||||
@@ -1833,6 +2151,31 @@ func (_u *UsageLogUpdateOne) check() error {
|
|||||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if v, ok := _u.mutation.RequestedModel(); ok {
|
||||||
|
if err := usagelog.RequestedModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "requested_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.requested_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.UpstreamModel(); ok {
|
||||||
|
if err := usagelog.UpstreamModelValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "upstream_model", err: fmt.Errorf(`ent: validator failed for field "UsageLog.upstream_model": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.ModelMappingChain(); ok {
|
||||||
|
if err := usagelog.ModelMappingChainValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "model_mapping_chain", err: fmt.Errorf(`ent: validator failed for field "UsageLog.model_mapping_chain": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.BillingTier(); ok {
|
||||||
|
if err := usagelog.BillingTierValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_tier", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_tier": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := _u.mutation.BillingMode(); ok {
|
||||||
|
if err := usagelog.BillingModeValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "billing_mode", err: fmt.Errorf(`ent: validator failed for field "UsageLog.billing_mode": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
if v, ok := _u.mutation.UserAgent(); ok {
|
if v, ok := _u.mutation.UserAgent(); ok {
|
||||||
if err := usagelog.UserAgentValidator(v); err != nil {
|
if err := usagelog.UserAgentValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
return &ValidationError{Name: "user_agent", err: fmt.Errorf(`ent: validator failed for field "UsageLog.user_agent": %w`, err)}
|
||||||
@@ -1900,6 +2243,45 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
|
|||||||
if value, ok := _u.mutation.Model(); ok {
|
if value, ok := _u.mutation.Model(); ok {
|
||||||
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
_spec.SetField(usagelog.FieldModel, field.TypeString, value)
|
||||||
}
|
}
|
||||||
|
if value, ok := _u.mutation.RequestedModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldRequestedModel, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.RequestedModelCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldRequestedModel, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.UpstreamModel(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldUpstreamModel, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.UpstreamModelCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldUpstreamModel, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ChannelID(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldChannelID, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.AddedChannelID(); ok {
|
||||||
|
_spec.AddField(usagelog.FieldChannelID, field.TypeInt64, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ChannelIDCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldChannelID, field.TypeInt64)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.ModelMappingChain(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldModelMappingChain, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.ModelMappingChainCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldModelMappingChain, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.BillingTier(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingTier, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.BillingTierCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldBillingTier, field.TypeString)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.BillingMode(); ok {
|
||||||
|
_spec.SetField(usagelog.FieldBillingMode, field.TypeString, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.BillingModeCleared() {
|
||||||
|
_spec.ClearField(usagelog.FieldBillingMode, field.TypeString)
|
||||||
|
}
|
||||||
if value, ok := _u.mutation.InputTokens(); ok {
|
if value, ok := _u.mutation.InputTokens(); ok {
|
||||||
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
_spec.SetField(usagelog.FieldInputTokens, field.TypeInt, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
module github.com/Wei-Shaw/sub2api
|
module github.com/Wei-Shaw/sub2api
|
||||||
|
|
||||||
go 1.25.7
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.14.5
|
entgo.io/ent v0.14.5
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||||
github.com/DouDOU-start/go-sora2api v1.1.0
|
github.com/DouDOU-start/go-sora2api v1.1.0
|
||||||
github.com/alitto/pond/v2 v2.6.2
|
github.com/alitto/pond/v2 v2.6.2
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.3
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
||||||
@@ -38,8 +39,6 @@ require (
|
|||||||
golang.org/x/net v0.49.0
|
golang.org/x/net v0.49.0
|
||||||
golang.org/x/sync v0.19.0
|
golang.org/x/sync v0.19.0
|
||||||
golang.org/x/term v0.40.0
|
golang.org/x/term v0.40.0
|
||||||
google.golang.org/grpc v1.75.1
|
|
||||||
google.golang.org/protobuf v1.36.10
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.44.3
|
modernc.org/sqlite v1.44.3
|
||||||
@@ -53,7 +52,6 @@ require (
|
|||||||
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/aws/aws-sdk-go-v2 v1.41.2 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||||
@@ -68,7 +66,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||||
github.com/aws/smithy-go v1.24.1 // indirect
|
github.com/aws/smithy-go v1.24.2 // indirect
|
||||||
github.com/bdandy/go-errors v1.2.2 // indirect
|
github.com/bdandy/go-errors v1.2.2 // indirect
|
||||||
github.com/bdandy/go-socks4 v1.2.3 // indirect
|
github.com/bdandy/go-socks4 v1.2.3 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
@@ -109,7 +107,6 @@ require (
|
|||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/subcommands v1.2.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
|
||||||
@@ -169,6 +166,7 @@ require (
|
|||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
@@ -178,8 +176,8 @@ require (
|
|||||||
golang.org/x/mod v0.32.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.41.0 // indirect
|
google.golang.org/grpc v1.75.1 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // 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/libc v1.67.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
|||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
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/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
||||||
@@ -58,8 +58,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWA
|
|||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
||||||
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
|
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||||
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
|
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
|
||||||
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
|
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
|
||||||
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
||||||
@@ -171,8 +171,6 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
@@ -182,8 +180,6 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 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=
|
||||||
@@ -203,6 +199,8 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
|||||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||||
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
||||||
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -398,8 +396,6 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd
|
|||||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
@@ -455,8 +451,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
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=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
||||||
|
|||||||
@@ -656,17 +656,33 @@ type TLSFingerprintConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TLSProfileConfig 单个TLS指纹模板的配置
|
// TLSProfileConfig 单个TLS指纹模板的配置
|
||||||
|
// 所有列表字段为空时使用内置默认值(Claude CLI 2.x / Node.js 20.x)
|
||||||
|
// 建议通过 TLS 指纹采集工具 (tests/tls-fingerprint-web) 获取完整配置
|
||||||
type TLSProfileConfig struct {
|
type TLSProfileConfig struct {
|
||||||
// Name: 模板显示名称
|
// Name: 模板显示名称
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
// EnableGREASE: 是否启用GREASE扩展(Chrome使用,Node.js不使用)
|
// EnableGREASE: 是否启用GREASE扩展(Chrome使用,Node.js不使用)
|
||||||
EnableGREASE bool `mapstructure:"enable_grease"`
|
EnableGREASE bool `mapstructure:"enable_grease"`
|
||||||
// CipherSuites: TLS加密套件列表(空则使用内置默认值)
|
// CipherSuites: TLS加密套件列表
|
||||||
CipherSuites []uint16 `mapstructure:"cipher_suites"`
|
CipherSuites []uint16 `mapstructure:"cipher_suites"`
|
||||||
// Curves: 椭圆曲线列表(空则使用内置默认值)
|
// Curves: 椭圆曲线列表
|
||||||
Curves []uint16 `mapstructure:"curves"`
|
Curves []uint16 `mapstructure:"curves"`
|
||||||
// PointFormats: 点格式列表(空则使用内置默认值)
|
// PointFormats: 点格式列表
|
||||||
PointFormats []uint8 `mapstructure:"point_formats"`
|
PointFormats []uint16 `mapstructure:"point_formats"`
|
||||||
|
// SignatureAlgorithms: 签名算法列表
|
||||||
|
SignatureAlgorithms []uint16 `mapstructure:"signature_algorithms"`
|
||||||
|
// ALPNProtocols: ALPN协议列表(如 ["h2", "http/1.1"])
|
||||||
|
ALPNProtocols []string `mapstructure:"alpn_protocols"`
|
||||||
|
// SupportedVersions: 支持的TLS版本列表(如 [0x0304, 0x0303] 即 TLS1.3, TLS1.2)
|
||||||
|
SupportedVersions []uint16 `mapstructure:"supported_versions"`
|
||||||
|
// KeyShareGroups: Key Share中发送的曲线组(如 [29] 即 X25519)
|
||||||
|
KeyShareGroups []uint16 `mapstructure:"key_share_groups"`
|
||||||
|
// PSKModes: PSK密钥交换模式(如 [1] 即 psk_dhe_ke)
|
||||||
|
PSKModes []uint16 `mapstructure:"psk_modes"`
|
||||||
|
// Extensions: TLS扩展类型ID列表,按发送顺序排列
|
||||||
|
// 空则使用内置默认顺序 [0,11,10,35,16,22,23,13,43,45,51]
|
||||||
|
// GREASE值(如0x0a0a)会自动插入GREASE扩展
|
||||||
|
Extensions []uint16 `mapstructure:"extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GatewaySchedulingConfig accounts scheduling configuration.
|
// GatewaySchedulingConfig accounts scheduling configuration.
|
||||||
@@ -935,6 +951,7 @@ type DashboardAggregationConfig struct {
|
|||||||
// DashboardAggregationRetentionConfig 预聚合保留窗口
|
// DashboardAggregationRetentionConfig 预聚合保留窗口
|
||||||
type DashboardAggregationRetentionConfig struct {
|
type DashboardAggregationRetentionConfig struct {
|
||||||
UsageLogsDays int `mapstructure:"usage_logs_days"`
|
UsageLogsDays int `mapstructure:"usage_logs_days"`
|
||||||
|
UsageBillingDedupDays int `mapstructure:"usage_billing_dedup_days"`
|
||||||
HourlyDays int `mapstructure:"hourly_days"`
|
HourlyDays int `mapstructure:"hourly_days"`
|
||||||
DailyDays int `mapstructure:"daily_days"`
|
DailyDays int `mapstructure:"daily_days"`
|
||||||
}
|
}
|
||||||
@@ -1264,8 +1281,8 @@ func setDefaults() {
|
|||||||
viper.SetDefault("rate_limit.oauth_401_cooldown_minutes", 10)
|
viper.SetDefault("rate_limit.oauth_401_cooldown_minutes", 10)
|
||||||
|
|
||||||
// Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据(固定到 commit,避免分支漂移)
|
// Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据(固定到 commit,避免分支漂移)
|
||||||
viper.SetDefault("pricing.remote_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.json")
|
viper.SetDefault("pricing.remote_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/main/model_prices_and_context_window.json")
|
||||||
viper.SetDefault("pricing.hash_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.sha256")
|
viper.SetDefault("pricing.hash_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/main/model_prices_and_context_window.sha256")
|
||||||
viper.SetDefault("pricing.data_dir", "./data")
|
viper.SetDefault("pricing.data_dir", "./data")
|
||||||
viper.SetDefault("pricing.fallback_file", "./resources/model-pricing/model_prices_and_context_window.json")
|
viper.SetDefault("pricing.fallback_file", "./resources/model-pricing/model_prices_and_context_window.json")
|
||||||
viper.SetDefault("pricing.update_interval_hours", 24)
|
viper.SetDefault("pricing.update_interval_hours", 24)
|
||||||
@@ -1301,6 +1318,7 @@ func setDefaults() {
|
|||||||
viper.SetDefault("dashboard_aggregation.backfill_enabled", false)
|
viper.SetDefault("dashboard_aggregation.backfill_enabled", false)
|
||||||
viper.SetDefault("dashboard_aggregation.backfill_max_days", 31)
|
viper.SetDefault("dashboard_aggregation.backfill_max_days", 31)
|
||||||
viper.SetDefault("dashboard_aggregation.retention.usage_logs_days", 90)
|
viper.SetDefault("dashboard_aggregation.retention.usage_logs_days", 90)
|
||||||
|
viper.SetDefault("dashboard_aggregation.retention.usage_billing_dedup_days", 365)
|
||||||
viper.SetDefault("dashboard_aggregation.retention.hourly_days", 180)
|
viper.SetDefault("dashboard_aggregation.retention.hourly_days", 180)
|
||||||
viper.SetDefault("dashboard_aggregation.retention.daily_days", 730)
|
viper.SetDefault("dashboard_aggregation.retention.daily_days", 730)
|
||||||
viper.SetDefault("dashboard_aggregation.recompute_days", 2)
|
viper.SetDefault("dashboard_aggregation.recompute_days", 2)
|
||||||
@@ -1402,7 +1420,7 @@ func setDefaults() {
|
|||||||
viper.SetDefault("gateway.concurrency_slot_ttl_minutes", 30) // 并发槽位过期时间(支持超长请求)
|
viper.SetDefault("gateway.concurrency_slot_ttl_minutes", 30) // 并发槽位过期时间(支持超长请求)
|
||||||
viper.SetDefault("gateway.stream_data_interval_timeout", 180)
|
viper.SetDefault("gateway.stream_data_interval_timeout", 180)
|
||||||
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", 500*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", 120*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)
|
||||||
@@ -1758,6 +1776,12 @@ func (c *Config) Validate() error {
|
|||||||
if c.DashboardAgg.Retention.UsageLogsDays <= 0 {
|
if c.DashboardAgg.Retention.UsageLogsDays <= 0 {
|
||||||
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be positive")
|
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be positive")
|
||||||
}
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageBillingDedupDays <= 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_billing_dedup_days must be positive")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageBillingDedupDays < c.DashboardAgg.Retention.UsageLogsDays {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_billing_dedup_days must be greater than or equal to usage_logs_days")
|
||||||
|
}
|
||||||
if c.DashboardAgg.Retention.HourlyDays <= 0 {
|
if c.DashboardAgg.Retention.HourlyDays <= 0 {
|
||||||
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be positive")
|
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be positive")
|
||||||
}
|
}
|
||||||
@@ -1780,6 +1804,14 @@ func (c *Config) Validate() error {
|
|||||||
if c.DashboardAgg.Retention.UsageLogsDays < 0 {
|
if c.DashboardAgg.Retention.UsageLogsDays < 0 {
|
||||||
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be non-negative")
|
return fmt.Errorf("dashboard_aggregation.retention.usage_logs_days must be non-negative")
|
||||||
}
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageBillingDedupDays < 0 {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_billing_dedup_days must be non-negative")
|
||||||
|
}
|
||||||
|
if c.DashboardAgg.Retention.UsageBillingDedupDays > 0 &&
|
||||||
|
c.DashboardAgg.Retention.UsageLogsDays > 0 &&
|
||||||
|
c.DashboardAgg.Retention.UsageBillingDedupDays < c.DashboardAgg.Retention.UsageLogsDays {
|
||||||
|
return fmt.Errorf("dashboard_aggregation.retention.usage_billing_dedup_days must be greater than or equal to usage_logs_days")
|
||||||
|
}
|
||||||
if c.DashboardAgg.Retention.HourlyDays < 0 {
|
if c.DashboardAgg.Retention.HourlyDays < 0 {
|
||||||
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be non-negative")
|
return fmt.Errorf("dashboard_aggregation.retention.hourly_days must be non-negative")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,6 +441,9 @@ func TestLoadDefaultDashboardAggregationConfig(t *testing.T) {
|
|||||||
if cfg.DashboardAgg.Retention.UsageLogsDays != 90 {
|
if cfg.DashboardAgg.Retention.UsageLogsDays != 90 {
|
||||||
t.Fatalf("DashboardAgg.Retention.UsageLogsDays = %d, want 90", cfg.DashboardAgg.Retention.UsageLogsDays)
|
t.Fatalf("DashboardAgg.Retention.UsageLogsDays = %d, want 90", cfg.DashboardAgg.Retention.UsageLogsDays)
|
||||||
}
|
}
|
||||||
|
if cfg.DashboardAgg.Retention.UsageBillingDedupDays != 365 {
|
||||||
|
t.Fatalf("DashboardAgg.Retention.UsageBillingDedupDays = %d, want 365", cfg.DashboardAgg.Retention.UsageBillingDedupDays)
|
||||||
|
}
|
||||||
if cfg.DashboardAgg.Retention.HourlyDays != 180 {
|
if cfg.DashboardAgg.Retention.HourlyDays != 180 {
|
||||||
t.Fatalf("DashboardAgg.Retention.HourlyDays = %d, want 180", cfg.DashboardAgg.Retention.HourlyDays)
|
t.Fatalf("DashboardAgg.Retention.HourlyDays = %d, want 180", cfg.DashboardAgg.Retention.HourlyDays)
|
||||||
}
|
}
|
||||||
@@ -1016,6 +1019,23 @@ func TestValidateConfigErrors(t *testing.T) {
|
|||||||
mutate: func(c *Config) { c.DashboardAgg.Enabled = true; c.DashboardAgg.Retention.UsageLogsDays = 0 },
|
mutate: func(c *Config) { c.DashboardAgg.Enabled = true; c.DashboardAgg.Retention.UsageLogsDays = 0 },
|
||||||
wantErr: "dashboard_aggregation.retention.usage_logs_days",
|
wantErr: "dashboard_aggregation.retention.usage_logs_days",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation dedup retention",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.DashboardAgg.Enabled = true
|
||||||
|
c.DashboardAgg.Retention.UsageBillingDedupDays = 0
|
||||||
|
},
|
||||||
|
wantErr: "dashboard_aggregation.retention.usage_billing_dedup_days",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard aggregation dedup retention smaller than usage logs",
|
||||||
|
mutate: func(c *Config) {
|
||||||
|
c.DashboardAgg.Enabled = true
|
||||||
|
c.DashboardAgg.Retention.UsageLogsDays = 30
|
||||||
|
c.DashboardAgg.Retention.UsageBillingDedupDays = 29
|
||||||
|
},
|
||||||
|
wantErr: "dashboard_aggregation.retention.usage_billing_dedup_days",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "dashboard aggregation disabled interval",
|
name: "dashboard aggregation disabled interval",
|
||||||
mutate: func(c *Config) { c.DashboardAgg.Enabled = false; c.DashboardAgg.IntervalSeconds = -1 },
|
mutate: func(c *Config) { c.DashboardAgg.Enabled = false; c.DashboardAgg.IntervalSeconds = -1 },
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ const (
|
|||||||
AnnouncementStatusArchived = "archived"
|
AnnouncementStatusArchived = "archived"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnouncementNotifyModeSilent = "silent"
|
||||||
|
AnnouncementNotifyModePopup = "popup"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AnnouncementConditionTypeSubscription = "subscription"
|
AnnouncementConditionTypeSubscription = "subscription"
|
||||||
AnnouncementConditionTypeBalance = "balance"
|
AnnouncementConditionTypeBalance = "balance"
|
||||||
@@ -199,6 +204,7 @@ type Announcement struct {
|
|||||||
Title string
|
Title string
|
||||||
Content string
|
Content string
|
||||||
Status string
|
Status string
|
||||||
|
NotifyMode string
|
||||||
Targeting AnnouncementTargeting
|
Targeting AnnouncementTargeting
|
||||||
StartsAt *time.Time
|
StartsAt *time.Time
|
||||||
EndsAt *time.Time
|
EndsAt *time.Time
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const (
|
|||||||
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
||||||
AccountTypeAPIKey = "apikey" // API Key类型账号
|
AccountTypeAPIKey = "apikey" // API Key类型账号
|
||||||
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
||||||
|
AccountTypeBedrock = "bedrock" // AWS Bedrock 类型账号(通过 SigV4 签名或 API Key 连接 Bedrock,由 credentials.auth_mode 区分)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redeem type constants
|
// Redeem type constants
|
||||||
@@ -81,10 +82,12 @@ var DefaultAntigravityModelMapping = map[string]string{
|
|||||||
"claude-opus-4-5-20251101": "claude-opus-4-6-thinking", // 迁移旧模型
|
"claude-opus-4-5-20251101": "claude-opus-4-6-thinking", // 迁移旧模型
|
||||||
"claude-sonnet-4-5-20250929": "claude-sonnet-4-5",
|
"claude-sonnet-4-5-20250929": "claude-sonnet-4-5",
|
||||||
// Claude Haiku → Sonnet(无 Haiku 支持)
|
// Claude Haiku → Sonnet(无 Haiku 支持)
|
||||||
"claude-haiku-4-5": "claude-sonnet-4-5",
|
"claude-haiku-4-5": "claude-sonnet-4-6",
|
||||||
"claude-haiku-4-5-20251001": "claude-sonnet-4-5",
|
"claude-haiku-4-5-20251001": "claude-sonnet-4-6",
|
||||||
// Gemini 2.5 白名单
|
// Gemini 2.5 白名单
|
||||||
"gemini-2.5-flash": "gemini-2.5-flash",
|
"gemini-2.5-flash": "gemini-2.5-flash",
|
||||||
|
"gemini-2.5-flash-image": "gemini-2.5-flash-image",
|
||||||
|
"gemini-2.5-flash-image-preview": "gemini-2.5-flash-image",
|
||||||
"gemini-2.5-flash-lite": "gemini-2.5-flash-lite",
|
"gemini-2.5-flash-lite": "gemini-2.5-flash-lite",
|
||||||
"gemini-2.5-flash-thinking": "gemini-2.5-flash-thinking",
|
"gemini-2.5-flash-thinking": "gemini-2.5-flash-thinking",
|
||||||
"gemini-2.5-pro": "gemini-2.5-pro",
|
"gemini-2.5-pro": "gemini-2.5-pro",
|
||||||
@@ -111,3 +114,27 @@ var DefaultAntigravityModelMapping = map[string]string{
|
|||||||
"gpt-oss-120b-medium": "gpt-oss-120b-medium",
|
"gpt-oss-120b-medium": "gpt-oss-120b-medium",
|
||||||
"tab_flash_lite_preview": "tab_flash_lite_preview",
|
"tab_flash_lite_preview": "tab_flash_lite_preview",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultBedrockModelMapping 是 AWS Bedrock 平台的默认模型映射
|
||||||
|
// 将 Anthropic 标准模型名映射到 Bedrock 模型 ID
|
||||||
|
// 注意:此处的 "us." 前缀仅为默认值,ResolveBedrockModelID 会根据账号配置的
|
||||||
|
// aws_region 自动调整为匹配的区域前缀(如 eu.、apac.、jp. 等)
|
||||||
|
var DefaultBedrockModelMapping = map[string]string{
|
||||||
|
// Claude Opus
|
||||||
|
"claude-opus-4-6-thinking": "us.anthropic.claude-opus-4-6-v1",
|
||||||
|
"claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1",
|
||||||
|
"claude-opus-4-5-thinking": "us.anthropic.claude-opus-4-5-20251101-v1:0",
|
||||||
|
"claude-opus-4-5-20251101": "us.anthropic.claude-opus-4-5-20251101-v1:0",
|
||||||
|
"claude-opus-4-1": "us.anthropic.claude-opus-4-1-20250805-v1:0",
|
||||||
|
"claude-opus-4-20250514": "us.anthropic.claude-opus-4-20250514-v1:0",
|
||||||
|
// Claude Sonnet
|
||||||
|
"claude-sonnet-4-6-thinking": "us.anthropic.claude-sonnet-4-6",
|
||||||
|
"claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
|
||||||
|
"claude-sonnet-4-5": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||||
|
"claude-sonnet-4-5-thinking": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||||
|
"claude-sonnet-4-5-20250929": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||||
|
"claude-sonnet-4-20250514": "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||||
|
// Claude Haiku
|
||||||
|
"claude-haiku-4-5": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
"claude-haiku-4-5-20251001": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ func TestDefaultAntigravityModelMapping_ImageCompatibilityAliases(t *testing.T)
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cases := map[string]string{
|
cases := map[string]string{
|
||||||
|
"gemini-2.5-flash-image": "gemini-2.5-flash-image",
|
||||||
|
"gemini-2.5-flash-image-preview": "gemini-2.5-flash-image",
|
||||||
"gemini-3.1-flash-image": "gemini-3.1-flash-image",
|
"gemini-3.1-flash-image": "gemini-3.1-flash-image",
|
||||||
"gemini-3.1-flash-image-preview": "gemini-3.1-flash-image",
|
"gemini-3.1-flash-image-preview": "gemini-3.1-flash-image",
|
||||||
"gemini-3-pro-image": "gemini-3.1-flash-image",
|
"gemini-3-pro-image": "gemini-3.1-flash-image",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -264,6 +267,9 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 收集需要异步设置隐私的 Antigravity OAuth 账号
|
||||||
|
var privacyAccounts []*service.Account
|
||||||
|
|
||||||
for i := range dataPayload.Accounts {
|
for i := range dataPayload.Accounts {
|
||||||
item := dataPayload.Accounts[i]
|
item := dataPayload.Accounts[i]
|
||||||
if err := validateDataAccount(item); err != nil {
|
if err := validateDataAccount(item); err != nil {
|
||||||
@@ -292,6 +298,8 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enrichCredentialsFromIDToken(&item)
|
||||||
|
|
||||||
accountInput := &service.CreateAccountInput{
|
accountInput := &service.CreateAccountInput{
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
Notes: item.Notes,
|
Notes: item.Notes,
|
||||||
@@ -309,7 +317,8 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
|||||||
SkipDefaultGroupBind: skipDefaultGroupBind,
|
SkipDefaultGroupBind: skipDefaultGroupBind,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := h.adminService.CreateAccount(ctx, accountInput); err != nil {
|
created, err := h.adminService.CreateAccount(ctx, accountInput)
|
||||||
|
if err != nil {
|
||||||
result.AccountFailed++
|
result.AccountFailed++
|
||||||
result.Errors = append(result.Errors, DataImportError{
|
result.Errors = append(result.Errors, DataImportError{
|
||||||
Kind: "account",
|
Kind: "account",
|
||||||
@@ -318,9 +327,30 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// 收集 Antigravity OAuth 账号,稍后异步设置隐私
|
||||||
|
if created.Platform == service.PlatformAntigravity && created.Type == service.AccountTypeOAuth {
|
||||||
|
privacyAccounts = append(privacyAccounts, created)
|
||||||
|
}
|
||||||
result.AccountCreated++
|
result.AccountCreated++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 异步设置 Antigravity 隐私,避免大量导入时阻塞请求
|
||||||
|
if len(privacyAccounts) > 0 {
|
||||||
|
adminSvc := h.adminService
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
slog.Error("import_antigravity_privacy_panic", "recover", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
bgCtx := context.Background()
|
||||||
|
for _, acc := range privacyAccounts {
|
||||||
|
adminSvc.ForceAntigravityPrivacy(bgCtx, acc)
|
||||||
|
}
|
||||||
|
slog.Info("import_antigravity_privacy_done", "count", len(privacyAccounts))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +377,7 @@ func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, acc
|
|||||||
pageSize := dataPageCap
|
pageSize := dataPageCap
|
||||||
var out []service.Account
|
var out []service.Account
|
||||||
for {
|
for {
|
||||||
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, 0)
|
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -535,6 +565,57 @@ func defaultProxyName(name string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enrichCredentialsFromIDToken performs best-effort extraction of user info fields
|
||||||
|
// (email, plan_type, chatgpt_account_id, etc.) from id_token in credentials.
|
||||||
|
// Only applies to OpenAI/Sora OAuth accounts. Skips expired token errors silently.
|
||||||
|
// Existing credential values are never overwritten — only missing fields are filled.
|
||||||
|
func enrichCredentialsFromIDToken(item *DataAccount) {
|
||||||
|
if item.Credentials == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Only enrich OpenAI/Sora OAuth accounts
|
||||||
|
platform := strings.ToLower(strings.TrimSpace(item.Platform))
|
||||||
|
if platform != service.PlatformOpenAI && platform != service.PlatformSora {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.ToLower(strings.TrimSpace(item.Type)) != service.AccountTypeOAuth {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idToken, _ := item.Credentials["id_token"].(string)
|
||||||
|
if strings.TrimSpace(idToken) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeIDToken skips expiry validation — safe for imported data
|
||||||
|
claims, err := openai.DecodeIDToken(idToken)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("import_enrich_id_token_decode_failed", "account", item.Name, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := claims.GetUserInfo()
|
||||||
|
if userInfo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill missing fields only (never overwrite existing values)
|
||||||
|
setIfMissing := func(key, value string) {
|
||||||
|
if value == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if existing, _ := item.Credentials[key].(string); existing == "" {
|
||||||
|
item.Credentials[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIfMissing("email", userInfo.Email)
|
||||||
|
setIfMissing("plan_type", userInfo.PlanType)
|
||||||
|
setIfMissing("chatgpt_account_id", userInfo.ChatGPTAccountID)
|
||||||
|
setIfMissing("chatgpt_user_id", userInfo.ChatGPTUserID)
|
||||||
|
setIfMissing("organization_id", userInfo.OrganizationID)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeProxyStatus(status string) string {
|
func normalizeProxyStatus(status string) string {
|
||||||
normalized := strings.TrimSpace(strings.ToLower(status))
|
normalized := strings.TrimSpace(strings.ToLower(status))
|
||||||
switch normalized {
|
switch normalized {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,6 +20,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
||||||
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
@@ -95,13 +98,14 @@ type CreateAccountRequest struct {
|
|||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
Platform string `json:"platform" binding:"required"`
|
Platform string `json:"platform" binding:"required"`
|
||||||
Type string `json:"type" binding:"required,oneof=oauth setup-token apikey upstream"`
|
Type string `json:"type" binding:"required,oneof=oauth setup-token apikey upstream bedrock"`
|
||||||
Credentials map[string]any `json:"credentials" binding:"required"`
|
Credentials map[string]any `json:"credentials" binding:"required"`
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
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"`
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
|
LoadFactor *int `json:"load_factor"`
|
||||||
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"`
|
||||||
@@ -113,14 +117,15 @@ type CreateAccountRequest struct {
|
|||||||
type UpdateAccountRequest struct {
|
type UpdateAccountRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey upstream"`
|
Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey upstream bedrock"`
|
||||||
Credentials map[string]any `json:"credentials"`
|
Credentials map[string]any `json:"credentials"`
|
||||||
Extra map[string]any `json:"extra"`
|
Extra map[string]any `json:"extra"`
|
||||||
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"`
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
LoadFactor *int `json:"load_factor"`
|
||||||
|
Status string `json:"status" binding:"omitempty,oneof=active inactive error"`
|
||||||
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"`
|
||||||
@@ -135,6 +140,7 @@ type BulkUpdateAccountsRequest struct {
|
|||||||
Concurrency *int `json:"concurrency"`
|
Concurrency *int `json:"concurrency"`
|
||||||
Priority *int `json:"priority"`
|
Priority *int `json:"priority"`
|
||||||
RateMultiplier *float64 `json:"rate_multiplier"`
|
RateMultiplier *float64 `json:"rate_multiplier"`
|
||||||
|
LoadFactor *int `json:"load_factor"`
|
||||||
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"`
|
||||||
@@ -160,6 +166,8 @@ type AccountWithConcurrency struct {
|
|||||||
CurrentRPM *int `json:"current_rpm,omitempty"` // 当前分钟 RPM 计数
|
CurrentRPM *int `json:"current_rpm,omitempty"` // 当前分钟 RPM 计数
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accountListGroupUngroupedQueryValue = "ungrouped"
|
||||||
|
|
||||||
func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, account *service.Account) AccountWithConcurrency {
|
func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, account *service.Account) AccountWithConcurrency {
|
||||||
item := AccountWithConcurrency{
|
item := AccountWithConcurrency{
|
||||||
Account: dto.AccountFromService(account),
|
Account: dto.AccountFromService(account),
|
||||||
@@ -212,6 +220,7 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
accountType := c.Query("type")
|
accountType := c.Query("type")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
search := c.Query("search")
|
search := c.Query("search")
|
||||||
|
privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
|
||||||
// 标准化和验证 search 参数
|
// 标准化和验证 search 参数
|
||||||
search = strings.TrimSpace(search)
|
search = strings.TrimSpace(search)
|
||||||
if len(search) > 100 {
|
if len(search) > 100 {
|
||||||
@@ -221,10 +230,23 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
|
|
||||||
var groupID int64
|
var groupID int64
|
||||||
if groupIDStr := c.Query("group"); groupIDStr != "" {
|
if groupIDStr := c.Query("group"); groupIDStr != "" {
|
||||||
groupID, _ = strconv.ParseInt(groupIDStr, 10, 64)
|
if groupIDStr == accountListGroupUngroupedQueryValue {
|
||||||
|
groupID = service.AccountListGroupUngrouped
|
||||||
|
} else {
|
||||||
|
parsedGroupID, parseErr := strconv.ParseInt(groupIDStr, 10, 64)
|
||||||
|
if parseErr != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if parsedGroupID < 0 {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupID = parsedGroupID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID)
|
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -285,8 +307,8 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅非 lite 模式获取窗口费用(PostgreSQL 聚合查询,高开销)
|
// 始终获取窗口费用(PostgreSQL 聚合查询)
|
||||||
if !lite && len(windowCostAccountIDs) > 0 {
|
if len(windowCostAccountIDs) > 0 {
|
||||||
windowCosts = make(map[int64]float64)
|
windowCosts = make(map[int64]float64)
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
g, gctx := errgroup.WithContext(c.Request.Context())
|
g, gctx := errgroup.WithContext(c.Request.Context())
|
||||||
@@ -506,6 +528,7 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
|||||||
Concurrency: req.Concurrency,
|
Concurrency: req.Concurrency,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
|
LoadFactor: req.LoadFactor,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
ExpiresAt: req.ExpiresAt,
|
ExpiresAt: req.ExpiresAt,
|
||||||
AutoPauseOnExpired: req.AutoPauseOnExpired,
|
AutoPauseOnExpired: req.AutoPauseOnExpired,
|
||||||
@@ -514,6 +537,10 @@ func (h *AccountHandler) Create(c *gin.Context) {
|
|||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
return nil, execErr
|
return nil, execErr
|
||||||
}
|
}
|
||||||
|
// Antigravity OAuth: 新账号直接设置隐私
|
||||||
|
h.adminService.ForceAntigravityPrivacy(ctx, account)
|
||||||
|
// OpenAI OAuth: 新账号直接设置隐私
|
||||||
|
h.adminService.ForceOpenAIPrivacy(ctx, account)
|
||||||
return h.buildAccountResponseWithRuntime(ctx, account), nil
|
return h.buildAccountResponseWithRuntime(ctx, account), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -575,6 +602,7 @@ func (h *AccountHandler) Update(c *gin.Context) {
|
|||||||
Concurrency: req.Concurrency, // 指针类型,nil 表示未提供
|
Concurrency: req.Concurrency, // 指针类型,nil 表示未提供
|
||||||
Priority: req.Priority, // 指针类型,nil 表示未提供
|
Priority: req.Priority, // 指针类型,nil 表示未提供
|
||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
|
LoadFactor: req.LoadFactor,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
ExpiresAt: req.ExpiresAt,
|
ExpiresAt: req.ExpiresAt,
|
||||||
@@ -621,6 +649,7 @@ func (h *AccountHandler) Delete(c *gin.Context) {
|
|||||||
// TestAccountRequest represents the request body for testing an account
|
// TestAccountRequest represents the request body for testing an account
|
||||||
type TestAccountRequest struct {
|
type TestAccountRequest struct {
|
||||||
ModelID string `json:"model_id"`
|
ModelID string `json:"model_id"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncFromCRSRequest struct {
|
type SyncFromCRSRequest struct {
|
||||||
@@ -651,10 +680,46 @@ func (h *AccountHandler) Test(c *gin.Context) {
|
|||||||
_ = c.ShouldBindJSON(&req)
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
// Use AccountTestService to test the account with SSE streaming
|
// Use AccountTestService to test the account with SSE streaming
|
||||||
if err := h.accountTestService.TestAccountConnection(c, accountID, req.ModelID); err != nil {
|
if err := h.accountTestService.TestAccountConnection(c, accountID, req.ModelID, req.Prompt); err != nil {
|
||||||
// Error already sent via SSE, just log
|
// Error already sent via SSE, just log
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.rateLimitService != nil {
|
||||||
|
if _, err := h.rateLimitService.RecoverAccountAfterSuccessfulTest(c.Request.Context(), accountID); err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoverState handles unified recovery of recoverable account runtime state.
|
||||||
|
// POST /api/v1/admin/accounts/:id/recover-state
|
||||||
|
func (h *AccountHandler) RecoverState(c *gin.Context) {
|
||||||
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid account ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.rateLimitService == nil {
|
||||||
|
response.Error(c, http.StatusServiceUnavailable, "Rate limit service unavailable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := h.rateLimitService.RecoverAccountState(c.Request.Context(), accountID, service.AccountRecoveryOptions{
|
||||||
|
InvalidateToken: true,
|
||||||
|
}); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncFromCRS handles syncing accounts from claude-relay-service (CRS)
|
// SyncFromCRS handles syncing accounts from claude-relay-service (CRS)
|
||||||
@@ -710,52 +775,33 @@ func (h *AccountHandler) PreviewFromCRS(c *gin.Context) {
|
|||||||
response.Success(c, result)
|
response.Success(c, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh handles refreshing account credentials
|
// refreshSingleAccount refreshes credentials for a single OAuth account.
|
||||||
// POST /api/v1/admin/accounts/:id/refresh
|
// Returns (updatedAccount, warning, error) where warning is used for Antigravity ProjectIDMissing scenario.
|
||||||
func (h *AccountHandler) Refresh(c *gin.Context) {
|
func (h *AccountHandler) refreshSingleAccount(ctx context.Context, account *service.Account) (*service.Account, string, error) {
|
||||||
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
response.BadRequest(c, "Invalid account ID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get account
|
|
||||||
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
|
||||||
if err != nil {
|
|
||||||
response.NotFound(c, "Account not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only refresh OAuth-based accounts (oauth and setup-token)
|
|
||||||
if !account.IsOAuth() {
|
if !account.IsOAuth() {
|
||||||
response.BadRequest(c, "Cannot refresh non-OAuth account credentials")
|
return nil, "", infraerrors.BadRequest("NOT_OAUTH", "cannot refresh non-OAuth account")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newCredentials map[string]any
|
var newCredentials map[string]any
|
||||||
|
|
||||||
if account.IsOpenAI() {
|
if account.IsOpenAI() {
|
||||||
// Use OpenAI OAuth service to refresh token
|
tokenInfo, err := h.openaiOAuthService.RefreshAccountToken(ctx, account)
|
||||||
tokenInfo, err := h.openaiOAuthService.RefreshAccountToken(c.Request.Context(), account)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
// 刷新失败但 access_token 可能仍有效,尝试设置隐私
|
||||||
return
|
h.adminService.EnsureOpenAIPrivacy(ctx, account)
|
||||||
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build new credentials from token info
|
|
||||||
newCredentials = h.openaiOAuthService.BuildAccountCredentials(tokenInfo)
|
newCredentials = h.openaiOAuthService.BuildAccountCredentials(tokenInfo)
|
||||||
|
|
||||||
// Preserve non-token settings from existing credentials
|
|
||||||
for k, v := range account.Credentials {
|
for k, v := range account.Credentials {
|
||||||
if _, exists := newCredentials[k]; !exists {
|
if _, exists := newCredentials[k]; !exists {
|
||||||
newCredentials[k] = v
|
newCredentials[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if account.Platform == service.PlatformGemini {
|
} else if account.Platform == service.PlatformGemini {
|
||||||
tokenInfo, err := h.geminiOAuthService.RefreshAccountToken(c.Request.Context(), account)
|
tokenInfo, err := h.geminiOAuthService.RefreshAccountToken(ctx, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to refresh credentials: "+err.Error())
|
return nil, "", fmt.Errorf("failed to refresh credentials: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newCredentials = h.geminiOAuthService.BuildAccountCredentials(tokenInfo)
|
newCredentials = h.geminiOAuthService.BuildAccountCredentials(tokenInfo)
|
||||||
@@ -765,10 +811,9 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if account.Platform == service.PlatformAntigravity {
|
} else if account.Platform == service.PlatformAntigravity {
|
||||||
tokenInfo, err := h.antigravityOAuthService.RefreshAccountToken(c.Request.Context(), account)
|
tokenInfo, err := h.antigravityOAuthService.RefreshAccountToken(ctx, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
return nil, "", err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newCredentials = h.antigravityOAuthService.BuildAccountCredentials(tokenInfo)
|
newCredentials = h.antigravityOAuthService.BuildAccountCredentials(tokenInfo)
|
||||||
@@ -787,37 +832,28 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果 project_id 获取失败,更新凭证但不标记为 error
|
// 如果 project_id 获取失败,更新凭证但不标记为 error
|
||||||
// LoadCodeAssist 失败可能是临时网络问题,给它机会在下次自动刷新时重试
|
|
||||||
if tokenInfo.ProjectIDMissing {
|
if tokenInfo.ProjectIDMissing {
|
||||||
// 先更新凭证(token 本身刷新成功了)
|
updatedAccount, updateErr := h.adminService.UpdateAccount(ctx, account.ID, &service.UpdateAccountInput{
|
||||||
_, updateErr := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{
|
|
||||||
Credentials: newCredentials,
|
Credentials: newCredentials,
|
||||||
})
|
})
|
||||||
if updateErr != nil {
|
if updateErr != nil {
|
||||||
response.InternalError(c, "Failed to update credentials: "+updateErr.Error())
|
return nil, "", fmt.Errorf("failed to update credentials: %w", updateErr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// 不标记为 error,只返回警告信息
|
h.adminService.EnsureAntigravityPrivacy(ctx, updatedAccount)
|
||||||
response.Success(c, gin.H{
|
return updatedAccount, "missing_project_id_temporary", nil
|
||||||
"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 错误则清除
|
// 成功获取到 project_id,如果之前是 missing_project_id 错误则清除
|
||||||
if account.Status == service.StatusError && strings.Contains(account.ErrorMessage, "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 {
|
if _, clearErr := h.adminService.ClearAccountError(ctx, account.ID); clearErr != nil {
|
||||||
response.InternalError(c, "Failed to clear account error: "+clearErr.Error())
|
return nil, "", fmt.Errorf("failed to clear account error: %w", clearErr)
|
||||||
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(ctx, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
return nil, "", err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy existing credentials to preserve non-token settings (e.g., intercept_warmup_requests)
|
// Copy existing credentials to preserve non-token settings (e.g., intercept_warmup_requests)
|
||||||
@@ -839,20 +875,56 @@ func (h *AccountHandler) Refresh(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAccount, err := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{
|
updatedAccount, err := h.adminService.UpdateAccount(ctx, account.ID, &service.UpdateAccountInput{
|
||||||
Credentials: newCredentials,
|
Credentials: newCredentials,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新成功后,清除 token 缓存,确保下次请求使用新 token
|
||||||
|
if h.tokenCacheInvalidator != nil {
|
||||||
|
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(ctx, updatedAccount); invalidateErr != nil {
|
||||||
|
log.Printf("[WARN] Failed to invalidate token cache for account %d: %v", updatedAccount.ID, invalidateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAI OAuth: 刷新成功后检查并设置 privacy_mode
|
||||||
|
h.adminService.EnsureOpenAIPrivacy(ctx, updatedAccount)
|
||||||
|
// Antigravity OAuth: 刷新成功后检查并设置 privacy_mode
|
||||||
|
h.adminService.EnsureAntigravityPrivacy(ctx, updatedAccount)
|
||||||
|
|
||||||
|
return updatedAccount, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh handles refreshing account credentials
|
||||||
|
// POST /api/v1/admin/accounts/:id/refresh
|
||||||
|
func (h *AccountHandler) Refresh(c *gin.Context) {
|
||||||
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid account ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get account
|
||||||
|
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
response.NotFound(c, "Account not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAccount, warning, err := h.refreshSingleAccount(c.Request.Context(), account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新成功后,清除 token 缓存,确保下次请求使用新 token
|
if warning == "missing_project_id_temporary" {
|
||||||
if h.tokenCacheInvalidator != nil {
|
response.Success(c, gin.H{
|
||||||
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(c.Request.Context(), updatedAccount); invalidateErr != nil {
|
"message": "Token refreshed successfully, but project_id could not be retrieved (will retry automatically)",
|
||||||
// 缓存失效失败只记录日志,不影响主流程
|
"warning": "missing_project_id_temporary",
|
||||||
_ = c.Error(invalidateErr)
|
})
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), updatedAccount))
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), updatedAccount))
|
||||||
@@ -908,14 +980,175 @@ func (h *AccountHandler) ClearError(c *gin.Context) {
|
|||||||
// 这解决了管理员重置账号状态后,旧的失效 token 仍在缓存中导致立即再次 401 的问题
|
// 这解决了管理员重置账号状态后,旧的失效 token 仍在缓存中导致立即再次 401 的问题
|
||||||
if h.tokenCacheInvalidator != nil && account.IsOAuth() {
|
if h.tokenCacheInvalidator != nil && account.IsOAuth() {
|
||||||
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(c.Request.Context(), account); invalidateErr != nil {
|
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(c.Request.Context(), account); invalidateErr != nil {
|
||||||
// 缓存失效失败只记录日志,不影响主流程
|
log.Printf("[WARN] Failed to invalidate token cache for account %d: %v", accountID, invalidateErr)
|
||||||
_ = c.Error(invalidateErr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BatchClearError handles batch clearing account errors
|
||||||
|
// POST /api/v1/admin/accounts/batch-clear-error
|
||||||
|
func (h *AccountHandler) BatchClearError(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
AccountIDs []int64 `json:"account_ids"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.AccountIDs) == 0 {
|
||||||
|
response.BadRequest(c, "account_ids is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
const maxConcurrency = 10
|
||||||
|
g, gctx := errgroup.WithContext(ctx)
|
||||||
|
g.SetLimit(maxConcurrency)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
var successCount, failedCount int
|
||||||
|
var errors []gin.H
|
||||||
|
|
||||||
|
// 注意:所有 goroutine 必须 return nil,避免 errgroup cancel 其他并发任务
|
||||||
|
for _, id := range req.AccountIDs {
|
||||||
|
accountID := id // 闭包捕获
|
||||||
|
g.Go(func() error {
|
||||||
|
account, err := h.adminService.ClearAccountError(gctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
mu.Lock()
|
||||||
|
failedCount++
|
||||||
|
errors = append(errors, gin.H{
|
||||||
|
"account_id": accountID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除错误后,同时清除 token 缓存
|
||||||
|
if h.tokenCacheInvalidator != nil && account.IsOAuth() {
|
||||||
|
if invalidateErr := h.tokenCacheInvalidator.InvalidateToken(gctx, account); invalidateErr != nil {
|
||||||
|
log.Printf("[WARN] Failed to invalidate token cache for account %d: %v", accountID, invalidateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
successCount++
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"total": len(req.AccountIDs),
|
||||||
|
"success": successCount,
|
||||||
|
"failed": failedCount,
|
||||||
|
"errors": errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchRefresh handles batch refreshing account credentials
|
||||||
|
// POST /api/v1/admin/accounts/batch-refresh
|
||||||
|
func (h *AccountHandler) BatchRefresh(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
AccountIDs []int64 `json:"account_ids"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.AccountIDs) == 0 {
|
||||||
|
response.BadRequest(c, "account_ids is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
accounts, err := h.adminService.GetAccountsByIDs(ctx, req.AccountIDs)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立已获取账号的 ID 集合,检测缺失的 ID
|
||||||
|
foundIDs := make(map[int64]bool, len(accounts))
|
||||||
|
for _, acc := range accounts {
|
||||||
|
if acc != nil {
|
||||||
|
foundIDs[acc.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxConcurrency = 10
|
||||||
|
g, gctx := errgroup.WithContext(ctx)
|
||||||
|
g.SetLimit(maxConcurrency)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
var successCount, failedCount int
|
||||||
|
var errors []gin.H
|
||||||
|
var warnings []gin.H
|
||||||
|
|
||||||
|
// 将不存在的账号 ID 标记为失败
|
||||||
|
for _, id := range req.AccountIDs {
|
||||||
|
if !foundIDs[id] {
|
||||||
|
failedCount++
|
||||||
|
errors = append(errors, gin.H{
|
||||||
|
"account_id": id,
|
||||||
|
"error": "account not found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:所有 goroutine 必须 return nil,避免 errgroup cancel 其他并发任务
|
||||||
|
for _, account := range accounts {
|
||||||
|
acc := account // 闭包捕获
|
||||||
|
if acc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
g.Go(func() error {
|
||||||
|
_, warning, err := h.refreshSingleAccount(gctx, acc)
|
||||||
|
mu.Lock()
|
||||||
|
if err != nil {
|
||||||
|
failedCount++
|
||||||
|
errors = append(errors, gin.H{
|
||||||
|
"account_id": acc.ID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
successCount++
|
||||||
|
if warning != "" {
|
||||||
|
warnings = append(warnings, gin.H{
|
||||||
|
"account_id": acc.ID,
|
||||||
|
"warning": warning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"total": len(req.AccountIDs),
|
||||||
|
"success": successCount,
|
||||||
|
"failed": failedCount,
|
||||||
|
"errors": errors,
|
||||||
|
"warnings": warnings,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// BatchCreate handles batch creating accounts
|
// BatchCreate handles batch creating accounts
|
||||||
// POST /api/v1/admin/accounts/batch
|
// POST /api/v1/admin/accounts/batch
|
||||||
func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
||||||
@@ -931,6 +1164,9 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
|||||||
success := 0
|
success := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
results := make([]gin.H, 0, len(req.Accounts))
|
results := make([]gin.H, 0, len(req.Accounts))
|
||||||
|
// 收集需要异步设置隐私的 OAuth 账号
|
||||||
|
var antigravityPrivacyAccounts []*service.Account
|
||||||
|
var openaiPrivacyAccounts []*service.Account
|
||||||
|
|
||||||
for _, item := range req.Accounts {
|
for _, item := range req.Accounts {
|
||||||
if item.RateMultiplier != nil && *item.RateMultiplier < 0 {
|
if item.RateMultiplier != nil && *item.RateMultiplier < 0 {
|
||||||
@@ -973,6 +1209,15 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// 收集需要异步设置隐私的 OAuth 账号
|
||||||
|
if account.Type == service.AccountTypeOAuth {
|
||||||
|
switch account.Platform {
|
||||||
|
case service.PlatformAntigravity:
|
||||||
|
antigravityPrivacyAccounts = append(antigravityPrivacyAccounts, account)
|
||||||
|
case service.PlatformOpenAI:
|
||||||
|
openaiPrivacyAccounts = append(openaiPrivacyAccounts, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
success++
|
success++
|
||||||
results = append(results, gin.H{
|
results = append(results, gin.H{
|
||||||
"name": item.Name,
|
"name": item.Name,
|
||||||
@@ -981,6 +1226,37 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 异步设置隐私,避免批量创建时阻塞请求
|
||||||
|
adminSvc := h.adminService
|
||||||
|
if len(antigravityPrivacyAccounts) > 0 {
|
||||||
|
accounts := antigravityPrivacyAccounts
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
slog.Error("batch_create_antigravity_privacy_panic", "recover", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
bgCtx := context.Background()
|
||||||
|
for _, acc := range accounts {
|
||||||
|
adminSvc.ForceAntigravityPrivacy(bgCtx, acc)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if len(openaiPrivacyAccounts) > 0 {
|
||||||
|
accounts := openaiPrivacyAccounts
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
slog.Error("batch_create_openai_privacy_panic", "recover", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
bgCtx := context.Background()
|
||||||
|
for _, acc := range accounts {
|
||||||
|
adminSvc.ForceOpenAIPrivacy(bgCtx, acc)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"success": success,
|
"success": success,
|
||||||
"failed": failed,
|
"failed": failed,
|
||||||
@@ -1101,6 +1377,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
|||||||
req.Concurrency != nil ||
|
req.Concurrency != nil ||
|
||||||
req.Priority != nil ||
|
req.Priority != nil ||
|
||||||
req.RateMultiplier != nil ||
|
req.RateMultiplier != nil ||
|
||||||
|
req.LoadFactor != nil ||
|
||||||
req.Status != "" ||
|
req.Status != "" ||
|
||||||
req.Schedulable != nil ||
|
req.Schedulable != nil ||
|
||||||
req.GroupIDs != nil ||
|
req.GroupIDs != nil ||
|
||||||
@@ -1119,6 +1396,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
|
|||||||
Concurrency: req.Concurrency,
|
Concurrency: req.Concurrency,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
|
LoadFactor: req.LoadFactor,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
Schedulable: req.Schedulable,
|
Schedulable: req.Schedulable,
|
||||||
GroupIDs: req.GroupIDs,
|
GroupIDs: req.GroupIDs,
|
||||||
@@ -1287,7 +1565,7 @@ func (h *OAuthHandler) SetupTokenCookieAuth(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage handles getting account usage information
|
// GetUsage handles getting account usage information
|
||||||
// GET /api/v1/admin/accounts/:id/usage
|
// GET /api/v1/admin/accounts/:id/usage?source=passive|active
|
||||||
func (h *AccountHandler) GetUsage(c *gin.Context) {
|
func (h *AccountHandler) GetUsage(c *gin.Context) {
|
||||||
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1295,7 +1573,14 @@ func (h *AccountHandler) GetUsage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usage, err := h.accountUsageService.GetUsage(c.Request.Context(), accountID)
|
source := c.DefaultQuery("source", "active")
|
||||||
|
|
||||||
|
var usage *service.UsageInfo
|
||||||
|
if source == "passive" {
|
||||||
|
usage, err = h.accountUsageService.GetPassiveUsage(c.Request.Context(), accountID)
|
||||||
|
} else {
|
||||||
|
usage, err = h.accountUsageService.GetUsage(c.Request.Context(), accountID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -1328,6 +1613,29 @@ func (h *AccountHandler) ClearRateLimit(c *gin.Context) {
|
|||||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetQuota handles resetting account quota usage
|
||||||
|
// POST /api/v1/admin/accounts/:id/reset-quota
|
||||||
|
func (h *AccountHandler) ResetQuota(c *gin.Context) {
|
||||||
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid account ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.adminService.ResetAccountQuota(c.Request.Context(), accountID); err != nil {
|
||||||
|
response.InternalError(c, "Failed to reset account quota: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||||
|
}
|
||||||
|
|
||||||
// GetTempUnschedulable handles getting temporary unschedulable status
|
// GetTempUnschedulable handles getting temporary unschedulable status
|
||||||
// GET /api/v1/admin/accounts/:id/temp-unschedulable
|
// GET /api/v1/admin/accounts/:id/temp-unschedulable
|
||||||
func (h *AccountHandler) GetTempUnschedulable(c *gin.Context) {
|
func (h *AccountHandler) GetTempUnschedulable(c *gin.Context) {
|
||||||
@@ -1486,13 +1794,12 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) {
|
|||||||
|
|
||||||
// Handle OpenAI accounts
|
// Handle OpenAI accounts
|
||||||
if account.IsOpenAI() {
|
if account.IsOpenAI() {
|
||||||
// For OAuth accounts: return default OpenAI models
|
// OpenAI 自动透传会绕过常规模型改写,测试/模型列表也应回落到默认模型集。
|
||||||
if account.IsOAuth() {
|
if account.IsOpenAIPassthroughEnabled() {
|
||||||
response.Success(c, openai.DefaultModels)
|
response.Success(c, openai.DefaultModels)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// For API Key accounts: check model_mapping
|
|
||||||
mapping := account.GetModelMapping()
|
mapping := account.GetModelMapping()
|
||||||
if len(mapping) == 0 {
|
if len(mapping) == 0 {
|
||||||
response.Success(c, openai.DefaultModels)
|
response.Success(c, openai.DefaultModels)
|
||||||
@@ -1615,6 +1922,51 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) {
|
|||||||
response.Success(c, models)
|
response.Success(c, models)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPrivacy handles setting privacy for a single OpenAI/Antigravity OAuth account
|
||||||
|
// POST /api/v1/admin/accounts/:id/set-privacy
|
||||||
|
func (h *AccountHandler) SetPrivacy(c *gin.Context) {
|
||||||
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid account ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
response.NotFound(c, "Account not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if account.Type != service.AccountTypeOAuth {
|
||||||
|
response.BadRequest(c, "Only OAuth accounts support privacy setting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var mode string
|
||||||
|
switch account.Platform {
|
||||||
|
case service.PlatformOpenAI:
|
||||||
|
mode = h.adminService.ForceOpenAIPrivacy(c.Request.Context(), account)
|
||||||
|
case service.PlatformAntigravity:
|
||||||
|
mode = h.adminService.ForceAntigravityPrivacy(c.Request.Context(), account)
|
||||||
|
default:
|
||||||
|
response.BadRequest(c, "Only OpenAI and Antigravity OAuth accounts support privacy setting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mode == "" {
|
||||||
|
response.BadRequest(c, "Cannot set privacy: missing access_token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 从 DB 重新读取以确保返回最新状态
|
||||||
|
updated, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
// 隐私已设置成功但读取失败,回退到内存更新
|
||||||
|
if account.Extra == nil {
|
||||||
|
account.Extra = make(map[string]any)
|
||||||
|
}
|
||||||
|
account.Extra["privacy_mode"] = mode
|
||||||
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), updated))
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshTier handles refreshing Google One tier for a single account
|
// RefreshTier handles refreshing Google One tier for a single account
|
||||||
// POST /api/v1/admin/accounts/:id/refresh-tier
|
// POST /api/v1/admin/accounts/:id/refresh-tier
|
||||||
func (h *AccountHandler) RefreshTier(c *gin.Context) {
|
func (h *AccountHandler) RefreshTier(c *gin.Context) {
|
||||||
@@ -1683,7 +2035,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
|
|||||||
accounts := make([]*service.Account, 0)
|
accounts := make([]*service.Account, 0)
|
||||||
|
|
||||||
if len(req.AccountIDs) == 0 {
|
if len(req.AccountIDs) == 0 {
|
||||||
allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0)
|
allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type availableModelsAdminService struct {
|
||||||
|
*stubAdminService
|
||||||
|
account service.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *availableModelsAdminService) GetAccount(_ context.Context, id int64) (*service.Account, error) {
|
||||||
|
if s.account.ID == id {
|
||||||
|
acc := s.account
|
||||||
|
return &acc, nil
|
||||||
|
}
|
||||||
|
return s.stubAdminService.GetAccount(context.Background(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAvailableModelsRouter(adminSvc service.AdminService) *gin.Engine {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
handler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||||
|
router.GET("/api/v1/admin/accounts/:id/models", handler.GetAvailableModels)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountHandlerGetAvailableModels_OpenAIOAuthUsesExplicitModelMapping(t *testing.T) {
|
||||||
|
svc := &availableModelsAdminService{
|
||||||
|
stubAdminService: newStubAdminService(),
|
||||||
|
account: service.Account{
|
||||||
|
ID: 42,
|
||||||
|
Name: "openai-oauth",
|
||||||
|
Platform: service.PlatformOpenAI,
|
||||||
|
Type: service.AccountTypeOAuth,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
Credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gpt-5": "gpt-5.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
router := setupAvailableModelsRouter(svc)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/42/models", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Data []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||||
|
require.Len(t, resp.Data, 1)
|
||||||
|
require.Equal(t, "gpt-5", resp.Data[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountHandlerGetAvailableModels_OpenAIOAuthPassthroughFallsBackToDefaults(t *testing.T) {
|
||||||
|
svc := &availableModelsAdminService{
|
||||||
|
stubAdminService: newStubAdminService(),
|
||||||
|
account: service.Account{
|
||||||
|
ID: 43,
|
||||||
|
Name: "openai-oauth-passthrough",
|
||||||
|
Platform: service.PlatformOpenAI,
|
||||||
|
Type: service.AccountTypeOAuth,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
Credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gpt-5": "gpt-5.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extra: map[string]any{
|
||||||
|
"openai_passthrough": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
router := setupAvailableModelsRouter(svc)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/43/models", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Data []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||||
|
require.NotEmpty(t, resp.Data)
|
||||||
|
require.NotEqual(t, "gpt-5", resp.Data[0].ID)
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
|
|||||||
adminSvc := newStubAdminService()
|
adminSvc := newStubAdminService()
|
||||||
|
|
||||||
userHandler := NewUserHandler(adminSvc, nil)
|
userHandler := NewUserHandler(adminSvc, nil)
|
||||||
groupHandler := NewGroupHandler(adminSvc)
|
groupHandler := NewGroupHandler(adminSvc, nil, nil)
|
||||||
proxyHandler := NewProxyHandler(adminSvc)
|
proxyHandler := NewProxyHandler(adminSvc)
|
||||||
redeemHandler := NewRedeemHandler(adminSvc, nil)
|
redeemHandler := NewRedeemHandler(adminSvc, nil)
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,19 @@ func (s *stubAdminService) GetGroupAPIKeys(ctx context.Context, groupID int64, p
|
|||||||
return s.apiKeys, int64(len(s.apiKeys)), nil
|
return s.apiKeys, int64(len(s.apiKeys)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64) ([]service.Account, int64, error) {
|
func (s *stubAdminService) GetGroupRateMultipliers(_ context.Context, _ int64) ([]service.UserGroupRateEntry, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ClearGroupRateMultipliers(_ context.Context, _ int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int64, _ []service.GroupRateMultiplierInput) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, int64, error) {
|
||||||
return s.accounts, int64(len(s.accounts)), nil
|
return s.accounts, int64(len(s.accounts)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,5 +437,29 @@ func (s *stubAdminService) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID i
|
|||||||
return nil, service.ErrAPIKeyNotFound
|
return nil, service.ErrAPIKeyNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ResetAccountQuota(ctx context.Context, id int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) EnsureOpenAIPrivacy(ctx context.Context, account *service.Account) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) EnsureAntigravityPrivacy(ctx context.Context, account *service.Account) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ForceOpenAIPrivacy(ctx context.Context, account *service.Account) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ForceAntigravityPrivacy(ctx context.Context, account *service.Account) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*service.ReplaceUserGroupResult, error) {
|
||||||
|
return &service.ReplaceUserGroupResult{MigratedKeys: 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure stub implements interface.
|
// Ensure stub implements interface.
|
||||||
var _ service.AdminService = (*stubAdminService)(nil)
|
var _ service.AdminService = (*stubAdminService)(nil)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type CreateAnnouncementRequest struct {
|
|||||||
Title string `json:"title" binding:"required"`
|
Title string `json:"title" binding:"required"`
|
||||||
Content string `json:"content" binding:"required"`
|
Content string `json:"content" binding:"required"`
|
||||||
Status string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
Status string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
||||||
|
NotifyMode string `json:"notify_mode" binding:"omitempty,oneof=silent popup"`
|
||||||
Targeting service.AnnouncementTargeting `json:"targeting"`
|
Targeting service.AnnouncementTargeting `json:"targeting"`
|
||||||
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0/empty = immediate
|
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0/empty = immediate
|
||||||
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0/empty = never
|
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0/empty = never
|
||||||
@@ -39,6 +40,7 @@ type UpdateAnnouncementRequest struct {
|
|||||||
Title *string `json:"title"`
|
Title *string `json:"title"`
|
||||||
Content *string `json:"content"`
|
Content *string `json:"content"`
|
||||||
Status *string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
Status *string `json:"status" binding:"omitempty,oneof=draft active archived"`
|
||||||
|
NotifyMode *string `json:"notify_mode" binding:"omitempty,oneof=silent popup"`
|
||||||
Targeting *service.AnnouncementTargeting `json:"targeting"`
|
Targeting *service.AnnouncementTargeting `json:"targeting"`
|
||||||
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0 = clear
|
StartsAt *int64 `json:"starts_at"` // Unix seconds, 0 = clear
|
||||||
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0 = clear
|
EndsAt *int64 `json:"ends_at"` // Unix seconds, 0 = clear
|
||||||
@@ -113,6 +115,7 @@ func (h *AnnouncementHandler) Create(c *gin.Context) {
|
|||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
|
NotifyMode: req.NotifyMode,
|
||||||
Targeting: req.Targeting,
|
Targeting: req.Targeting,
|
||||||
ActorID: &subject.UserID,
|
ActorID: &subject.UserID,
|
||||||
}
|
}
|
||||||
@@ -160,6 +163,7 @@ func (h *AnnouncementHandler) Update(c *gin.Context) {
|
|||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
|
NotifyMode: req.NotifyMode,
|
||||||
Targeting: req.Targeting,
|
Targeting: req.Targeting,
|
||||||
ActorID: &subject.UserID,
|
ActorID: &subject.UserID,
|
||||||
}
|
}
|
||||||
|
|||||||
205
backend/internal/handler/admin/backup_handler.go
Normal file
205
backend/internal/handler/admin/backup_handler.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 BackupHandler struct {
|
||||||
|
backupService *service.BackupService
|
||||||
|
userService *service.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBackupHandler(backupService *service.BackupService, userService *service.UserService) *BackupHandler {
|
||||||
|
return &BackupHandler{
|
||||||
|
backupService: backupService,
|
||||||
|
userService: userService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── S3 配置 ───
|
||||||
|
|
||||||
|
func (h *BackupHandler) GetS3Config(c *gin.Context) {
|
||||||
|
cfg, err := h.backupService.GetS3Config(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) UpdateS3Config(c *gin.Context) {
|
||||||
|
var req service.BackupS3Config
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg, err := h.backupService.UpdateS3Config(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) TestS3Connection(c *gin.Context) {
|
||||||
|
var req service.BackupS3Config
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := h.backupService.TestS3Connection(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
response.Success(c, gin.H{"ok": false, "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"ok": true, "message": "connection successful"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 定时备份 ───
|
||||||
|
|
||||||
|
func (h *BackupHandler) GetSchedule(c *gin.Context) {
|
||||||
|
cfg, err := h.backupService.GetSchedule(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) UpdateSchedule(c *gin.Context) {
|
||||||
|
var req service.BackupScheduleConfig
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg, err := h.backupService.UpdateSchedule(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 备份操作 ───
|
||||||
|
|
||||||
|
type CreateBackupRequest struct {
|
||||||
|
ExpireDays *int `json:"expire_days"` // nil=使用默认值14,0=永不过期
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) CreateBackup(c *gin.Context) {
|
||||||
|
var req CreateBackupRequest
|
||||||
|
_ = c.ShouldBindJSON(&req) // 允许空 body
|
||||||
|
|
||||||
|
expireDays := 14 // 默认14天过期
|
||||||
|
if req.ExpireDays != nil {
|
||||||
|
expireDays = *req.ExpireDays
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := h.backupService.StartBackup(c.Request.Context(), "manual", expireDays)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Accepted(c, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) ListBackups(c *gin.Context) {
|
||||||
|
records, err := h.backupService.ListBackups(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if records == nil {
|
||||||
|
records = []service.BackupRecord{}
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"items": records})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) GetBackup(c *gin.Context) {
|
||||||
|
backupID := c.Param("id")
|
||||||
|
if backupID == "" {
|
||||||
|
response.BadRequest(c, "backup ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record, err := h.backupService.GetBackupRecord(c.Request.Context(), backupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) DeleteBackup(c *gin.Context) {
|
||||||
|
backupID := c.Param("id")
|
||||||
|
if backupID == "" {
|
||||||
|
response.BadRequest(c, "backup ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.backupService.DeleteBackup(c.Request.Context(), backupID); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"deleted": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) GetDownloadURL(c *gin.Context) {
|
||||||
|
backupID := c.Param("id")
|
||||||
|
if backupID == "" {
|
||||||
|
response.BadRequest(c, "backup ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url, err := h.backupService.GetBackupDownloadURL(c.Request.Context(), backupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, gin.H{"url": url})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 恢复操作(需要重新输入管理员密码) ───
|
||||||
|
|
||||||
|
type RestoreBackupRequest struct {
|
||||||
|
Password string `json:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BackupHandler) RestoreBackup(c *gin.Context) {
|
||||||
|
backupID := c.Param("id")
|
||||||
|
if backupID == "" {
|
||||||
|
response.BadRequest(c, "backup ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req RestoreBackupRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "password is required for restore operation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从上下文获取当前管理员用户 ID
|
||||||
|
sub, ok := middleware.GetAuthSubjectFromContext(c)
|
||||||
|
if !ok {
|
||||||
|
response.Unauthorized(c, "unauthorized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取管理员用户并验证密码
|
||||||
|
user, err := h.userService.GetByID(c.Request.Context(), sub.UserID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !user.CheckPassword(req.Password) {
|
||||||
|
response.BadRequest(c, "incorrect admin password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := h.backupService.StartRestore(c.Request.Context(), backupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Accepted(c, record)
|
||||||
|
}
|
||||||
452
backend/internal/handler/admin/channel_handler.go
Normal file
452
backend/internal/handler/admin/channel_handler.go
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChannelHandler handles admin channel management
|
||||||
|
type ChannelHandler struct {
|
||||||
|
channelService *service.ChannelService
|
||||||
|
billingService *service.BillingService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChannelHandler creates a new admin channel handler
|
||||||
|
func NewChannelHandler(channelService *service.ChannelService, billingService *service.BillingService) *ChannelHandler {
|
||||||
|
return &ChannelHandler{channelService: channelService, billingService: billingService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Request / Response types ---
|
||||||
|
|
||||||
|
type createChannelRequest struct {
|
||||||
|
Name string `json:"name" binding:"required,max=100"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
GroupIDs []int64 `json:"group_ids"`
|
||||||
|
ModelPricing []channelModelPricingRequest `json:"model_pricing"`
|
||||||
|
ModelMapping map[string]map[string]string `json:"model_mapping"`
|
||||||
|
BillingModelSource string `json:"billing_model_source" binding:"omitempty,oneof=requested upstream channel_mapped"`
|
||||||
|
RestrictModels bool `json:"restrict_models"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateChannelRequest struct {
|
||||||
|
Name string `json:"name" binding:"omitempty,max=100"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Status string `json:"status" binding:"omitempty,oneof=active disabled"`
|
||||||
|
GroupIDs *[]int64 `json:"group_ids"`
|
||||||
|
ModelPricing *[]channelModelPricingRequest `json:"model_pricing"`
|
||||||
|
ModelMapping map[string]map[string]string `json:"model_mapping"`
|
||||||
|
BillingModelSource string `json:"billing_model_source" binding:"omitempty,oneof=requested upstream channel_mapped"`
|
||||||
|
RestrictModels *bool `json:"restrict_models"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelModelPricingRequest struct {
|
||||||
|
Platform string `json:"platform" binding:"omitempty,max=50"`
|
||||||
|
Models []string `json:"models" binding:"required,min=1,max=100"`
|
||||||
|
BillingMode string `json:"billing_mode" binding:"omitempty,oneof=token per_request image"`
|
||||||
|
InputPrice *float64 `json:"input_price" binding:"omitempty,min=0"`
|
||||||
|
OutputPrice *float64 `json:"output_price" binding:"omitempty,min=0"`
|
||||||
|
CacheWritePrice *float64 `json:"cache_write_price" binding:"omitempty,min=0"`
|
||||||
|
CacheReadPrice *float64 `json:"cache_read_price" binding:"omitempty,min=0"`
|
||||||
|
ImageOutputPrice *float64 `json:"image_output_price" binding:"omitempty,min=0"`
|
||||||
|
PerRequestPrice *float64 `json:"per_request_price" binding:"omitempty,min=0"`
|
||||||
|
Intervals []pricingIntervalRequest `json:"intervals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pricingIntervalRequest struct {
|
||||||
|
MinTokens int `json:"min_tokens"`
|
||||||
|
MaxTokens *int `json:"max_tokens"`
|
||||||
|
TierLabel string `json:"tier_label"`
|
||||||
|
InputPrice *float64 `json:"input_price"`
|
||||||
|
OutputPrice *float64 `json:"output_price"`
|
||||||
|
CacheWritePrice *float64 `json:"cache_write_price"`
|
||||||
|
CacheReadPrice *float64 `json:"cache_read_price"`
|
||||||
|
PerRequestPrice *float64 `json:"per_request_price"`
|
||||||
|
SortOrder int `json:"sort_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
BillingModelSource string `json:"billing_model_source"`
|
||||||
|
RestrictModels bool `json:"restrict_models"`
|
||||||
|
GroupIDs []int64 `json:"group_ids"`
|
||||||
|
ModelPricing []channelModelPricingResponse `json:"model_pricing"`
|
||||||
|
ModelMapping map[string]map[string]string `json:"model_mapping"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelModelPricingResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
Models []string `json:"models"`
|
||||||
|
BillingMode string `json:"billing_mode"`
|
||||||
|
InputPrice *float64 `json:"input_price"`
|
||||||
|
OutputPrice *float64 `json:"output_price"`
|
||||||
|
CacheWritePrice *float64 `json:"cache_write_price"`
|
||||||
|
CacheReadPrice *float64 `json:"cache_read_price"`
|
||||||
|
ImageOutputPrice *float64 `json:"image_output_price"`
|
||||||
|
PerRequestPrice *float64 `json:"per_request_price"`
|
||||||
|
Intervals []pricingIntervalResponse `json:"intervals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pricingIntervalResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
MinTokens int `json:"min_tokens"`
|
||||||
|
MaxTokens *int `json:"max_tokens"`
|
||||||
|
TierLabel string `json:"tier_label,omitempty"`
|
||||||
|
InputPrice *float64 `json:"input_price"`
|
||||||
|
OutputPrice *float64 `json:"output_price"`
|
||||||
|
CacheWritePrice *float64 `json:"cache_write_price"`
|
||||||
|
CacheReadPrice *float64 `json:"cache_read_price"`
|
||||||
|
PerRequestPrice *float64 `json:"per_request_price"`
|
||||||
|
SortOrder int `json:"sort_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelToResponse(ch *service.Channel) *channelResponse {
|
||||||
|
if ch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp := &channelResponse{
|
||||||
|
ID: ch.ID,
|
||||||
|
Name: ch.Name,
|
||||||
|
Description: ch.Description,
|
||||||
|
Status: ch.Status,
|
||||||
|
RestrictModels: ch.RestrictModels,
|
||||||
|
GroupIDs: ch.GroupIDs,
|
||||||
|
ModelMapping: ch.ModelMapping,
|
||||||
|
CreatedAt: ch.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
UpdatedAt: ch.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
}
|
||||||
|
resp.BillingModelSource = ch.BillingModelSource
|
||||||
|
if resp.BillingModelSource == "" {
|
||||||
|
resp.BillingModelSource = service.BillingModelSourceChannelMapped
|
||||||
|
}
|
||||||
|
if resp.GroupIDs == nil {
|
||||||
|
resp.GroupIDs = []int64{}
|
||||||
|
}
|
||||||
|
if resp.ModelMapping == nil {
|
||||||
|
resp.ModelMapping = map[string]map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.ModelPricing = make([]channelModelPricingResponse, 0, len(ch.ModelPricing))
|
||||||
|
for _, p := range ch.ModelPricing {
|
||||||
|
resp.ModelPricing = append(resp.ModelPricing, pricingToResponse(&p))
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricingToResponse(p *service.ChannelModelPricing) channelModelPricingResponse {
|
||||||
|
models := p.Models
|
||||||
|
if models == nil {
|
||||||
|
models = []string{}
|
||||||
|
}
|
||||||
|
billingMode := string(p.BillingMode)
|
||||||
|
if billingMode == "" {
|
||||||
|
billingMode = string(service.BillingModeToken)
|
||||||
|
}
|
||||||
|
platform := p.Platform
|
||||||
|
if platform == "" {
|
||||||
|
platform = service.PlatformAnthropic
|
||||||
|
}
|
||||||
|
intervals := make([]pricingIntervalResponse, 0, len(p.Intervals))
|
||||||
|
for _, iv := range p.Intervals {
|
||||||
|
intervals = append(intervals, intervalToResponse(iv))
|
||||||
|
}
|
||||||
|
return channelModelPricingResponse{
|
||||||
|
ID: p.ID,
|
||||||
|
Platform: platform,
|
||||||
|
Models: models,
|
||||||
|
BillingMode: billingMode,
|
||||||
|
InputPrice: p.InputPrice,
|
||||||
|
OutputPrice: p.OutputPrice,
|
||||||
|
CacheWritePrice: p.CacheWritePrice,
|
||||||
|
CacheReadPrice: p.CacheReadPrice,
|
||||||
|
ImageOutputPrice: p.ImageOutputPrice,
|
||||||
|
PerRequestPrice: p.PerRequestPrice,
|
||||||
|
Intervals: intervals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intervalToResponse(iv service.PricingInterval) pricingIntervalResponse {
|
||||||
|
return pricingIntervalResponse{
|
||||||
|
ID: iv.ID,
|
||||||
|
MinTokens: iv.MinTokens,
|
||||||
|
MaxTokens: iv.MaxTokens,
|
||||||
|
TierLabel: iv.TierLabel,
|
||||||
|
InputPrice: iv.InputPrice,
|
||||||
|
OutputPrice: iv.OutputPrice,
|
||||||
|
CacheWritePrice: iv.CacheWritePrice,
|
||||||
|
CacheReadPrice: iv.CacheReadPrice,
|
||||||
|
PerRequestPrice: iv.PerRequestPrice,
|
||||||
|
SortOrder: iv.SortOrder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricingRequestToService(reqs []channelModelPricingRequest) []service.ChannelModelPricing {
|
||||||
|
result := make([]service.ChannelModelPricing, 0, len(reqs))
|
||||||
|
for _, r := range reqs {
|
||||||
|
billingMode := service.BillingMode(r.BillingMode)
|
||||||
|
if billingMode == "" {
|
||||||
|
billingMode = service.BillingModeToken
|
||||||
|
}
|
||||||
|
platform := r.Platform
|
||||||
|
if platform == "" {
|
||||||
|
platform = service.PlatformAnthropic
|
||||||
|
}
|
||||||
|
intervals := make([]service.PricingInterval, 0, len(r.Intervals))
|
||||||
|
for _, iv := range r.Intervals {
|
||||||
|
intervals = append(intervals, service.PricingInterval{
|
||||||
|
MinTokens: iv.MinTokens,
|
||||||
|
MaxTokens: iv.MaxTokens,
|
||||||
|
TierLabel: iv.TierLabel,
|
||||||
|
InputPrice: iv.InputPrice,
|
||||||
|
OutputPrice: iv.OutputPrice,
|
||||||
|
CacheWritePrice: iv.CacheWritePrice,
|
||||||
|
CacheReadPrice: iv.CacheReadPrice,
|
||||||
|
PerRequestPrice: iv.PerRequestPrice,
|
||||||
|
SortOrder: iv.SortOrder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result = append(result, service.ChannelModelPricing{
|
||||||
|
Platform: platform,
|
||||||
|
Models: r.Models,
|
||||||
|
BillingMode: billingMode,
|
||||||
|
InputPrice: r.InputPrice,
|
||||||
|
OutputPrice: r.OutputPrice,
|
||||||
|
CacheWritePrice: r.CacheWritePrice,
|
||||||
|
CacheReadPrice: r.CacheReadPrice,
|
||||||
|
ImageOutputPrice: r.ImageOutputPrice,
|
||||||
|
PerRequestPrice: r.PerRequestPrice,
|
||||||
|
Intervals: intervals,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatePricingBillingMode 校验计费配置
|
||||||
|
func validatePricingBillingMode(pricing []service.ChannelModelPricing) error {
|
||||||
|
for _, p := range pricing {
|
||||||
|
// 按次/图片模式必须配置默认价格或区间
|
||||||
|
if p.BillingMode == service.BillingModePerRequest || p.BillingMode == service.BillingModeImage {
|
||||||
|
if p.PerRequestPrice == nil && len(p.Intervals) == 0 {
|
||||||
|
return errors.New("per-request price or intervals required for per_request/image billing mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 校验价格不能为负
|
||||||
|
if err := validatePriceNotNegative("input_price", p.InputPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validatePriceNotNegative("output_price", p.OutputPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validatePriceNotNegative("cache_write_price", p.CacheWritePrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validatePriceNotNegative("cache_read_price", p.CacheReadPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validatePriceNotNegative("image_output_price", p.ImageOutputPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validatePriceNotNegative("per_request_price", p.PerRequestPrice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 校验 interval:至少有一个价格字段非空
|
||||||
|
for _, iv := range p.Intervals {
|
||||||
|
if iv.InputPrice == nil && iv.OutputPrice == nil &&
|
||||||
|
iv.CacheWritePrice == nil && iv.CacheReadPrice == nil &&
|
||||||
|
iv.PerRequestPrice == nil {
|
||||||
|
return fmt.Errorf("interval [%d, %s] has no price fields set for model %v",
|
||||||
|
iv.MinTokens, formatMaxTokens(iv.MaxTokens), p.Models)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatePriceNotNegative(field string, val *float64) error {
|
||||||
|
if val != nil && *val < 0 {
|
||||||
|
return fmt.Errorf("%s must be >= 0", field)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMaxTokens(max *int) string {
|
||||||
|
if max == nil {
|
||||||
|
return "∞"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", *max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Handlers ---
|
||||||
|
|
||||||
|
// List handles listing channels with pagination
|
||||||
|
// GET /api/v1/admin/channels
|
||||||
|
func (h *ChannelHandler) List(c *gin.Context) {
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
status := c.Query("status")
|
||||||
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
if len(search) > 100 {
|
||||||
|
search = search[:100]
|
||||||
|
}
|
||||||
|
|
||||||
|
channels, pag, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{Page: page, PageSize: pageSize}, status, search)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]*channelResponse, 0, len(channels))
|
||||||
|
for i := range channels {
|
||||||
|
out = append(out, channelToResponse(&channels[i]))
|
||||||
|
}
|
||||||
|
response.Paginated(c, out, pag.Total, page, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID handles getting a channel by ID
|
||||||
|
// GET /api/v1/admin/channels/:id
|
||||||
|
func (h *ChannelHandler) GetByID(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_CHANNEL_ID", "Invalid channel ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := h.channelService.GetByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, channelToResponse(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handles creating a new channel
|
||||||
|
// POST /api/v1/admin/channels
|
||||||
|
func (h *ChannelHandler) Create(c *gin.Context) {
|
||||||
|
var req createChannelRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pricing := pricingRequestToService(req.ModelPricing)
|
||||||
|
if err := validatePricingBillingMode(pricing); err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := h.channelService.Create(c.Request.Context(), &service.CreateChannelInput{
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
GroupIDs: req.GroupIDs,
|
||||||
|
ModelPricing: pricing,
|
||||||
|
ModelMapping: req.ModelMapping,
|
||||||
|
BillingModelSource: req.BillingModelSource,
|
||||||
|
RestrictModels: req.RestrictModels,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, channelToResponse(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update handles updating a channel
|
||||||
|
// PUT /api/v1/admin/channels/:id
|
||||||
|
func (h *ChannelHandler) Update(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_CHANNEL_ID", "Invalid channel ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req updateChannelRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &service.UpdateChannelInput{
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
Status: req.Status,
|
||||||
|
GroupIDs: req.GroupIDs,
|
||||||
|
ModelMapping: req.ModelMapping,
|
||||||
|
BillingModelSource: req.BillingModelSource,
|
||||||
|
RestrictModels: req.RestrictModels,
|
||||||
|
}
|
||||||
|
if req.ModelPricing != nil {
|
||||||
|
pricing := pricingRequestToService(*req.ModelPricing)
|
||||||
|
if err := validatePricingBillingMode(pricing); err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
input.ModelPricing = &pricing
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := h.channelService.Update(c.Request.Context(), id, input)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, channelToResponse(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete handles deleting a channel
|
||||||
|
// DELETE /api/v1/admin/channels/:id
|
||||||
|
func (h *ChannelHandler) Delete(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_CHANNEL_ID", "Invalid channel ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.channelService.Delete(c.Request.Context(), id); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "Channel deleted successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModelDefaultPricing 获取模型的默认定价(用于前端自动填充)
|
||||||
|
// GET /api/v1/admin/channels/model-pricing?model=claude-sonnet-4
|
||||||
|
func (h *ChannelHandler) GetModelDefaultPricing(c *gin.Context) {
|
||||||
|
model := strings.TrimSpace(c.Query("model"))
|
||||||
|
if model == "" {
|
||||||
|
response.ErrorFrom(c, infraerrors.BadRequest("MISSING_PARAMETER", "model parameter is required").
|
||||||
|
WithMetadata(map[string]string{"param": "model"}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pricing, err := h.billingService.GetModelPricing(model)
|
||||||
|
if err != nil {
|
||||||
|
// 模型不在定价列表中
|
||||||
|
response.Success(c, gin.H{"found": false})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"found": true,
|
||||||
|
"input_price": pricing.InputPricePerToken,
|
||||||
|
"output_price": pricing.OutputPricePerToken,
|
||||||
|
"cache_write_price": pricing.CacheCreationPricePerToken,
|
||||||
|
"cache_read_price": pricing.CacheReadPricePerToken,
|
||||||
|
"image_output_price": pricing.ImageOutputPricePerToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
502
backend/internal/handler/admin/channel_handler_test.go
Normal file
502
backend/internal/handler/admin/channel_handler_test.go
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
//go:build unit
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func float64Ptr(v float64) *float64 { return &v }
|
||||||
|
func intPtr(v int) *int { return &v }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 1. channelToResponse
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestChannelToResponse_NilInput(t *testing.T) {
|
||||||
|
require.Nil(t, channelToResponse(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelToResponse_FullChannel(t *testing.T) {
|
||||||
|
now := time.Date(2025, 6, 1, 12, 0, 0, 0, time.UTC)
|
||||||
|
ch := &service.Channel{
|
||||||
|
ID: 42,
|
||||||
|
Name: "test-channel",
|
||||||
|
Description: "desc",
|
||||||
|
Status: "active",
|
||||||
|
BillingModelSource: "upstream",
|
||||||
|
RestrictModels: true,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now.Add(time.Hour),
|
||||||
|
GroupIDs: []int64{1, 2, 3},
|
||||||
|
ModelPricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
ID: 10,
|
||||||
|
Platform: "openai",
|
||||||
|
Models: []string{"gpt-4"},
|
||||||
|
BillingMode: service.BillingModeToken,
|
||||||
|
InputPrice: float64Ptr(0.01),
|
||||||
|
OutputPrice: float64Ptr(0.03),
|
||||||
|
CacheWritePrice: float64Ptr(0.005),
|
||||||
|
CacheReadPrice: float64Ptr(0.002),
|
||||||
|
PerRequestPrice: float64Ptr(0.5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ModelMapping: map[string]map[string]string{
|
||||||
|
"anthropic": {"claude-3-haiku": "claude-haiku-3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := channelToResponse(ch)
|
||||||
|
require.NotNil(t, resp)
|
||||||
|
require.Equal(t, int64(42), resp.ID)
|
||||||
|
require.Equal(t, "test-channel", resp.Name)
|
||||||
|
require.Equal(t, "desc", resp.Description)
|
||||||
|
require.Equal(t, "active", resp.Status)
|
||||||
|
require.Equal(t, "upstream", resp.BillingModelSource)
|
||||||
|
require.True(t, resp.RestrictModels)
|
||||||
|
require.Equal(t, []int64{1, 2, 3}, resp.GroupIDs)
|
||||||
|
require.Equal(t, "2025-06-01T12:00:00Z", resp.CreatedAt)
|
||||||
|
require.Equal(t, "2025-06-01T13:00:00Z", resp.UpdatedAt)
|
||||||
|
|
||||||
|
// model mapping
|
||||||
|
require.Len(t, resp.ModelMapping, 1)
|
||||||
|
require.Equal(t, "claude-haiku-3", resp.ModelMapping["anthropic"]["claude-3-haiku"])
|
||||||
|
|
||||||
|
// pricing
|
||||||
|
require.Len(t, resp.ModelPricing, 1)
|
||||||
|
p := resp.ModelPricing[0]
|
||||||
|
require.Equal(t, int64(10), p.ID)
|
||||||
|
require.Equal(t, "openai", p.Platform)
|
||||||
|
require.Equal(t, []string{"gpt-4"}, p.Models)
|
||||||
|
require.Equal(t, "token", p.BillingMode)
|
||||||
|
require.Equal(t, float64Ptr(0.01), p.InputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.03), p.OutputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.005), p.CacheWritePrice)
|
||||||
|
require.Equal(t, float64Ptr(0.002), p.CacheReadPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.5), p.PerRequestPrice)
|
||||||
|
require.Empty(t, p.Intervals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelToResponse_EmptyDefaults(t *testing.T) {
|
||||||
|
now := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
ch := &service.Channel{
|
||||||
|
ID: 1,
|
||||||
|
Name: "ch",
|
||||||
|
BillingModelSource: "",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
GroupIDs: nil,
|
||||||
|
ModelMapping: nil,
|
||||||
|
ModelPricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
Platform: "",
|
||||||
|
BillingMode: "",
|
||||||
|
Models: []string{"m1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := channelToResponse(ch)
|
||||||
|
require.Equal(t, "channel_mapped", resp.BillingModelSource)
|
||||||
|
require.NotNil(t, resp.GroupIDs)
|
||||||
|
require.Empty(t, resp.GroupIDs)
|
||||||
|
require.NotNil(t, resp.ModelMapping)
|
||||||
|
require.Empty(t, resp.ModelMapping)
|
||||||
|
|
||||||
|
require.Len(t, resp.ModelPricing, 1)
|
||||||
|
require.Equal(t, "anthropic", resp.ModelPricing[0].Platform)
|
||||||
|
require.Equal(t, "token", resp.ModelPricing[0].BillingMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelToResponse_NilModels(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
ch := &service.Channel{
|
||||||
|
ID: 1,
|
||||||
|
Name: "ch",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
ModelPricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
Models: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := channelToResponse(ch)
|
||||||
|
require.Len(t, resp.ModelPricing, 1)
|
||||||
|
require.NotNil(t, resp.ModelPricing[0].Models)
|
||||||
|
require.Empty(t, resp.ModelPricing[0].Models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelToResponse_WithIntervals(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
ch := &service.Channel{
|
||||||
|
ID: 1,
|
||||||
|
Name: "ch",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
ModelPricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
Models: []string{"m1"},
|
||||||
|
BillingMode: service.BillingModePerRequest,
|
||||||
|
Intervals: []service.PricingInterval{
|
||||||
|
{
|
||||||
|
ID: 100,
|
||||||
|
MinTokens: 0,
|
||||||
|
MaxTokens: intPtr(1000),
|
||||||
|
TierLabel: "1K",
|
||||||
|
InputPrice: float64Ptr(0.01),
|
||||||
|
OutputPrice: float64Ptr(0.02),
|
||||||
|
CacheWritePrice: float64Ptr(0.003),
|
||||||
|
CacheReadPrice: float64Ptr(0.001),
|
||||||
|
PerRequestPrice: float64Ptr(0.1),
|
||||||
|
SortOrder: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 101,
|
||||||
|
MinTokens: 1000,
|
||||||
|
MaxTokens: nil,
|
||||||
|
TierLabel: "unlimited",
|
||||||
|
SortOrder: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := channelToResponse(ch)
|
||||||
|
require.Len(t, resp.ModelPricing, 1)
|
||||||
|
intervals := resp.ModelPricing[0].Intervals
|
||||||
|
require.Len(t, intervals, 2)
|
||||||
|
|
||||||
|
iv0 := intervals[0]
|
||||||
|
require.Equal(t, int64(100), iv0.ID)
|
||||||
|
require.Equal(t, 0, iv0.MinTokens)
|
||||||
|
require.Equal(t, intPtr(1000), iv0.MaxTokens)
|
||||||
|
require.Equal(t, "1K", iv0.TierLabel)
|
||||||
|
require.Equal(t, float64Ptr(0.01), iv0.InputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.02), iv0.OutputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.003), iv0.CacheWritePrice)
|
||||||
|
require.Equal(t, float64Ptr(0.001), iv0.CacheReadPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.1), iv0.PerRequestPrice)
|
||||||
|
require.Equal(t, 1, iv0.SortOrder)
|
||||||
|
|
||||||
|
iv1 := intervals[1]
|
||||||
|
require.Equal(t, int64(101), iv1.ID)
|
||||||
|
require.Equal(t, 1000, iv1.MinTokens)
|
||||||
|
require.Nil(t, iv1.MaxTokens)
|
||||||
|
require.Equal(t, "unlimited", iv1.TierLabel)
|
||||||
|
require.Equal(t, 2, iv1.SortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelToResponse_MultipleEntries(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
ch := &service.Channel{
|
||||||
|
ID: 1,
|
||||||
|
Name: "multi",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
ModelPricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Platform: "anthropic",
|
||||||
|
Models: []string{"claude-sonnet-4"},
|
||||||
|
BillingMode: service.BillingModeToken,
|
||||||
|
InputPrice: float64Ptr(0.003),
|
||||||
|
OutputPrice: float64Ptr(0.015),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Platform: "openai",
|
||||||
|
Models: []string{"gpt-4", "gpt-4o"},
|
||||||
|
BillingMode: service.BillingModePerRequest,
|
||||||
|
PerRequestPrice: float64Ptr(1.0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Platform: "gemini",
|
||||||
|
Models: []string{"gemini-2.5-pro"},
|
||||||
|
BillingMode: service.BillingModeImage,
|
||||||
|
ImageOutputPrice: float64Ptr(0.05),
|
||||||
|
PerRequestPrice: float64Ptr(0.2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := channelToResponse(ch)
|
||||||
|
require.Len(t, resp.ModelPricing, 3)
|
||||||
|
|
||||||
|
require.Equal(t, int64(1), resp.ModelPricing[0].ID)
|
||||||
|
require.Equal(t, "anthropic", resp.ModelPricing[0].Platform)
|
||||||
|
require.Equal(t, []string{"claude-sonnet-4"}, resp.ModelPricing[0].Models)
|
||||||
|
require.Equal(t, "token", resp.ModelPricing[0].BillingMode)
|
||||||
|
|
||||||
|
require.Equal(t, int64(2), resp.ModelPricing[1].ID)
|
||||||
|
require.Equal(t, "openai", resp.ModelPricing[1].Platform)
|
||||||
|
require.Equal(t, []string{"gpt-4", "gpt-4o"}, resp.ModelPricing[1].Models)
|
||||||
|
require.Equal(t, "per_request", resp.ModelPricing[1].BillingMode)
|
||||||
|
|
||||||
|
require.Equal(t, int64(3), resp.ModelPricing[2].ID)
|
||||||
|
require.Equal(t, "gemini", resp.ModelPricing[2].Platform)
|
||||||
|
require.Equal(t, []string{"gemini-2.5-pro"}, resp.ModelPricing[2].Models)
|
||||||
|
require.Equal(t, "image", resp.ModelPricing[2].BillingMode)
|
||||||
|
require.Equal(t, float64Ptr(0.05), resp.ModelPricing[2].ImageOutputPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2. pricingRequestToService
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestPricingRequestToService_Defaults(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req channelModelPricingRequest
|
||||||
|
wantField string // which default field to check
|
||||||
|
wantValue string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty billing mode defaults to token",
|
||||||
|
req: channelModelPricingRequest{
|
||||||
|
Models: []string{"m1"},
|
||||||
|
BillingMode: "",
|
||||||
|
},
|
||||||
|
wantField: "BillingMode",
|
||||||
|
wantValue: string(service.BillingModeToken),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty platform defaults to anthropic",
|
||||||
|
req: channelModelPricingRequest{
|
||||||
|
Models: []string{"m1"},
|
||||||
|
Platform: "",
|
||||||
|
},
|
||||||
|
wantField: "Platform",
|
||||||
|
wantValue: "anthropic",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := pricingRequestToService([]channelModelPricingRequest{tt.req})
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
switch tt.wantField {
|
||||||
|
case "BillingMode":
|
||||||
|
require.Equal(t, service.BillingMode(tt.wantValue), result[0].BillingMode)
|
||||||
|
case "Platform":
|
||||||
|
require.Equal(t, tt.wantValue, result[0].Platform)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPricingRequestToService_WithAllFields(t *testing.T) {
|
||||||
|
reqs := []channelModelPricingRequest{
|
||||||
|
{
|
||||||
|
Platform: "openai",
|
||||||
|
Models: []string{"gpt-4", "gpt-4o"},
|
||||||
|
BillingMode: "per_request",
|
||||||
|
InputPrice: float64Ptr(0.01),
|
||||||
|
OutputPrice: float64Ptr(0.03),
|
||||||
|
CacheWritePrice: float64Ptr(0.005),
|
||||||
|
CacheReadPrice: float64Ptr(0.002),
|
||||||
|
ImageOutputPrice: float64Ptr(0.04),
|
||||||
|
PerRequestPrice: float64Ptr(0.5),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := pricingRequestToService(reqs)
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
r := result[0]
|
||||||
|
require.Equal(t, "openai", r.Platform)
|
||||||
|
require.Equal(t, []string{"gpt-4", "gpt-4o"}, r.Models)
|
||||||
|
require.Equal(t, service.BillingModePerRequest, r.BillingMode)
|
||||||
|
require.Equal(t, float64Ptr(0.01), r.InputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.03), r.OutputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.005), r.CacheWritePrice)
|
||||||
|
require.Equal(t, float64Ptr(0.002), r.CacheReadPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.04), r.ImageOutputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.5), r.PerRequestPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPricingRequestToService_WithIntervals(t *testing.T) {
|
||||||
|
reqs := []channelModelPricingRequest{
|
||||||
|
{
|
||||||
|
Models: []string{"m1"},
|
||||||
|
BillingMode: "per_request",
|
||||||
|
Intervals: []pricingIntervalRequest{
|
||||||
|
{
|
||||||
|
MinTokens: 0,
|
||||||
|
MaxTokens: intPtr(2000),
|
||||||
|
TierLabel: "small",
|
||||||
|
InputPrice: float64Ptr(0.01),
|
||||||
|
OutputPrice: float64Ptr(0.02),
|
||||||
|
CacheWritePrice: float64Ptr(0.003),
|
||||||
|
CacheReadPrice: float64Ptr(0.001),
|
||||||
|
PerRequestPrice: float64Ptr(0.1),
|
||||||
|
SortOrder: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MinTokens: 2000,
|
||||||
|
MaxTokens: nil,
|
||||||
|
TierLabel: "large",
|
||||||
|
SortOrder: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := pricingRequestToService(reqs)
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
require.Len(t, result[0].Intervals, 2)
|
||||||
|
|
||||||
|
iv0 := result[0].Intervals[0]
|
||||||
|
require.Equal(t, 0, iv0.MinTokens)
|
||||||
|
require.Equal(t, intPtr(2000), iv0.MaxTokens)
|
||||||
|
require.Equal(t, "small", iv0.TierLabel)
|
||||||
|
require.Equal(t, float64Ptr(0.01), iv0.InputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.02), iv0.OutputPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.003), iv0.CacheWritePrice)
|
||||||
|
require.Equal(t, float64Ptr(0.001), iv0.CacheReadPrice)
|
||||||
|
require.Equal(t, float64Ptr(0.1), iv0.PerRequestPrice)
|
||||||
|
require.Equal(t, 1, iv0.SortOrder)
|
||||||
|
|
||||||
|
iv1 := result[0].Intervals[1]
|
||||||
|
require.Equal(t, 2000, iv1.MinTokens)
|
||||||
|
require.Nil(t, iv1.MaxTokens)
|
||||||
|
require.Equal(t, "large", iv1.TierLabel)
|
||||||
|
require.Equal(t, 2, iv1.SortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPricingRequestToService_EmptySlice(t *testing.T) {
|
||||||
|
result := pricingRequestToService([]channelModelPricingRequest{})
|
||||||
|
require.NotNil(t, result)
|
||||||
|
require.Empty(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPricingRequestToService_NilPriceFields(t *testing.T) {
|
||||||
|
reqs := []channelModelPricingRequest{
|
||||||
|
{
|
||||||
|
Models: []string{"m1"},
|
||||||
|
BillingMode: "token",
|
||||||
|
// all price fields are nil by default
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := pricingRequestToService(reqs)
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
r := result[0]
|
||||||
|
require.Nil(t, r.InputPrice)
|
||||||
|
require.Nil(t, r.OutputPrice)
|
||||||
|
require.Nil(t, r.CacheWritePrice)
|
||||||
|
require.Nil(t, r.CacheReadPrice)
|
||||||
|
require.Nil(t, r.ImageOutputPrice)
|
||||||
|
require.Nil(t, r.PerRequestPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 3. validatePricingBillingMode
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestValidatePricingBillingMode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pricing []service.ChannelModelPricing
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "token mode - valid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{BillingMode: service.BillingModeToken},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "per_request with price - valid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModePerRequest,
|
||||||
|
PerRequestPrice: float64Ptr(0.5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "per_request with intervals - valid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModePerRequest,
|
||||||
|
Intervals: []service.PricingInterval{
|
||||||
|
{MinTokens: 0, MaxTokens: intPtr(1000), PerRequestPrice: float64Ptr(0.1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "per_request no price no intervals - invalid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{BillingMode: service.BillingModePerRequest},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "image with price - valid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModeImage,
|
||||||
|
PerRequestPrice: float64Ptr(0.2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "image no price no intervals - invalid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{BillingMode: service.BillingModeImage},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty list - valid",
|
||||||
|
pricing: []service.ChannelModelPricing{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed modes with invalid image - invalid",
|
||||||
|
pricing: []service.ChannelModelPricing{
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModeToken,
|
||||||
|
InputPrice: float64Ptr(0.01),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModePerRequest,
|
||||||
|
PerRequestPrice: float64Ptr(0.5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BillingMode: service.BillingModeImage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validatePricingBillingMode(tt.pricing)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "per-request price or intervals required")
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -249,11 +250,12 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, requestType, stream, billingType)
|
trend, hit, err := h.getUsageTrendCached(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, requestType, 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
|
||||||
}
|
}
|
||||||
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"trend": trend,
|
"trend": trend,
|
||||||
@@ -271,6 +273,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
|
|
||||||
// Parse optional filter params
|
// Parse optional filter params
|
||||||
var userID, apiKeyID, accountID, groupID int64
|
var userID, apiKeyID, accountID, groupID int64
|
||||||
|
modelSource := usagestats.ModelSourceRequested
|
||||||
var requestType *int16
|
var requestType *int16
|
||||||
var stream *bool
|
var stream *bool
|
||||||
var billingType *int8
|
var billingType *int8
|
||||||
@@ -295,6 +298,13 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
groupID = id
|
groupID = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rawModelSource := strings.TrimSpace(c.Query("model_source")); rawModelSource != "" {
|
||||||
|
if !usagestats.IsValidModelSource(rawModelSource) {
|
||||||
|
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelSource = rawModelSource
|
||||||
|
}
|
||||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -321,11 +331,12 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.dashboardService.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
stats, hit, err := h.getModelStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, modelSource, requestType, 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
|
||||||
}
|
}
|
||||||
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"models": stats,
|
"models": stats,
|
||||||
@@ -391,11 +402,12 @@ func (h *DashboardHandler) GetGroupStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.dashboardService.GetGroupStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
stats, hit, err := h.getGroupStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get group statistics")
|
response.Error(c, 500, "Failed to get group statistics")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"groups": stats,
|
"groups": stats,
|
||||||
@@ -416,11 +428,12 @@ func (h *DashboardHandler) GetAPIKeyUsageTrend(c *gin.Context) {
|
|||||||
limit = 5
|
limit = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.dashboardService.GetAPIKeyUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
trend, hit, err := h.getAPIKeyUsageTrendCached(c.Request.Context(), startTime, endTime, granularity, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get API key usage trend")
|
response.Error(c, 500, "Failed to get API key usage trend")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"trend": trend,
|
"trend": trend,
|
||||||
@@ -442,11 +455,12 @@ func (h *DashboardHandler) GetUserUsageTrend(c *gin.Context) {
|
|||||||
limit = 12
|
limit = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.dashboardService.GetUserUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
trend, hit, err := h.getUserUsageTrendCached(c.Request.Context(), startTime, endTime, granularity, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get user usage trend")
|
response.Error(c, 500, "Failed to get user usage trend")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"trend": trend,
|
"trend": trend,
|
||||||
@@ -461,9 +475,62 @@ type BatchUsersUsageRequest struct {
|
|||||||
UserIDs []int64 `json:"user_ids" binding:"required"`
|
UserIDs []int64 `json:"user_ids" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dashboardUsersRankingCache = newSnapshotCache(5 * time.Minute)
|
||||||
var dashboardBatchUsersUsageCache = newSnapshotCache(30 * time.Second)
|
var dashboardBatchUsersUsageCache = newSnapshotCache(30 * time.Second)
|
||||||
var dashboardBatchAPIKeysUsageCache = newSnapshotCache(30 * time.Second)
|
var dashboardBatchAPIKeysUsageCache = newSnapshotCache(30 * time.Second)
|
||||||
|
|
||||||
|
func parseRankingLimit(raw string) int {
|
||||||
|
limit, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||||
|
if err != nil || limit <= 0 {
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
if limit > 50 {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
return limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserSpendingRanking handles getting user spending ranking data.
|
||||||
|
// GET /api/v1/admin/dashboard/users-ranking
|
||||||
|
func (h *DashboardHandler) GetUserSpendingRanking(c *gin.Context) {
|
||||||
|
startTime, endTime := parseTimeRange(c)
|
||||||
|
limit := parseRankingLimit(c.DefaultQuery("limit", "12"))
|
||||||
|
|
||||||
|
keyRaw, _ := json.Marshal(struct {
|
||||||
|
Start string `json:"start"`
|
||||||
|
End string `json:"end"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}{
|
||||||
|
Start: startTime.UTC().Format(time.RFC3339),
|
||||||
|
End: endTime.UTC().Format(time.RFC3339),
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
cacheKey := string(keyRaw)
|
||||||
|
if cached, ok := dashboardUsersRankingCache.Get(cacheKey); ok {
|
||||||
|
c.Header("X-Snapshot-Cache", "hit")
|
||||||
|
response.Success(c, cached.Payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ranking, err := h.dashboardService.GetUserSpendingRanking(c.Request.Context(), startTime, endTime, limit)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, 500, "Failed to get user spending ranking")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := gin.H{
|
||||||
|
"ranking": ranking.Ranking,
|
||||||
|
"total_actual_cost": ranking.TotalActualCost,
|
||||||
|
"total_requests": ranking.TotalRequests,
|
||||||
|
"total_tokens": ranking.TotalTokens,
|
||||||
|
"start_date": startTime.Format("2006-01-02"),
|
||||||
|
"end_date": endTime.Add(-24 * time.Hour).Format("2006-01-02"),
|
||||||
|
}
|
||||||
|
dashboardUsersRankingCache.Set(cacheKey, payload)
|
||||||
|
c.Header("X-Snapshot-Cache", "miss")
|
||||||
|
response.Success(c, payload)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBatchUsersUsage handles getting usage stats for multiple users
|
// GetBatchUsersUsage handles getting usage stats for multiple users
|
||||||
// POST /api/v1/admin/dashboard/users-usage
|
// POST /api/v1/admin/dashboard/users-usage
|
||||||
func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
|
func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
|
||||||
@@ -546,3 +613,81 @@ func (h *DashboardHandler) GetBatchAPIKeysUsage(c *gin.Context) {
|
|||||||
c.Header("X-Snapshot-Cache", "miss")
|
c.Header("X-Snapshot-Cache", "miss")
|
||||||
response.Success(c, payload)
|
response.Success(c, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserBreakdown handles getting per-user usage breakdown within a dimension.
|
||||||
|
// GET /api/v1/admin/dashboard/user-breakdown
|
||||||
|
// Query params: start_date, end_date, group_id, model, endpoint, endpoint_type, limit
|
||||||
|
func (h *DashboardHandler) GetUserBreakdown(c *gin.Context) {
|
||||||
|
startTime, endTime := parseTimeRange(c)
|
||||||
|
|
||||||
|
dim := usagestats.UserBreakdownDimension{}
|
||||||
|
if v := c.Query("group_id"); v != "" {
|
||||||
|
if id, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
|
dim.GroupID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dim.Model = c.Query("model")
|
||||||
|
rawModelSource := strings.TrimSpace(c.DefaultQuery("model_source", usagestats.ModelSourceRequested))
|
||||||
|
if !usagestats.IsValidModelSource(rawModelSource) {
|
||||||
|
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dim.ModelType = rawModelSource
|
||||||
|
dim.Endpoint = c.Query("endpoint")
|
||||||
|
dim.EndpointType = c.DefaultQuery("endpoint_type", "inbound")
|
||||||
|
|
||||||
|
// Additional filter conditions
|
||||||
|
if v := c.Query("user_id"); v != "" {
|
||||||
|
if id, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
|
dim.UserID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := c.Query("api_key_id"); v != "" {
|
||||||
|
if id, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
|
dim.APIKeyID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := c.Query("account_id"); v != "" {
|
||||||
|
if id, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||||
|
dim.AccountID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := c.Query("request_type"); v != "" {
|
||||||
|
if rt, err := strconv.ParseInt(v, 10, 16); err == nil {
|
||||||
|
rtVal := int16(rt)
|
||||||
|
dim.RequestType = &rtVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := c.Query("stream"); v != "" {
|
||||||
|
if s, err := strconv.ParseBool(v); err == nil {
|
||||||
|
dim.Stream = &s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := c.Query("billing_type"); v != "" {
|
||||||
|
if bt, err := strconv.ParseInt(v, 10, 8); err == nil {
|
||||||
|
btVal := int8(bt)
|
||||||
|
dim.BillingType = &btVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 50
|
||||||
|
if v := c.Query("limit"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
|
||||||
|
limit = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := h.dashboardService.GetUserBreakdownStats(
|
||||||
|
c.Request.Context(), startTime, endTime, dim, limit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, 500, "Failed to get user breakdown stats")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"users": stats,
|
||||||
|
"start_date": startTime.Format("2006-01-02"),
|
||||||
|
"end_date": endTime.Add(-24 * time.Hour).Format("2006-01-02"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
118
backend/internal/handler/admin/dashboard_handler_cache_test.go
Normal file
118
backend/internal/handler/admin/dashboard_handler_cache_test.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dashboardUsageRepoCacheProbe struct {
|
||||||
|
service.UsageLogRepository
|
||||||
|
trendCalls atomic.Int32
|
||||||
|
usersTrendCalls atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dashboardUsageRepoCacheProbe) GetUsageTrendWithFilters(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
granularity string,
|
||||||
|
userID, apiKeyID, accountID, groupID int64,
|
||||||
|
model string,
|
||||||
|
requestType *int16,
|
||||||
|
stream *bool,
|
||||||
|
billingType *int8,
|
||||||
|
) ([]usagestats.TrendDataPoint, error) {
|
||||||
|
r.trendCalls.Add(1)
|
||||||
|
return []usagestats.TrendDataPoint{{
|
||||||
|
Date: "2026-03-11",
|
||||||
|
Requests: 1,
|
||||||
|
TotalTokens: 2,
|
||||||
|
Cost: 3,
|
||||||
|
ActualCost: 4,
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dashboardUsageRepoCacheProbe) GetUserUsageTrend(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
granularity string,
|
||||||
|
limit int,
|
||||||
|
) ([]usagestats.UserUsageTrendPoint, error) {
|
||||||
|
r.usersTrendCalls.Add(1)
|
||||||
|
return []usagestats.UserUsageTrendPoint{{
|
||||||
|
Date: "2026-03-11",
|
||||||
|
UserID: 1,
|
||||||
|
Email: "cache@test.dev",
|
||||||
|
Requests: 2,
|
||||||
|
Tokens: 20,
|
||||||
|
Cost: 2,
|
||||||
|
ActualCost: 1,
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetDashboardReadCachesForTest() {
|
||||||
|
dashboardTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardUsersTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardAPIKeysTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardModelStatsCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardGroupStatsCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardSnapshotV2Cache = newSnapshotCache(30 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDashboardHandler_GetUsageTrend_UsesCache(t *testing.T) {
|
||||||
|
t.Cleanup(resetDashboardReadCachesForTest)
|
||||||
|
resetDashboardReadCachesForTest()
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
repo := &dashboardUsageRepoCacheProbe{}
|
||||||
|
dashboardSvc := service.NewDashboardService(repo, nil, nil, nil)
|
||||||
|
handler := NewDashboardHandler(dashboardSvc, nil)
|
||||||
|
router := gin.New()
|
||||||
|
router.GET("/admin/dashboard/trend", handler.GetUsageTrend)
|
||||||
|
|
||||||
|
req1 := httptest.NewRequest(http.MethodGet, "/admin/dashboard/trend?start_date=2026-03-01&end_date=2026-03-07&granularity=day", nil)
|
||||||
|
rec1 := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec1, req1)
|
||||||
|
require.Equal(t, http.StatusOK, rec1.Code)
|
||||||
|
require.Equal(t, "miss", rec1.Header().Get("X-Snapshot-Cache"))
|
||||||
|
|
||||||
|
req2 := httptest.NewRequest(http.MethodGet, "/admin/dashboard/trend?start_date=2026-03-01&end_date=2026-03-07&granularity=day", nil)
|
||||||
|
rec2 := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec2, req2)
|
||||||
|
require.Equal(t, http.StatusOK, rec2.Code)
|
||||||
|
require.Equal(t, "hit", rec2.Header().Get("X-Snapshot-Cache"))
|
||||||
|
require.Equal(t, int32(1), repo.trendCalls.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDashboardHandler_GetUserUsageTrend_UsesCache(t *testing.T) {
|
||||||
|
t.Cleanup(resetDashboardReadCachesForTest)
|
||||||
|
resetDashboardReadCachesForTest()
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
repo := &dashboardUsageRepoCacheProbe{}
|
||||||
|
dashboardSvc := service.NewDashboardService(repo, nil, nil, nil)
|
||||||
|
handler := NewDashboardHandler(dashboardSvc, nil)
|
||||||
|
router := gin.New()
|
||||||
|
router.GET("/admin/dashboard/users-trend", handler.GetUserUsageTrend)
|
||||||
|
|
||||||
|
req1 := httptest.NewRequest(http.MethodGet, "/admin/dashboard/users-trend?start_date=2026-03-01&end_date=2026-03-07&granularity=day&limit=8", nil)
|
||||||
|
rec1 := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec1, req1)
|
||||||
|
require.Equal(t, http.StatusOK, rec1.Code)
|
||||||
|
require.Equal(t, "miss", rec1.Header().Get("X-Snapshot-Cache"))
|
||||||
|
|
||||||
|
req2 := httptest.NewRequest(http.MethodGet, "/admin/dashboard/users-trend?start_date=2026-03-01&end_date=2026-03-07&granularity=day&limit=8", nil)
|
||||||
|
rec2 := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec2, req2)
|
||||||
|
require.Equal(t, http.StatusOK, rec2.Code)
|
||||||
|
require.Equal(t, "hit", rec2.Header().Get("X-Snapshot-Cache"))
|
||||||
|
require.Equal(t, int32(1), repo.usersTrendCalls.Load())
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ type dashboardUsageRepoCapture struct {
|
|||||||
trendStream *bool
|
trendStream *bool
|
||||||
modelRequestType *int16
|
modelRequestType *int16
|
||||||
modelStream *bool
|
modelStream *bool
|
||||||
|
rankingLimit int
|
||||||
|
ranking []usagestats.UserSpendingRankingItem
|
||||||
|
rankingTotal float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dashboardUsageRepoCapture) GetUsageTrendWithFilters(
|
func (s *dashboardUsageRepoCapture) GetUsageTrendWithFilters(
|
||||||
@@ -49,6 +52,20 @@ func (s *dashboardUsageRepoCapture) GetModelStatsWithFilters(
|
|||||||
return []usagestats.ModelStat{}, nil
|
return []usagestats.ModelStat{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *dashboardUsageRepoCapture) GetUserSpendingRanking(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
limit int,
|
||||||
|
) (*usagestats.UserSpendingRankingResponse, error) {
|
||||||
|
s.rankingLimit = limit
|
||||||
|
return &usagestats.UserSpendingRankingResponse{
|
||||||
|
Ranking: s.ranking,
|
||||||
|
TotalActualCost: s.rankingTotal,
|
||||||
|
TotalRequests: 44,
|
||||||
|
TotalTokens: 1234,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newDashboardRequestTypeTestRouter(repo *dashboardUsageRepoCapture) *gin.Engine {
|
func newDashboardRequestTypeTestRouter(repo *dashboardUsageRepoCapture) *gin.Engine {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
dashboardSvc := service.NewDashboardService(repo, nil, nil, nil)
|
dashboardSvc := service.NewDashboardService(repo, nil, nil, nil)
|
||||||
@@ -56,6 +73,7 @@ func newDashboardRequestTypeTestRouter(repo *dashboardUsageRepoCapture) *gin.Eng
|
|||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.GET("/admin/dashboard/trend", handler.GetUsageTrend)
|
router.GET("/admin/dashboard/trend", handler.GetUsageTrend)
|
||||||
router.GET("/admin/dashboard/models", handler.GetModelStats)
|
router.GET("/admin/dashboard/models", handler.GetModelStats)
|
||||||
|
router.GET("/admin/dashboard/users-ranking", handler.GetUserSpendingRanking)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,3 +148,54 @@ func TestDashboardModelStatsInvalidStream(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDashboardModelStatsInvalidModelSource(t *testing.T) {
|
||||||
|
repo := &dashboardUsageRepoCapture{}
|
||||||
|
router := newDashboardRequestTypeTestRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/models?model_source=invalid", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDashboardModelStatsValidModelSource(t *testing.T) {
|
||||||
|
repo := &dashboardUsageRepoCapture{}
|
||||||
|
router := newDashboardRequestTypeTestRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/models?model_source=upstream", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDashboardUsersRankingLimitAndCache(t *testing.T) {
|
||||||
|
dashboardUsersRankingCache = newSnapshotCache(5 * time.Minute)
|
||||||
|
repo := &dashboardUsageRepoCapture{
|
||||||
|
ranking: []usagestats.UserSpendingRankingItem{
|
||||||
|
{UserID: 7, Email: "rank@example.com", ActualCost: 10.5, Requests: 3, Tokens: 300},
|
||||||
|
},
|
||||||
|
rankingTotal: 88.8,
|
||||||
|
}
|
||||||
|
router := newDashboardRequestTypeTestRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/users-ranking?limit=100&start_date=2025-01-01&end_date=2025-01-02", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
require.Equal(t, 50, repo.rankingLimit)
|
||||||
|
require.Contains(t, rec.Body.String(), "\"total_actual_cost\":88.8")
|
||||||
|
require.Contains(t, rec.Body.String(), "\"total_requests\":44")
|
||||||
|
require.Contains(t, rec.Body.String(), "\"total_tokens\":1234")
|
||||||
|
require.Equal(t, "miss", rec.Header().Get("X-Snapshot-Cache"))
|
||||||
|
|
||||||
|
req2 := httptest.NewRequest(http.MethodGet, "/admin/dashboard/users-ranking?limit=100&start_date=2025-01-01&end_date=2025-01-02", nil)
|
||||||
|
rec2 := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec2, req2)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, rec2.Code)
|
||||||
|
require.Equal(t, "hit", rec2.Header().Get("X-Snapshot-Cache"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- mock repo ---
|
||||||
|
|
||||||
|
type userBreakdownRepoCapture struct {
|
||||||
|
service.UsageLogRepository
|
||||||
|
capturedDim usagestats.UserBreakdownDimension
|
||||||
|
capturedLimit int
|
||||||
|
result []usagestats.UserBreakdownItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userBreakdownRepoCapture) GetUserBreakdownStats(
|
||||||
|
_ context.Context, _, _ time.Time,
|
||||||
|
dim usagestats.UserBreakdownDimension, limit int,
|
||||||
|
) ([]usagestats.UserBreakdownItem, error) {
|
||||||
|
r.capturedDim = dim
|
||||||
|
r.capturedLimit = limit
|
||||||
|
if r.result != nil {
|
||||||
|
return r.result, nil
|
||||||
|
}
|
||||||
|
return []usagestats.UserBreakdownItem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUserBreakdownRouter(repo *userBreakdownRepoCapture) *gin.Engine {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
svc := service.NewDashboardService(repo, nil, nil, nil)
|
||||||
|
h := NewDashboardHandler(svc, nil)
|
||||||
|
router := gin.New()
|
||||||
|
router.GET("/admin/dashboard/user-breakdown", h.GetUserBreakdown)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- tests ---
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_GroupIDFilter(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=42", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, int64(42), repo.capturedDim.GroupID)
|
||||||
|
require.Empty(t, repo.capturedDim.Model)
|
||||||
|
require.Empty(t, repo.capturedDim.Endpoint)
|
||||||
|
require.Equal(t, 50, repo.capturedLimit) // default limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_ModelFilter(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=claude-opus-4-6", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, "claude-opus-4-6", repo.capturedDim.Model)
|
||||||
|
require.Equal(t, usagestats.ModelSourceRequested, repo.capturedDim.ModelType)
|
||||||
|
require.Equal(t, int64(0), repo.capturedDim.GroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_ModelSourceFilter(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=claude-opus-4-6&model_source=upstream", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, usagestats.ModelSourceUpstream, repo.capturedDim.ModelType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_InvalidModelSource(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model_source=foobar", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_EndpointFilter(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&endpoint=/v1/messages&endpoint_type=upstream", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, "/v1/messages", repo.capturedDim.Endpoint)
|
||||||
|
require.Equal(t, "upstream", repo.capturedDim.EndpointType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_DefaultEndpointType(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&endpoint=/chat", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, "inbound", repo.capturedDim.EndpointType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_CustomLimit(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=test&limit=100", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, 100, repo.capturedLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_LimitClamped(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
// limit > 200 should fall back to default 50
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&model=test&limit=999", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, 50, repo.capturedLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_ResponseFormat(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{
|
||||||
|
result: []usagestats.UserBreakdownItem{
|
||||||
|
{UserID: 1, Email: "alice@test.com", Requests: 100, TotalTokens: 50000, Cost: 1.5, ActualCost: 1.2},
|
||||||
|
{UserID: 2, Email: "bob@test.com", Requests: 50, TotalTokens: 25000, Cost: 0.8, ActualCost: 0.6},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=1", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
Users []usagestats.UserBreakdownItem `json:"users"`
|
||||||
|
StartDate string `json:"start_date"`
|
||||||
|
EndDate string `json:"end_date"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, resp.Code)
|
||||||
|
require.Len(t, resp.Data.Users, 2)
|
||||||
|
require.Equal(t, int64(1), resp.Data.Users[0].UserID)
|
||||||
|
require.Equal(t, "alice@test.com", resp.Data.Users[0].Email)
|
||||||
|
require.Equal(t, int64(100), resp.Data.Users[0].Requests)
|
||||||
|
require.InDelta(t, 1.2, resp.Data.Users[0].ActualCost, 0.001)
|
||||||
|
require.Equal(t, "2026-03-01", resp.Data.StartDate)
|
||||||
|
require.Equal(t, "2026-03-16", resp.Data.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_EmptyResult(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&group_id=999", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Data struct {
|
||||||
|
Users []usagestats.UserBreakdownItem `json:"users"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, resp.Data.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserBreakdown_NoFilters(t *testing.T) {
|
||||||
|
repo := &userBreakdownRepoCapture{}
|
||||||
|
router := newUserBreakdownRouter(repo)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet,
|
||||||
|
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
require.Equal(t, int64(0), repo.capturedDim.GroupID)
|
||||||
|
require.Empty(t, repo.capturedDim.Model)
|
||||||
|
require.Empty(t, repo.capturedDim.Endpoint)
|
||||||
|
}
|
||||||
203
backend/internal/handler/admin/dashboard_query_cache.go
Normal file
203
backend/internal/handler/admin/dashboard_query_cache.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dashboardTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardModelStatsCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardGroupStatsCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardUsersTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
dashboardAPIKeysTrendCache = newSnapshotCache(30 * time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
type dashboardTrendCacheKey struct {
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
EndTime string `json:"end_time"`
|
||||||
|
Granularity string `json:"granularity"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
APIKeyID int64 `json:"api_key_id"`
|
||||||
|
AccountID int64 `json:"account_id"`
|
||||||
|
GroupID int64 `json:"group_id"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
RequestType *int16 `json:"request_type"`
|
||||||
|
Stream *bool `json:"stream"`
|
||||||
|
BillingType *int8 `json:"billing_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardModelGroupCacheKey struct {
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
EndTime string `json:"end_time"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
APIKeyID int64 `json:"api_key_id"`
|
||||||
|
AccountID int64 `json:"account_id"`
|
||||||
|
GroupID int64 `json:"group_id"`
|
||||||
|
ModelSource string `json:"model_source,omitempty"`
|
||||||
|
RequestType *int16 `json:"request_type"`
|
||||||
|
Stream *bool `json:"stream"`
|
||||||
|
BillingType *int8 `json:"billing_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardEntityTrendCacheKey struct {
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
EndTime string `json:"end_time"`
|
||||||
|
Granularity string `json:"granularity"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheStatusValue(hit bool) string {
|
||||||
|
if hit {
|
||||||
|
return "hit"
|
||||||
|
}
|
||||||
|
return "miss"
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustMarshalDashboardCacheKey(value any) string {
|
||||||
|
raw, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotPayloadAs[T any](payload any) (T, error) {
|
||||||
|
typed, ok := payload.(T)
|
||||||
|
if !ok {
|
||||||
|
var zero T
|
||||||
|
return zero, fmt.Errorf("unexpected cache payload type %T", payload)
|
||||||
|
}
|
||||||
|
return typed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) getUsageTrendCached(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
granularity string,
|
||||||
|
userID, apiKeyID, accountID, groupID int64,
|
||||||
|
model string,
|
||||||
|
requestType *int16,
|
||||||
|
stream *bool,
|
||||||
|
billingType *int8,
|
||||||
|
) ([]usagestats.TrendDataPoint, bool, error) {
|
||||||
|
key := mustMarshalDashboardCacheKey(dashboardTrendCacheKey{
|
||||||
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||||
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||||
|
Granularity: granularity,
|
||||||
|
UserID: userID,
|
||||||
|
APIKeyID: apiKeyID,
|
||||||
|
AccountID: accountID,
|
||||||
|
GroupID: groupID,
|
||||||
|
Model: model,
|
||||||
|
RequestType: requestType,
|
||||||
|
Stream: stream,
|
||||||
|
BillingType: billingType,
|
||||||
|
})
|
||||||
|
entry, hit, err := dashboardTrendCache.GetOrLoad(key, func() (any, error) {
|
||||||
|
return h.dashboardService.GetUsageTrendWithFilters(ctx, startTime, endTime, granularity, userID, apiKeyID, accountID, groupID, model, requestType, stream, billingType)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, hit, err
|
||||||
|
}
|
||||||
|
trend, err := snapshotPayloadAs[[]usagestats.TrendDataPoint](entry.Payload)
|
||||||
|
return trend, hit, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) getModelStatsCached(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
userID, apiKeyID, accountID, groupID int64,
|
||||||
|
modelSource string,
|
||||||
|
requestType *int16,
|
||||||
|
stream *bool,
|
||||||
|
billingType *int8,
|
||||||
|
) ([]usagestats.ModelStat, bool, error) {
|
||||||
|
key := mustMarshalDashboardCacheKey(dashboardModelGroupCacheKey{
|
||||||
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||||
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||||
|
UserID: userID,
|
||||||
|
APIKeyID: apiKeyID,
|
||||||
|
AccountID: accountID,
|
||||||
|
GroupID: groupID,
|
||||||
|
ModelSource: usagestats.NormalizeModelSource(modelSource),
|
||||||
|
RequestType: requestType,
|
||||||
|
Stream: stream,
|
||||||
|
BillingType: billingType,
|
||||||
|
})
|
||||||
|
entry, hit, err := dashboardModelStatsCache.GetOrLoad(key, func() (any, error) {
|
||||||
|
return h.dashboardService.GetModelStatsWithFiltersBySource(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType, modelSource)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, hit, err
|
||||||
|
}
|
||||||
|
stats, err := snapshotPayloadAs[[]usagestats.ModelStat](entry.Payload)
|
||||||
|
return stats, hit, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) getGroupStatsCached(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
userID, apiKeyID, accountID, groupID int64,
|
||||||
|
requestType *int16,
|
||||||
|
stream *bool,
|
||||||
|
billingType *int8,
|
||||||
|
) ([]usagestats.GroupStat, bool, error) {
|
||||||
|
key := mustMarshalDashboardCacheKey(dashboardModelGroupCacheKey{
|
||||||
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||||
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||||
|
UserID: userID,
|
||||||
|
APIKeyID: apiKeyID,
|
||||||
|
AccountID: accountID,
|
||||||
|
GroupID: groupID,
|
||||||
|
RequestType: requestType,
|
||||||
|
Stream: stream,
|
||||||
|
BillingType: billingType,
|
||||||
|
})
|
||||||
|
entry, hit, err := dashboardGroupStatsCache.GetOrLoad(key, func() (any, error) {
|
||||||
|
return h.dashboardService.GetGroupStatsWithFilters(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, hit, err
|
||||||
|
}
|
||||||
|
stats, err := snapshotPayloadAs[[]usagestats.GroupStat](entry.Payload)
|
||||||
|
return stats, hit, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) getAPIKeyUsageTrendCached(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.APIKeyUsageTrendPoint, bool, error) {
|
||||||
|
key := mustMarshalDashboardCacheKey(dashboardEntityTrendCacheKey{
|
||||||
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||||
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||||
|
Granularity: granularity,
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
entry, hit, err := dashboardAPIKeysTrendCache.GetOrLoad(key, func() (any, error) {
|
||||||
|
return h.dashboardService.GetAPIKeyUsageTrend(ctx, startTime, endTime, granularity, limit)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, hit, err
|
||||||
|
}
|
||||||
|
trend, err := snapshotPayloadAs[[]usagestats.APIKeyUsageTrendPoint](entry.Payload)
|
||||||
|
return trend, hit, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) getUserUsageTrendCached(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.UserUsageTrendPoint, bool, error) {
|
||||||
|
key := mustMarshalDashboardCacheKey(dashboardEntityTrendCacheKey{
|
||||||
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
||||||
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
||||||
|
Granularity: granularity,
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
entry, hit, err := dashboardUsersTrendCache.GetOrLoad(key, func() (any, error) {
|
||||||
|
return h.dashboardService.GetUserUsageTrend(ctx, startTime, endTime, granularity, limit)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, hit, err
|
||||||
|
}
|
||||||
|
trend, err := snapshotPayloadAs[[]usagestats.UserUsageTrendPoint](entry.Payload)
|
||||||
|
return trend, hit, err
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -111,7 +113,25 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
cacheKey := string(keyRaw)
|
cacheKey := string(keyRaw)
|
||||||
|
|
||||||
if cached, ok := dashboardSnapshotV2Cache.Get(cacheKey); ok {
|
cached, hit, err := dashboardSnapshotV2Cache.GetOrLoad(cacheKey, func() (any, error) {
|
||||||
|
return h.buildSnapshotV2Response(
|
||||||
|
c.Request.Context(),
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
granularity,
|
||||||
|
filters,
|
||||||
|
includeStats,
|
||||||
|
includeTrend,
|
||||||
|
includeModels,
|
||||||
|
includeGroups,
|
||||||
|
includeUsersTrend,
|
||||||
|
usersTrendLimit,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, 500, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if cached.ETag != "" {
|
if cached.ETag != "" {
|
||||||
c.Header("ETag", cached.ETag)
|
c.Header("ETag", cached.ETag)
|
||||||
c.Header("Vary", "If-None-Match")
|
c.Header("Vary", "If-None-Match")
|
||||||
@@ -120,11 +140,18 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Header("X-Snapshot-Cache", "hit")
|
c.Header("X-Snapshot-Cache", cacheStatusValue(hit))
|
||||||
response.Success(c, cached.Payload)
|
response.Success(c, cached.Payload)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) buildSnapshotV2Response(
|
||||||
|
ctx context.Context,
|
||||||
|
startTime, endTime time.Time,
|
||||||
|
granularity string,
|
||||||
|
filters *dashboardSnapshotV2Filters,
|
||||||
|
includeStats, includeTrend, includeModels, includeGroups, includeUsersTrend bool,
|
||||||
|
usersTrendLimit int,
|
||||||
|
) (*dashboardSnapshotV2Response, error) {
|
||||||
resp := &dashboardSnapshotV2Response{
|
resp := &dashboardSnapshotV2Response{
|
||||||
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
||||||
StartDate: startTime.Format("2006-01-02"),
|
StartDate: startTime.Format("2006-01-02"),
|
||||||
@@ -133,10 +160,9 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if includeStats {
|
if includeStats {
|
||||||
stats, err := h.dashboardService.GetDashboardStats(c.Request.Context())
|
stats, err := h.dashboardService.GetDashboardStats(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get dashboard statistics")
|
return nil, errors.New("failed to get dashboard statistics")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
resp.Stats = &dashboardSnapshotV2Stats{
|
resp.Stats = &dashboardSnapshotV2Stats{
|
||||||
DashboardStats: *stats,
|
DashboardStats: *stats,
|
||||||
@@ -145,8 +171,8 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if includeTrend {
|
if includeTrend {
|
||||||
trend, err := h.dashboardService.GetUsageTrendWithFilters(
|
trend, _, err := h.getUsageTrendCached(
|
||||||
c.Request.Context(),
|
ctx,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
granularity,
|
granularity,
|
||||||
@@ -160,35 +186,34 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
filters.BillingType,
|
filters.BillingType,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get usage trend")
|
return nil, errors.New("failed to get usage trend")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
resp.Trend = trend
|
resp.Trend = trend
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeModels {
|
if includeModels {
|
||||||
models, err := h.dashboardService.GetModelStatsWithFilters(
|
models, _, err := h.getModelStatsCached(
|
||||||
c.Request.Context(),
|
ctx,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
filters.UserID,
|
filters.UserID,
|
||||||
filters.APIKeyID,
|
filters.APIKeyID,
|
||||||
filters.AccountID,
|
filters.AccountID,
|
||||||
filters.GroupID,
|
filters.GroupID,
|
||||||
|
usagestats.ModelSourceRequested,
|
||||||
filters.RequestType,
|
filters.RequestType,
|
||||||
filters.Stream,
|
filters.Stream,
|
||||||
filters.BillingType,
|
filters.BillingType,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get model statistics")
|
return nil, errors.New("failed to get model statistics")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
resp.Models = models
|
resp.Models = models
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeGroups {
|
if includeGroups {
|
||||||
groups, err := h.dashboardService.GetGroupStatsWithFilters(
|
groups, _, err := h.getGroupStatsCached(
|
||||||
c.Request.Context(),
|
ctx,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
filters.UserID,
|
filters.UserID,
|
||||||
@@ -200,34 +225,20 @@ func (h *DashboardHandler) GetSnapshotV2(c *gin.Context) {
|
|||||||
filters.BillingType,
|
filters.BillingType,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get group statistics")
|
return nil, errors.New("failed to get group statistics")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
resp.Groups = groups
|
resp.Groups = groups
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeUsersTrend {
|
if includeUsersTrend {
|
||||||
usersTrend, err := h.dashboardService.GetUserUsageTrend(
|
usersTrend, _, err := h.getUserUsageTrendCached(ctx, startTime, endTime, granularity, usersTrendLimit)
|
||||||
c.Request.Context(),
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
granularity,
|
|
||||||
usersTrendLimit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get user usage trend")
|
return nil, errors.New("failed to get user usage trend")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
resp.UsersTrend = usersTrend
|
resp.UsersTrend = usersTrend
|
||||||
}
|
}
|
||||||
|
|
||||||
cached := dashboardSnapshotV2Cache.Set(cacheKey, resp)
|
return resp, nil
|
||||||
if cached.ETag != "" {
|
|
||||||
c.Header("ETag", cached.ETag)
|
|
||||||
c.Header("Vary", "If-None-Match")
|
|
||||||
}
|
|
||||||
c.Header("X-Snapshot-Cache", "miss")
|
|
||||||
response.Success(c, resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDashboardSnapshotV2Filters(c *gin.Context) (*dashboardSnapshotV2Filters, error) {
|
func parseDashboardSnapshotV2Filters(c *gin.Context) (*dashboardSnapshotV2Filters, error) {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -14,12 +18,65 @@ import (
|
|||||||
// GroupHandler handles admin group management
|
// GroupHandler handles admin group management
|
||||||
type GroupHandler struct {
|
type GroupHandler struct {
|
||||||
adminService service.AdminService
|
adminService service.AdminService
|
||||||
|
dashboardService *service.DashboardService
|
||||||
|
groupCapacityService *service.GroupCapacityService
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalLimitField struct {
|
||||||
|
set bool
|
||||||
|
value *float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *optionalLimitField) UnmarshalJSON(data []byte) error {
|
||||||
|
f.set = true
|
||||||
|
|
||||||
|
trimmed := bytes.TrimSpace(data)
|
||||||
|
if bytes.Equal(trimmed, []byte("null")) {
|
||||||
|
f.value = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var number float64
|
||||||
|
if err := json.Unmarshal(trimmed, &number); err == nil {
|
||||||
|
f.value = &number
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var text string
|
||||||
|
if err := json.Unmarshal(trimmed, &text); err == nil {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
f.value = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
number, err = strconv.ParseFloat(text, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid numeric limit value %q: %w", text, err)
|
||||||
|
}
|
||||||
|
f.value = &number
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("invalid limit value: %s", string(trimmed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f optionalLimitField) ToServiceInput() *float64 {
|
||||||
|
if !f.set {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.value != nil {
|
||||||
|
return f.value
|
||||||
|
}
|
||||||
|
zero := 0.0
|
||||||
|
return &zero
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGroupHandler creates a new admin group handler
|
// NewGroupHandler creates a new admin group handler
|
||||||
func NewGroupHandler(adminService service.AdminService) *GroupHandler {
|
func NewGroupHandler(adminService service.AdminService, dashboardService *service.DashboardService, groupCapacityService *service.GroupCapacityService) *GroupHandler {
|
||||||
return &GroupHandler{
|
return &GroupHandler{
|
||||||
adminService: adminService,
|
adminService: adminService,
|
||||||
|
dashboardService: dashboardService,
|
||||||
|
groupCapacityService: groupCapacityService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +88,9 @@ type CreateGroupRequest struct {
|
|||||||
RateMultiplier float64 `json:"rate_multiplier"`
|
RateMultiplier float64 `json:"rate_multiplier"`
|
||||||
IsExclusive bool `json:"is_exclusive"`
|
IsExclusive bool `json:"is_exclusive"`
|
||||||
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
|
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
|
||||||
DailyLimitUSD *float64 `json:"daily_limit_usd"`
|
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
|
||||||
WeeklyLimitUSD *float64 `json:"weekly_limit_usd"`
|
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
|
||||||
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
|
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
|
||||||
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
|
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
|
||||||
ImagePrice1K *float64 `json:"image_price_1k"`
|
ImagePrice1K *float64 `json:"image_price_1k"`
|
||||||
ImagePrice2K *float64 `json:"image_price_2k"`
|
ImagePrice2K *float64 `json:"image_price_2k"`
|
||||||
@@ -53,6 +110,11 @@ type CreateGroupRequest struct {
|
|||||||
SupportedModelScopes []string `json:"supported_model_scopes"`
|
SupportedModelScopes []string `json:"supported_model_scopes"`
|
||||||
// Sora 存储配额
|
// Sora 存储配额
|
||||||
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes"`
|
SoraStorageQuotaBytes int64 `json:"sora_storage_quota_bytes"`
|
||||||
|
// OpenAI Messages 调度配置(仅 openai 平台使用)
|
||||||
|
AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
|
||||||
|
RequireOAuthOnly bool `json:"require_oauth_only"`
|
||||||
|
RequirePrivacySet bool `json:"require_privacy_set"`
|
||||||
|
DefaultMappedModel string `json:"default_mapped_model"`
|
||||||
// 从指定分组复制账号(创建后自动绑定)
|
// 从指定分组复制账号(创建后自动绑定)
|
||||||
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
||||||
}
|
}
|
||||||
@@ -66,9 +128,9 @@ type UpdateGroupRequest struct {
|
|||||||
IsExclusive *bool `json:"is_exclusive"`
|
IsExclusive *bool `json:"is_exclusive"`
|
||||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||||
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
|
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
|
||||||
DailyLimitUSD *float64 `json:"daily_limit_usd"`
|
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
|
||||||
WeeklyLimitUSD *float64 `json:"weekly_limit_usd"`
|
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
|
||||||
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
|
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
|
||||||
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
|
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
|
||||||
ImagePrice1K *float64 `json:"image_price_1k"`
|
ImagePrice1K *float64 `json:"image_price_1k"`
|
||||||
ImagePrice2K *float64 `json:"image_price_2k"`
|
ImagePrice2K *float64 `json:"image_price_2k"`
|
||||||
@@ -88,6 +150,11 @@ type UpdateGroupRequest struct {
|
|||||||
SupportedModelScopes *[]string `json:"supported_model_scopes"`
|
SupportedModelScopes *[]string `json:"supported_model_scopes"`
|
||||||
// Sora 存储配额
|
// Sora 存储配额
|
||||||
SoraStorageQuotaBytes *int64 `json:"sora_storage_quota_bytes"`
|
SoraStorageQuotaBytes *int64 `json:"sora_storage_quota_bytes"`
|
||||||
|
// OpenAI Messages 调度配置(仅 openai 平台使用)
|
||||||
|
AllowMessagesDispatch *bool `json:"allow_messages_dispatch"`
|
||||||
|
RequireOAuthOnly *bool `json:"require_oauth_only"`
|
||||||
|
RequirePrivacySet *bool `json:"require_privacy_set"`
|
||||||
|
DefaultMappedModel *string `json:"default_mapped_model"`
|
||||||
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
|
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
|
||||||
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
|
||||||
}
|
}
|
||||||
@@ -185,9 +252,9 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
|||||||
RateMultiplier: req.RateMultiplier,
|
RateMultiplier: req.RateMultiplier,
|
||||||
IsExclusive: req.IsExclusive,
|
IsExclusive: req.IsExclusive,
|
||||||
SubscriptionType: req.SubscriptionType,
|
SubscriptionType: req.SubscriptionType,
|
||||||
DailyLimitUSD: req.DailyLimitUSD,
|
DailyLimitUSD: req.DailyLimitUSD.ToServiceInput(),
|
||||||
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(),
|
||||||
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(),
|
||||||
ImagePrice1K: req.ImagePrice1K,
|
ImagePrice1K: req.ImagePrice1K,
|
||||||
ImagePrice2K: req.ImagePrice2K,
|
ImagePrice2K: req.ImagePrice2K,
|
||||||
ImagePrice4K: req.ImagePrice4K,
|
ImagePrice4K: req.ImagePrice4K,
|
||||||
@@ -203,6 +270,10 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
|||||||
MCPXMLInject: req.MCPXMLInject,
|
MCPXMLInject: req.MCPXMLInject,
|
||||||
SupportedModelScopes: req.SupportedModelScopes,
|
SupportedModelScopes: req.SupportedModelScopes,
|
||||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||||
|
AllowMessagesDispatch: req.AllowMessagesDispatch,
|
||||||
|
RequireOAuthOnly: req.RequireOAuthOnly,
|
||||||
|
RequirePrivacySet: req.RequirePrivacySet,
|
||||||
|
DefaultMappedModel: req.DefaultMappedModel,
|
||||||
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,9 +307,9 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
|||||||
IsExclusive: req.IsExclusive,
|
IsExclusive: req.IsExclusive,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
SubscriptionType: req.SubscriptionType,
|
SubscriptionType: req.SubscriptionType,
|
||||||
DailyLimitUSD: req.DailyLimitUSD,
|
DailyLimitUSD: req.DailyLimitUSD.ToServiceInput(),
|
||||||
WeeklyLimitUSD: req.WeeklyLimitUSD,
|
WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(),
|
||||||
MonthlyLimitUSD: req.MonthlyLimitUSD,
|
MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(),
|
||||||
ImagePrice1K: req.ImagePrice1K,
|
ImagePrice1K: req.ImagePrice1K,
|
||||||
ImagePrice2K: req.ImagePrice2K,
|
ImagePrice2K: req.ImagePrice2K,
|
||||||
ImagePrice4K: req.ImagePrice4K,
|
ImagePrice4K: req.ImagePrice4K,
|
||||||
@@ -254,6 +325,10 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
|||||||
MCPXMLInject: req.MCPXMLInject,
|
MCPXMLInject: req.MCPXMLInject,
|
||||||
SupportedModelScopes: req.SupportedModelScopes,
|
SupportedModelScopes: req.SupportedModelScopes,
|
||||||
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
SoraStorageQuotaBytes: req.SoraStorageQuotaBytes,
|
||||||
|
AllowMessagesDispatch: req.AllowMessagesDispatch,
|
||||||
|
RequireOAuthOnly: req.RequireOAuthOnly,
|
||||||
|
RequirePrivacySet: req.RequirePrivacySet,
|
||||||
|
DefaultMappedModel: req.DefaultMappedModel,
|
||||||
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -301,6 +376,33 @@ func (h *GroupHandler) GetStats(c *gin.Context) {
|
|||||||
_ = groupID // TODO: implement actual stats
|
_ = groupID // TODO: implement actual stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsageSummary returns today's and cumulative cost for all groups.
|
||||||
|
// GET /api/v1/admin/groups/usage-summary?timezone=Asia/Shanghai
|
||||||
|
func (h *GroupHandler) GetUsageSummary(c *gin.Context) {
|
||||||
|
userTZ := c.Query("timezone")
|
||||||
|
now := timezone.NowInUserLocation(userTZ)
|
||||||
|
todayStart := timezone.StartOfDayInUserLocation(now, userTZ)
|
||||||
|
|
||||||
|
results, err := h.dashboardService.GetGroupUsageSummary(c.Request.Context(), todayStart)
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, 500, "Failed to get group usage summary")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCapacitySummary returns aggregated capacity (concurrency/sessions/RPM) for all active groups.
|
||||||
|
// GET /api/v1/admin/groups/capacity-summary
|
||||||
|
func (h *GroupHandler) GetCapacitySummary(c *gin.Context) {
|
||||||
|
results, err := h.groupCapacityService.GetAllGroupCapacity(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.Error(c, 500, "Failed to get group capacity summary")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, results)
|
||||||
|
}
|
||||||
|
|
||||||
// GetGroupAPIKeys handles getting API keys in a group
|
// GetGroupAPIKeys handles getting API keys in a group
|
||||||
// GET /api/v1/admin/groups/:id/api-keys
|
// GET /api/v1/admin/groups/:id/api-keys
|
||||||
func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) {
|
func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) {
|
||||||
@@ -325,6 +427,72 @@ func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) {
|
|||||||
response.Paginated(c, outKeys, total, page, pageSize)
|
response.Paginated(c, outKeys, total, page, pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGroupRateMultipliers handles getting rate multipliers for users in a group
|
||||||
|
// GET /api/v1/admin/groups/:id/rate-multipliers
|
||||||
|
func (h *GroupHandler) GetGroupRateMultipliers(c *gin.Context) {
|
||||||
|
groupID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid group ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := h.adminService.GetGroupRateMultipliers(c.Request.Context(), groupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if entries == nil {
|
||||||
|
entries = []service.UserGroupRateEntry{}
|
||||||
|
}
|
||||||
|
response.Success(c, entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearGroupRateMultipliers handles clearing all rate multipliers for a group
|
||||||
|
// DELETE /api/v1/admin/groups/:id/rate-multipliers
|
||||||
|
func (h *GroupHandler) ClearGroupRateMultipliers(c *gin.Context) {
|
||||||
|
groupID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid group ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.adminService.ClearGroupRateMultipliers(c.Request.Context(), groupID); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "Rate multipliers cleared successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSetGroupRateMultipliersRequest represents batch set rate multipliers request
|
||||||
|
type BatchSetGroupRateMultipliersRequest struct {
|
||||||
|
Entries []service.GroupRateMultiplierInput `json:"entries" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSetGroupRateMultipliers handles batch setting rate multipliers for a group
|
||||||
|
// PUT /api/v1/admin/groups/:id/rate-multipliers
|
||||||
|
func (h *GroupHandler) BatchSetGroupRateMultipliers(c *gin.Context) {
|
||||||
|
groupID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid group ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req BatchSetGroupRateMultipliersRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.adminService.BatchSetGroupRateMultipliers(c.Request.Context(), groupID, req.Entries); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "Rate multipliers updated successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateSortOrderRequest represents the request to update group sort orders
|
// UpdateSortOrderRequest represents the request to update group sort orders
|
||||||
type UpdateSortOrderRequest struct {
|
type UpdateSortOrderRequest struct {
|
||||||
Updates []struct {
|
Updates []struct {
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ func (h *OpenAIOAuthHandler) CreateAccountFromOAuth(c *gin.Context) {
|
|||||||
Platform: platform,
|
Platform: platform,
|
||||||
Type: "oauth",
|
Type: "oauth",
|
||||||
Credentials: credentials,
|
Credentials: credentials,
|
||||||
|
Extra: nil,
|
||||||
ProxyID: req.ProxyID,
|
ProxyID: req.ProxyID,
|
||||||
Concurrency: req.Concurrency,
|
Concurrency: req.Concurrency,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ var validOpsAlertMetricTypes = []string{
|
|||||||
"cpu_usage_percent",
|
"cpu_usage_percent",
|
||||||
"memory_usage_percent",
|
"memory_usage_percent",
|
||||||
"concurrency_queue_depth",
|
"concurrency_queue_depth",
|
||||||
|
"group_available_accounts",
|
||||||
|
"group_available_ratio",
|
||||||
|
"group_rate_limit_ratio",
|
||||||
|
"account_rate_limited_count",
|
||||||
|
"account_error_count",
|
||||||
|
"account_error_ratio",
|
||||||
|
"overload_account_count",
|
||||||
}
|
}
|
||||||
|
|
||||||
var validOpsAlertMetricTypeSet = func() map[string]struct{} {
|
var validOpsAlertMetricTypeSet = func() map[string]struct{} {
|
||||||
@@ -82,7 +89,10 @@ func isPercentOrRateMetric(metricType string) bool {
|
|||||||
"error_rate",
|
"error_rate",
|
||||||
"upstream_error_rate",
|
"upstream_error_rate",
|
||||||
"cpu_usage_percent",
|
"cpu_usage_percent",
|
||||||
"memory_usage_percent":
|
"memory_usage_percent",
|
||||||
|
"group_available_ratio",
|
||||||
|
"group_rate_limit_ratio",
|
||||||
|
"account_error_ratio":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -35,17 +35,20 @@ func NewRedeemHandler(adminService service.AdminService, redeemService *service.
|
|||||||
type GenerateRedeemCodesRequest struct {
|
type GenerateRedeemCodesRequest struct {
|
||||||
Count int `json:"count" binding:"required,min=1,max=100"`
|
Count int `json:"count" binding:"required,min=1,max=100"`
|
||||||
Type string `json:"type" binding:"required,oneof=balance concurrency subscription invitation"`
|
Type string `json:"type" binding:"required,oneof=balance concurrency subscription invitation"`
|
||||||
Value float64 `json:"value" binding:"min=0"`
|
Value float64 `json:"value"`
|
||||||
GroupID *int64 `json:"group_id"` // 订阅类型必填
|
GroupID *int64 `json:"group_id"` // 订阅类型必填
|
||||||
ValidityDays int `json:"validity_days" binding:"omitempty,max=36500"` // 订阅类型使用,默认30天,最大100年
|
ValidityDays int `json:"validity_days"` // 订阅类型使用,正数增加/负数退款扣减
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAndRedeemCodeRequest represents creating a fixed code and redeeming it for a target user.
|
// CreateAndRedeemCodeRequest represents creating a fixed code and redeeming it for a target user.
|
||||||
|
// Type 为 omitempty 而非 required 是为了向后兼容旧版调用方(不传 type 时默认 balance)。
|
||||||
type CreateAndRedeemCodeRequest struct {
|
type CreateAndRedeemCodeRequest struct {
|
||||||
Code string `json:"code" binding:"required,min=3,max=128"`
|
Code string `json:"code" binding:"required,min=3,max=128"`
|
||||||
Type string `json:"type" binding:"required,oneof=balance concurrency subscription invitation"`
|
Type string `json:"type" binding:"omitempty,oneof=balance concurrency subscription invitation"` // 不传时默认 balance(向后兼容)
|
||||||
Value float64 `json:"value" binding:"required,gt=0"`
|
Value float64 `json:"value" binding:"required"`
|
||||||
UserID int64 `json:"user_id" binding:"required,gt=0"`
|
UserID int64 `json:"user_id" binding:"required,gt=0"`
|
||||||
|
GroupID *int64 `json:"group_id"` // subscription 类型必填
|
||||||
|
ValidityDays int `json:"validity_days"` // subscription 类型:正数增加,负数退款扣减
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +139,22 @@ func (h *RedeemHandler) CreateAndRedeem(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Code = strings.TrimSpace(req.Code)
|
req.Code = strings.TrimSpace(req.Code)
|
||||||
|
// 向后兼容:旧版调用方(如 Sub2ApiPay)不传 type 字段,默认当作 balance 充值处理。
|
||||||
|
// 请勿删除此默认值逻辑,否则会导致旧版调用方 400 报错。
|
||||||
|
if req.Type == "" {
|
||||||
|
req.Type = "balance"
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == "subscription" {
|
||||||
|
if req.GroupID == nil {
|
||||||
|
response.BadRequest(c, "group_id is required for subscription type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.ValidityDays == 0 {
|
||||||
|
response.BadRequest(c, "validity_days must not be zero for subscription type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
executeAdminIdempotentJSON(c, "admin.redeem_codes.create_and_redeem", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
executeAdminIdempotentJSON(c, "admin.redeem_codes.create_and_redeem", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
||||||
existing, err := h.redeemService.GetByCode(ctx, req.Code)
|
existing, err := h.redeemService.GetByCode(ctx, req.Code)
|
||||||
@@ -152,6 +171,8 @@ func (h *RedeemHandler) CreateAndRedeem(c *gin.Context) {
|
|||||||
Value: req.Value,
|
Value: req.Value,
|
||||||
Status: service.StatusUnused,
|
Status: service.StatusUnused,
|
||||||
Notes: req.Notes,
|
Notes: req.Notes,
|
||||||
|
GroupID: req.GroupID,
|
||||||
|
ValidityDays: req.ValidityDays,
|
||||||
})
|
})
|
||||||
if createErr != nil {
|
if createErr != nil {
|
||||||
// Unique code race: if code now exists, use idempotent semantics by used_by.
|
// Unique code race: if code now exists, use idempotent semantics by used_by.
|
||||||
|
|||||||
141
backend/internal/handler/admin/redeem_handler_test.go
Normal file
141
backend/internal/handler/admin/redeem_handler_test.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newCreateAndRedeemHandler creates a RedeemHandler with a non-nil (but minimal)
|
||||||
|
// RedeemService so that CreateAndRedeem's nil guard passes and we can test the
|
||||||
|
// parameter-validation layer that runs before any service call.
|
||||||
|
func newCreateAndRedeemHandler() *RedeemHandler {
|
||||||
|
return &RedeemHandler{
|
||||||
|
adminService: newStubAdminService(),
|
||||||
|
redeemService: &service.RedeemService{}, // non-nil to pass nil guard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// postCreateAndRedeemValidation calls CreateAndRedeem and returns the response
|
||||||
|
// status code. For cases that pass validation and proceed into the service layer,
|
||||||
|
// a panic may occur (because RedeemService internals are nil); this is expected
|
||||||
|
// and treated as "validation passed" (returns 0 to indicate panic).
|
||||||
|
func postCreateAndRedeemValidation(t *testing.T, handler *RedeemHandler, body any) (code int) {
|
||||||
|
t.Helper()
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
c.Request, _ = http.NewRequest(http.MethodPost, "/api/v1/admin/redeem-codes/create-and-redeem", bytes.NewReader(jsonBytes))
|
||||||
|
c.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
// Panic means we passed validation and entered service layer (expected for minimal stub).
|
||||||
|
code = 0
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
handler.CreateAndRedeem(c)
|
||||||
|
return w.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndRedeem_TypeDefaultsToBalance(t *testing.T) {
|
||||||
|
// 不传 type 字段时应默认 balance,不触发 subscription 校验。
|
||||||
|
// 验证通过后进入 service 层会 panic(返回 0),说明默认值生效。
|
||||||
|
h := newCreateAndRedeemHandler()
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-balance-default",
|
||||||
|
"value": 10.0,
|
||||||
|
"user_id": 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
||||||
|
"omitting type should default to balance and pass validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndRedeem_SubscriptionRequiresGroupID(t *testing.T) {
|
||||||
|
h := newCreateAndRedeemHandler()
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-sub-no-group",
|
||||||
|
"type": "subscription",
|
||||||
|
"value": 29.9,
|
||||||
|
"user_id": 1,
|
||||||
|
"validity_days": 30,
|
||||||
|
// group_id 缺失
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndRedeem_SubscriptionRequiresNonZeroValidityDays(t *testing.T) {
|
||||||
|
groupID := int64(5)
|
||||||
|
h := newCreateAndRedeemHandler()
|
||||||
|
|
||||||
|
// zero should be rejected
|
||||||
|
t.Run("zero", func(t *testing.T) {
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-sub-bad-days-zero",
|
||||||
|
"type": "subscription",
|
||||||
|
"value": 29.9,
|
||||||
|
"user_id": 1,
|
||||||
|
"group_id": groupID,
|
||||||
|
"validity_days": 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, code)
|
||||||
|
})
|
||||||
|
|
||||||
|
// negative should pass validation (used for refund/reduction)
|
||||||
|
t.Run("negative_passes_validation", func(t *testing.T) {
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-sub-negative-days",
|
||||||
|
"type": "subscription",
|
||||||
|
"value": 29.9,
|
||||||
|
"user_id": 1,
|
||||||
|
"group_id": groupID,
|
||||||
|
"validity_days": -7,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
||||||
|
"negative validity_days should pass validation for refund")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndRedeem_SubscriptionValidParamsPassValidation(t *testing.T) {
|
||||||
|
groupID := int64(5)
|
||||||
|
h := newCreateAndRedeemHandler()
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-sub-valid",
|
||||||
|
"type": "subscription",
|
||||||
|
"value": 29.9,
|
||||||
|
"user_id": 1,
|
||||||
|
"group_id": groupID,
|
||||||
|
"validity_days": 31,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
||||||
|
"valid subscription params should pass validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndRedeem_BalanceIgnoresSubscriptionFields(t *testing.T) {
|
||||||
|
h := newCreateAndRedeemHandler()
|
||||||
|
// balance 类型不传 group_id 和 validity_days,不应报 400
|
||||||
|
code := postCreateAndRedeemValidation(t, h, map[string]any{
|
||||||
|
"code": "test-balance-no-extras",
|
||||||
|
"type": "balance",
|
||||||
|
"value": 50.0,
|
||||||
|
"user_id": 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotEqual(t, http.StatusBadRequest, code,
|
||||||
|
"balance type should not require group_id or validity_days")
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ type createScheduledTestPlanRequest struct {
|
|||||||
CronExpression string `json:"cron_expression" binding:"required"`
|
CronExpression string `json:"cron_expression" binding:"required"`
|
||||||
Enabled *bool `json:"enabled"`
|
Enabled *bool `json:"enabled"`
|
||||||
MaxResults int `json:"max_results"`
|
MaxResults int `json:"max_results"`
|
||||||
|
AutoRecover *bool `json:"auto_recover"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateScheduledTestPlanRequest struct {
|
type updateScheduledTestPlanRequest struct {
|
||||||
@@ -32,6 +33,7 @@ type updateScheduledTestPlanRequest struct {
|
|||||||
CronExpression string `json:"cron_expression"`
|
CronExpression string `json:"cron_expression"`
|
||||||
Enabled *bool `json:"enabled"`
|
Enabled *bool `json:"enabled"`
|
||||||
MaxResults int `json:"max_results"`
|
MaxResults int `json:"max_results"`
|
||||||
|
AutoRecover *bool `json:"auto_recover"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListByAccount GET /admin/accounts/:id/scheduled-test-plans
|
// ListByAccount GET /admin/accounts/:id/scheduled-test-plans
|
||||||
@@ -68,6 +70,9 @@ func (h *ScheduledTestHandler) Create(c *gin.Context) {
|
|||||||
if req.Enabled != nil {
|
if req.Enabled != nil {
|
||||||
plan.Enabled = *req.Enabled
|
plan.Enabled = *req.Enabled
|
||||||
}
|
}
|
||||||
|
if req.AutoRecover != nil {
|
||||||
|
plan.AutoRecover = *req.AutoRecover
|
||||||
|
}
|
||||||
|
|
||||||
created, err := h.scheduledTestSvc.CreatePlan(c.Request.Context(), plan)
|
created, err := h.scheduledTestSvc.CreatePlan(c.Request.Context(), plan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,6 +114,9 @@ func (h *ScheduledTestHandler) Update(c *gin.Context) {
|
|||||||
if req.MaxResults > 0 {
|
if req.MaxResults > 0 {
|
||||||
existing.MaxResults = req.MaxResults
|
existing.MaxResults = req.MaxResults
|
||||||
}
|
}
|
||||||
|
if req.AutoRecover != nil {
|
||||||
|
existing.AutoRecover = *req.AutoRecover
|
||||||
|
}
|
||||||
|
|
||||||
updated, err := h.scheduledTestSvc.UpdatePlan(c.Request.Context(), existing)
|
updated, err := h.scheduledTestSvc.UpdatePlan(c.Request.Context(), existing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
|
RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
|
||||||
PromoCodeEnabled: settings.PromoCodeEnabled,
|
PromoCodeEnabled: settings.PromoCodeEnabled,
|
||||||
PasswordResetEnabled: settings.PasswordResetEnabled,
|
PasswordResetEnabled: settings.PasswordResetEnabled,
|
||||||
|
FrontendURL: settings.FrontendURL,
|
||||||
InvitationCodeEnabled: settings.InvitationCodeEnabled,
|
InvitationCodeEnabled: settings.InvitationCodeEnabled,
|
||||||
TotpEnabled: settings.TotpEnabled,
|
TotpEnabled: settings.TotpEnabled,
|
||||||
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
||||||
@@ -109,6 +110,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
|
||||||
SoraClientEnabled: settings.SoraClientEnabled,
|
SoraClientEnabled: settings.SoraClientEnabled,
|
||||||
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
|
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
|
||||||
|
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
|
||||||
DefaultConcurrency: settings.DefaultConcurrency,
|
DefaultConcurrency: settings.DefaultConcurrency,
|
||||||
DefaultBalance: settings.DefaultBalance,
|
DefaultBalance: settings.DefaultBalance,
|
||||||
DefaultSubscriptions: defaultSubscriptions,
|
DefaultSubscriptions: defaultSubscriptions,
|
||||||
@@ -124,7 +126,11 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
OpsQueryModeDefault: settings.OpsQueryModeDefault,
|
||||||
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
|
||||||
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
|
MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
|
||||||
|
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
|
||||||
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
|
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
|
||||||
|
BackendModeEnabled: settings.BackendModeEnabled,
|
||||||
|
EnableFingerprintUnification: settings.EnableFingerprintUnification,
|
||||||
|
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +142,7 @@ type UpdateSettingsRequest struct {
|
|||||||
RegistrationEmailSuffixWhitelist []string `json:"registration_email_suffix_whitelist"`
|
RegistrationEmailSuffixWhitelist []string `json:"registration_email_suffix_whitelist"`
|
||||||
PromoCodeEnabled bool `json:"promo_code_enabled"`
|
PromoCodeEnabled bool `json:"promo_code_enabled"`
|
||||||
PasswordResetEnabled bool `json:"password_reset_enabled"`
|
PasswordResetEnabled bool `json:"password_reset_enabled"`
|
||||||
|
FrontendURL string `json:"frontend_url"`
|
||||||
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
|
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
|
||||||
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
|
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
|
||||||
|
|
||||||
@@ -172,6 +179,7 @@ type UpdateSettingsRequest struct {
|
|||||||
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
|
||||||
SoraClientEnabled bool `json:"sora_client_enabled"`
|
SoraClientEnabled bool `json:"sora_client_enabled"`
|
||||||
CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"`
|
CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"`
|
||||||
|
CustomEndpoints *[]dto.CustomEndpoint `json:"custom_endpoints"`
|
||||||
|
|
||||||
// 默认配置
|
// 默认配置
|
||||||
DefaultConcurrency int `json:"default_concurrency"`
|
DefaultConcurrency int `json:"default_concurrency"`
|
||||||
@@ -196,9 +204,17 @@ type UpdateSettingsRequest struct {
|
|||||||
OpsMetricsIntervalSeconds *int `json:"ops_metrics_interval_seconds"`
|
OpsMetricsIntervalSeconds *int `json:"ops_metrics_interval_seconds"`
|
||||||
|
|
||||||
MinClaudeCodeVersion string `json:"min_claude_code_version"`
|
MinClaudeCodeVersion string `json:"min_claude_code_version"`
|
||||||
|
MaxClaudeCodeVersion string `json:"max_claude_code_version"`
|
||||||
|
|
||||||
// 分组隔离
|
// 分组隔离
|
||||||
AllowUngroupedKeyScheduling bool `json:"allow_ungrouped_key_scheduling"`
|
AllowUngroupedKeyScheduling bool `json:"allow_ungrouped_key_scheduling"`
|
||||||
|
|
||||||
|
// Backend Mode
|
||||||
|
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
||||||
|
|
||||||
|
// Gateway forwarding behavior
|
||||||
|
EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"`
|
||||||
|
EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSettings 更新系统设置
|
// UpdateSettings 更新系统设置
|
||||||
@@ -223,11 +239,27 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
if req.DefaultBalance < 0 {
|
if req.DefaultBalance < 0 {
|
||||||
req.DefaultBalance = 0
|
req.DefaultBalance = 0
|
||||||
}
|
}
|
||||||
|
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
|
||||||
|
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
|
||||||
|
req.SMTPPassword = strings.TrimSpace(req.SMTPPassword)
|
||||||
|
req.SMTPFrom = strings.TrimSpace(req.SMTPFrom)
|
||||||
|
req.SMTPFromName = strings.TrimSpace(req.SMTPFromName)
|
||||||
if req.SMTPPort <= 0 {
|
if req.SMTPPort <= 0 {
|
||||||
req.SMTPPort = 587
|
req.SMTPPort = 587
|
||||||
}
|
}
|
||||||
req.DefaultSubscriptions = normalizeDefaultSubscriptions(req.DefaultSubscriptions)
|
req.DefaultSubscriptions = normalizeDefaultSubscriptions(req.DefaultSubscriptions)
|
||||||
|
|
||||||
|
// SMTP 配置保护:如果请求中 smtp_host 为空但数据库中已有配置,则保留已有 SMTP 配置
|
||||||
|
// 防止前端加载设置失败时空表单覆盖已保存的 SMTP 配置
|
||||||
|
if req.SMTPHost == "" && previousSettings.SMTPHost != "" {
|
||||||
|
req.SMTPHost = previousSettings.SMTPHost
|
||||||
|
req.SMTPPort = previousSettings.SMTPPort
|
||||||
|
req.SMTPUsername = previousSettings.SMTPUsername
|
||||||
|
req.SMTPFrom = previousSettings.SMTPFrom
|
||||||
|
req.SMTPFromName = previousSettings.SMTPFromName
|
||||||
|
req.SMTPUseTLS = previousSettings.SMTPUseTLS
|
||||||
|
}
|
||||||
|
|
||||||
// Turnstile 参数验证
|
// Turnstile 参数验证
|
||||||
if req.TurnstileEnabled {
|
if req.TurnstileEnabled {
|
||||||
// 检查必填字段
|
// 检查必填字段
|
||||||
@@ -322,6 +354,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frontend URL 验证
|
||||||
|
req.FrontendURL = strings.TrimSpace(req.FrontendURL)
|
||||||
|
if req.FrontendURL != "" {
|
||||||
|
if err := config.ValidateAbsoluteHTTPURL(req.FrontendURL); err != nil {
|
||||||
|
response.BadRequest(c, "Frontend URL must be an absolute http(s) URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 自定义菜单项验证
|
// 自定义菜单项验证
|
||||||
const (
|
const (
|
||||||
maxCustomMenuItems = 20
|
maxCustomMenuItems = 20
|
||||||
@@ -400,6 +441,55 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
customMenuJSON = string(menuBytes)
|
customMenuJSON = string(menuBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自定义端点验证
|
||||||
|
const (
|
||||||
|
maxCustomEndpoints = 10
|
||||||
|
maxEndpointNameLen = 50
|
||||||
|
maxEndpointURLLen = 2048
|
||||||
|
maxEndpointDescriptionLen = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
customEndpointsJSON := previousSettings.CustomEndpoints
|
||||||
|
if req.CustomEndpoints != nil {
|
||||||
|
endpoints := *req.CustomEndpoints
|
||||||
|
if len(endpoints) > maxCustomEndpoints {
|
||||||
|
response.BadRequest(c, "Too many custom endpoints (max 10)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, ep := range endpoints {
|
||||||
|
if strings.TrimSpace(ep.Name) == "" {
|
||||||
|
response.BadRequest(c, "Custom endpoint name is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ep.Name) > maxEndpointNameLen {
|
||||||
|
response.BadRequest(c, "Custom endpoint name is too long (max 50 characters)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(ep.Endpoint) == "" {
|
||||||
|
response.BadRequest(c, "Custom endpoint URL is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ep.Endpoint) > maxEndpointURLLen {
|
||||||
|
response.BadRequest(c, "Custom endpoint URL is too long (max 2048 characters)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := config.ValidateAbsoluteHTTPURL(strings.TrimSpace(ep.Endpoint)); err != nil {
|
||||||
|
response.BadRequest(c, "Custom endpoint URL must be an absolute http(s) URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ep.Description) > maxEndpointDescriptionLen {
|
||||||
|
response.BadRequest(c, "Custom endpoint description is too long (max 200 characters)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endpointBytes, err := json.Marshal(endpoints)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Failed to serialize custom endpoints")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customEndpointsJSON = string(endpointBytes)
|
||||||
|
}
|
||||||
|
|
||||||
// Ops metrics collector interval validation (seconds).
|
// Ops metrics collector interval validation (seconds).
|
||||||
if req.OpsMetricsIntervalSeconds != nil {
|
if req.OpsMetricsIntervalSeconds != nil {
|
||||||
v := *req.OpsMetricsIntervalSeconds
|
v := *req.OpsMetricsIntervalSeconds
|
||||||
@@ -427,12 +517,29 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证最高版本号格式(空字符串=禁用,或合法 semver)
|
||||||
|
if req.MaxClaudeCodeVersion != "" {
|
||||||
|
if !semverPattern.MatchString(req.MaxClaudeCodeVersion) {
|
||||||
|
response.Error(c, http.StatusBadRequest, "max_claude_code_version must be empty or a valid semver (e.g. 3.0.0)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交叉验证:如果同时设置了最低和最高版本号,最高版本号必须 >= 最低版本号
|
||||||
|
if req.MinClaudeCodeVersion != "" && req.MaxClaudeCodeVersion != "" {
|
||||||
|
if service.CompareVersions(req.MaxClaudeCodeVersion, req.MinClaudeCodeVersion) < 0 {
|
||||||
|
response.Error(c, http.StatusBadRequest, "max_claude_code_version must be greater than or equal to min_claude_code_version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settings := &service.SystemSettings{
|
settings := &service.SystemSettings{
|
||||||
RegistrationEnabled: req.RegistrationEnabled,
|
RegistrationEnabled: req.RegistrationEnabled,
|
||||||
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
EmailVerifyEnabled: req.EmailVerifyEnabled,
|
||||||
RegistrationEmailSuffixWhitelist: req.RegistrationEmailSuffixWhitelist,
|
RegistrationEmailSuffixWhitelist: req.RegistrationEmailSuffixWhitelist,
|
||||||
PromoCodeEnabled: req.PromoCodeEnabled,
|
PromoCodeEnabled: req.PromoCodeEnabled,
|
||||||
PasswordResetEnabled: req.PasswordResetEnabled,
|
PasswordResetEnabled: req.PasswordResetEnabled,
|
||||||
|
FrontendURL: req.FrontendURL,
|
||||||
InvitationCodeEnabled: req.InvitationCodeEnabled,
|
InvitationCodeEnabled: req.InvitationCodeEnabled,
|
||||||
TotpEnabled: req.TotpEnabled,
|
TotpEnabled: req.TotpEnabled,
|
||||||
SMTPHost: req.SMTPHost,
|
SMTPHost: req.SMTPHost,
|
||||||
@@ -461,6 +568,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
PurchaseSubscriptionURL: purchaseURL,
|
PurchaseSubscriptionURL: purchaseURL,
|
||||||
SoraClientEnabled: req.SoraClientEnabled,
|
SoraClientEnabled: req.SoraClientEnabled,
|
||||||
CustomMenuItems: customMenuJSON,
|
CustomMenuItems: customMenuJSON,
|
||||||
|
CustomEndpoints: customEndpointsJSON,
|
||||||
DefaultConcurrency: req.DefaultConcurrency,
|
DefaultConcurrency: req.DefaultConcurrency,
|
||||||
DefaultBalance: req.DefaultBalance,
|
DefaultBalance: req.DefaultBalance,
|
||||||
DefaultSubscriptions: defaultSubscriptions,
|
DefaultSubscriptions: defaultSubscriptions,
|
||||||
@@ -472,7 +580,9 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
EnableIdentityPatch: req.EnableIdentityPatch,
|
EnableIdentityPatch: req.EnableIdentityPatch,
|
||||||
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
IdentityPatchPrompt: req.IdentityPatchPrompt,
|
||||||
MinClaudeCodeVersion: req.MinClaudeCodeVersion,
|
MinClaudeCodeVersion: req.MinClaudeCodeVersion,
|
||||||
|
MaxClaudeCodeVersion: req.MaxClaudeCodeVersion,
|
||||||
AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling,
|
AllowUngroupedKeyScheduling: req.AllowUngroupedKeyScheduling,
|
||||||
|
BackendModeEnabled: req.BackendModeEnabled,
|
||||||
OpsMonitoringEnabled: func() bool {
|
OpsMonitoringEnabled: func() bool {
|
||||||
if req.OpsMonitoringEnabled != nil {
|
if req.OpsMonitoringEnabled != nil {
|
||||||
return *req.OpsMonitoringEnabled
|
return *req.OpsMonitoringEnabled
|
||||||
@@ -497,6 +607,18 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
return previousSettings.OpsMetricsIntervalSeconds
|
return previousSettings.OpsMetricsIntervalSeconds
|
||||||
}(),
|
}(),
|
||||||
|
EnableFingerprintUnification: func() bool {
|
||||||
|
if req.EnableFingerprintUnification != nil {
|
||||||
|
return *req.EnableFingerprintUnification
|
||||||
|
}
|
||||||
|
return previousSettings.EnableFingerprintUnification
|
||||||
|
}(),
|
||||||
|
EnableMetadataPassthrough: func() bool {
|
||||||
|
if req.EnableMetadataPassthrough != nil {
|
||||||
|
return *req.EnableMetadataPassthrough
|
||||||
|
}
|
||||||
|
return previousSettings.EnableMetadataPassthrough
|
||||||
|
}(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
|
||||||
@@ -526,6 +648,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
RegistrationEmailSuffixWhitelist: updatedSettings.RegistrationEmailSuffixWhitelist,
|
RegistrationEmailSuffixWhitelist: updatedSettings.RegistrationEmailSuffixWhitelist,
|
||||||
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
|
PromoCodeEnabled: updatedSettings.PromoCodeEnabled,
|
||||||
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
|
PasswordResetEnabled: updatedSettings.PasswordResetEnabled,
|
||||||
|
FrontendURL: updatedSettings.FrontendURL,
|
||||||
InvitationCodeEnabled: updatedSettings.InvitationCodeEnabled,
|
InvitationCodeEnabled: updatedSettings.InvitationCodeEnabled,
|
||||||
TotpEnabled: updatedSettings.TotpEnabled,
|
TotpEnabled: updatedSettings.TotpEnabled,
|
||||||
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
|
||||||
@@ -555,6 +678,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
|
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
|
||||||
SoraClientEnabled: updatedSettings.SoraClientEnabled,
|
SoraClientEnabled: updatedSettings.SoraClientEnabled,
|
||||||
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
|
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
|
||||||
|
CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints),
|
||||||
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
DefaultConcurrency: updatedSettings.DefaultConcurrency,
|
||||||
DefaultBalance: updatedSettings.DefaultBalance,
|
DefaultBalance: updatedSettings.DefaultBalance,
|
||||||
DefaultSubscriptions: updatedDefaultSubscriptions,
|
DefaultSubscriptions: updatedDefaultSubscriptions,
|
||||||
@@ -570,7 +694,11 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
|
OpsQueryModeDefault: updatedSettings.OpsQueryModeDefault,
|
||||||
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
|
OpsMetricsIntervalSeconds: updatedSettings.OpsMetricsIntervalSeconds,
|
||||||
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
|
MinClaudeCodeVersion: updatedSettings.MinClaudeCodeVersion,
|
||||||
|
MaxClaudeCodeVersion: updatedSettings.MaxClaudeCodeVersion,
|
||||||
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
|
AllowUngroupedKeyScheduling: updatedSettings.AllowUngroupedKeyScheduling,
|
||||||
|
BackendModeEnabled: updatedSettings.BackendModeEnabled,
|
||||||
|
EnableFingerprintUnification: updatedSettings.EnableFingerprintUnification,
|
||||||
|
EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,6 +736,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.PasswordResetEnabled != after.PasswordResetEnabled {
|
if before.PasswordResetEnabled != after.PasswordResetEnabled {
|
||||||
changed = append(changed, "password_reset_enabled")
|
changed = append(changed, "password_reset_enabled")
|
||||||
}
|
}
|
||||||
|
if before.FrontendURL != after.FrontendURL {
|
||||||
|
changed = append(changed, "frontend_url")
|
||||||
|
}
|
||||||
if before.TotpEnabled != after.TotpEnabled {
|
if before.TotpEnabled != after.TotpEnabled {
|
||||||
changed = append(changed, "totp_enabled")
|
changed = append(changed, "totp_enabled")
|
||||||
}
|
}
|
||||||
@@ -722,9 +853,15 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
|
if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
|
||||||
changed = append(changed, "min_claude_code_version")
|
changed = append(changed, "min_claude_code_version")
|
||||||
}
|
}
|
||||||
|
if before.MaxClaudeCodeVersion != after.MaxClaudeCodeVersion {
|
||||||
|
changed = append(changed, "max_claude_code_version")
|
||||||
|
}
|
||||||
if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
|
if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
|
||||||
changed = append(changed, "allow_ungrouped_key_scheduling")
|
changed = append(changed, "allow_ungrouped_key_scheduling")
|
||||||
}
|
}
|
||||||
|
if before.BackendModeEnabled != after.BackendModeEnabled {
|
||||||
|
changed = append(changed, "backend_mode_enabled")
|
||||||
|
}
|
||||||
if before.PurchaseSubscriptionEnabled != after.PurchaseSubscriptionEnabled {
|
if before.PurchaseSubscriptionEnabled != after.PurchaseSubscriptionEnabled {
|
||||||
changed = append(changed, "purchase_subscription_enabled")
|
changed = append(changed, "purchase_subscription_enabled")
|
||||||
}
|
}
|
||||||
@@ -734,6 +871,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.CustomMenuItems != after.CustomMenuItems {
|
if before.CustomMenuItems != after.CustomMenuItems {
|
||||||
changed = append(changed, "custom_menu_items")
|
changed = append(changed, "custom_menu_items")
|
||||||
}
|
}
|
||||||
|
if before.EnableFingerprintUnification != after.EnableFingerprintUnification {
|
||||||
|
changed = append(changed, "enable_fingerprint_unification")
|
||||||
|
}
|
||||||
|
if before.EnableMetadataPassthrough != after.EnableMetadataPassthrough {
|
||||||
|
changed = append(changed, "enable_metadata_passthrough")
|
||||||
|
}
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -780,7 +923,7 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
|
|||||||
|
|
||||||
// TestSMTPRequest 测试SMTP连接请求
|
// TestSMTPRequest 测试SMTP连接请求
|
||||||
type TestSMTPRequest struct {
|
type TestSMTPRequest struct {
|
||||||
SMTPHost string `json:"smtp_host" binding:"required"`
|
SMTPHost string `json:"smtp_host"`
|
||||||
SMTPPort int `json:"smtp_port"`
|
SMTPPort int `json:"smtp_port"`
|
||||||
SMTPUsername string `json:"smtp_username"`
|
SMTPUsername string `json:"smtp_username"`
|
||||||
SMTPPassword string `json:"smtp_password"`
|
SMTPPassword string `json:"smtp_password"`
|
||||||
@@ -796,17 +939,34 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.SMTPPort <= 0 {
|
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
|
||||||
req.SMTPPort = 587
|
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
|
||||||
|
|
||||||
|
var savedConfig *service.SMTPConfig
|
||||||
|
if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
|
||||||
|
savedConfig = cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未提供密码,从数据库获取已保存的密码
|
if req.SMTPHost == "" && savedConfig != nil {
|
||||||
password := req.SMTPPassword
|
req.SMTPHost = savedConfig.Host
|
||||||
if password == "" {
|
}
|
||||||
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context())
|
if req.SMTPPort <= 0 {
|
||||||
if err == nil && savedConfig != nil {
|
if savedConfig != nil && savedConfig.Port > 0 {
|
||||||
|
req.SMTPPort = savedConfig.Port
|
||||||
|
} else {
|
||||||
|
req.SMTPPort = 587
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.SMTPUsername == "" && savedConfig != nil {
|
||||||
|
req.SMTPUsername = savedConfig.Username
|
||||||
|
}
|
||||||
|
password := strings.TrimSpace(req.SMTPPassword)
|
||||||
|
if password == "" && savedConfig != nil {
|
||||||
password = savedConfig.Password
|
password = savedConfig.Password
|
||||||
}
|
}
|
||||||
|
if req.SMTPHost == "" {
|
||||||
|
response.BadRequest(c, "SMTP host is required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &service.SMTPConfig{
|
config := &service.SMTPConfig{
|
||||||
@@ -829,7 +989,7 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
|
|||||||
// SendTestEmailRequest 发送测试邮件请求
|
// SendTestEmailRequest 发送测试邮件请求
|
||||||
type SendTestEmailRequest struct {
|
type SendTestEmailRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
SMTPHost string `json:"smtp_host" binding:"required"`
|
SMTPHost string `json:"smtp_host"`
|
||||||
SMTPPort int `json:"smtp_port"`
|
SMTPPort int `json:"smtp_port"`
|
||||||
SMTPUsername string `json:"smtp_username"`
|
SMTPUsername string `json:"smtp_username"`
|
||||||
SMTPPassword string `json:"smtp_password"`
|
SMTPPassword string `json:"smtp_password"`
|
||||||
@@ -847,17 +1007,42 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.SMTPPort <= 0 {
|
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
|
||||||
req.SMTPPort = 587
|
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
|
||||||
|
req.SMTPFrom = strings.TrimSpace(req.SMTPFrom)
|
||||||
|
req.SMTPFromName = strings.TrimSpace(req.SMTPFromName)
|
||||||
|
|
||||||
|
var savedConfig *service.SMTPConfig
|
||||||
|
if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
|
||||||
|
savedConfig = cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未提供密码,从数据库获取已保存的密码
|
if req.SMTPHost == "" && savedConfig != nil {
|
||||||
password := req.SMTPPassword
|
req.SMTPHost = savedConfig.Host
|
||||||
if password == "" {
|
}
|
||||||
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context())
|
if req.SMTPPort <= 0 {
|
||||||
if err == nil && savedConfig != nil {
|
if savedConfig != nil && savedConfig.Port > 0 {
|
||||||
|
req.SMTPPort = savedConfig.Port
|
||||||
|
} else {
|
||||||
|
req.SMTPPort = 587
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.SMTPUsername == "" && savedConfig != nil {
|
||||||
|
req.SMTPUsername = savedConfig.Username
|
||||||
|
}
|
||||||
|
password := strings.TrimSpace(req.SMTPPassword)
|
||||||
|
if password == "" && savedConfig != nil {
|
||||||
password = savedConfig.Password
|
password = savedConfig.Password
|
||||||
}
|
}
|
||||||
|
if req.SMTPFrom == "" && savedConfig != nil {
|
||||||
|
req.SMTPFrom = savedConfig.From
|
||||||
|
}
|
||||||
|
if req.SMTPFromName == "" && savedConfig != nil {
|
||||||
|
req.SMTPFromName = savedConfig.FromName
|
||||||
|
}
|
||||||
|
if req.SMTPHost == "" {
|
||||||
|
response.BadRequest(c, "SMTP host is required")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &service.SMTPConfig{
|
config := &service.SMTPConfig{
|
||||||
@@ -952,6 +1137,58 @@ 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"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOverloadCooldownSettings 获取529过载冷却配置
|
||||||
|
// GET /api/v1/admin/settings/overload-cooldown
|
||||||
|
func (h *SettingHandler) GetOverloadCooldownSettings(c *gin.Context) {
|
||||||
|
settings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.OverloadCooldownSettings{
|
||||||
|
Enabled: settings.Enabled,
|
||||||
|
CooldownMinutes: settings.CooldownMinutes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOverloadCooldownSettingsRequest 更新529过载冷却配置请求
|
||||||
|
type UpdateOverloadCooldownSettingsRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
CooldownMinutes int `json:"cooldown_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOverloadCooldownSettings 更新529过载冷却配置
|
||||||
|
// PUT /api/v1/admin/settings/overload-cooldown
|
||||||
|
func (h *SettingHandler) UpdateOverloadCooldownSettings(c *gin.Context) {
|
||||||
|
var req UpdateOverloadCooldownSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := &service.OverloadCooldownSettings{
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
CooldownMinutes: req.CooldownMinutes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.settingService.SetOverloadCooldownSettings(c.Request.Context(), settings); err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedSettings, err := h.settingService.GetOverloadCooldownSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, dto.OverloadCooldownSettings{
|
||||||
|
Enabled: updatedSettings.Enabled,
|
||||||
|
CooldownMinutes: updatedSettings.CooldownMinutes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetStreamTimeoutSettings 获取流超时处理配置
|
// GetStreamTimeoutSettings 获取流超时处理配置
|
||||||
// GET /api/v1/admin/settings/stream-timeout
|
// GET /api/v1/admin/settings/stream-timeout
|
||||||
func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
|
func (h *SettingHandler) GetStreamTimeoutSettings(c *gin.Context) {
|
||||||
@@ -1348,6 +1585,154 @@ func (h *SettingHandler) TestSoraS3Connection(c *gin.Context) {
|
|||||||
response.Success(c, gin.H{"message": "S3 连接成功"})
|
response.Success(c, gin.H{"message": "S3 连接成功"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRectifierSettings 获取请求整流器配置
|
||||||
|
// GET /api/v1/admin/settings/rectifier
|
||||||
|
func (h *SettingHandler) GetRectifierSettings(c *gin.Context) {
|
||||||
|
settings, err := h.settingService.GetRectifierSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns := settings.APIKeySignaturePatterns
|
||||||
|
if patterns == nil {
|
||||||
|
patterns = []string{}
|
||||||
|
}
|
||||||
|
response.Success(c, dto.RectifierSettings{
|
||||||
|
Enabled: settings.Enabled,
|
||||||
|
ThinkingSignatureEnabled: settings.ThinkingSignatureEnabled,
|
||||||
|
ThinkingBudgetEnabled: settings.ThinkingBudgetEnabled,
|
||||||
|
APIKeySignatureEnabled: settings.APIKeySignatureEnabled,
|
||||||
|
APIKeySignaturePatterns: patterns,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRectifierSettingsRequest 更新整流器配置请求
|
||||||
|
type UpdateRectifierSettingsRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
ThinkingSignatureEnabled bool `json:"thinking_signature_enabled"`
|
||||||
|
ThinkingBudgetEnabled bool `json:"thinking_budget_enabled"`
|
||||||
|
APIKeySignatureEnabled bool `json:"apikey_signature_enabled"`
|
||||||
|
APIKeySignaturePatterns []string `json:"apikey_signature_patterns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRectifierSettings 更新请求整流器配置
|
||||||
|
// PUT /api/v1/admin/settings/rectifier
|
||||||
|
func (h *SettingHandler) UpdateRectifierSettings(c *gin.Context) {
|
||||||
|
var req UpdateRectifierSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验并清理自定义匹配关键词
|
||||||
|
const maxPatterns = 50
|
||||||
|
const maxPatternLen = 500
|
||||||
|
if len(req.APIKeySignaturePatterns) > maxPatterns {
|
||||||
|
response.BadRequest(c, "Too many signature patterns (max 50)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cleanedPatterns []string
|
||||||
|
for _, p := range req.APIKeySignaturePatterns {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(p) > maxPatternLen {
|
||||||
|
response.BadRequest(c, "Signature pattern too long (max 500 characters)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cleanedPatterns = append(cleanedPatterns, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := &service.RectifierSettings{
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
ThinkingSignatureEnabled: req.ThinkingSignatureEnabled,
|
||||||
|
ThinkingBudgetEnabled: req.ThinkingBudgetEnabled,
|
||||||
|
APIKeySignatureEnabled: req.APIKeySignatureEnabled,
|
||||||
|
APIKeySignaturePatterns: cleanedPatterns,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.settingService.SetRectifierSettings(c.Request.Context(), settings); err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新获取设置返回
|
||||||
|
updatedSettings, err := h.settingService.GetRectifierSettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPatterns := updatedSettings.APIKeySignaturePatterns
|
||||||
|
if updatedPatterns == nil {
|
||||||
|
updatedPatterns = []string{}
|
||||||
|
}
|
||||||
|
response.Success(c, dto.RectifierSettings{
|
||||||
|
Enabled: updatedSettings.Enabled,
|
||||||
|
ThinkingSignatureEnabled: updatedSettings.ThinkingSignatureEnabled,
|
||||||
|
ThinkingBudgetEnabled: updatedSettings.ThinkingBudgetEnabled,
|
||||||
|
APIKeySignatureEnabled: updatedSettings.APIKeySignatureEnabled,
|
||||||
|
APIKeySignaturePatterns: updatedPatterns,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBetaPolicySettings 获取 Beta 策略配置
|
||||||
|
// GET /api/v1/admin/settings/beta-policy
|
||||||
|
func (h *SettingHandler) GetBetaPolicySettings(c *gin.Context) {
|
||||||
|
settings, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rules := make([]dto.BetaPolicyRule, len(settings.Rules))
|
||||||
|
for i, r := range settings.Rules {
|
||||||
|
rules[i] = dto.BetaPolicyRule(r)
|
||||||
|
}
|
||||||
|
response.Success(c, dto.BetaPolicySettings{Rules: rules})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBetaPolicySettingsRequest 更新 Beta 策略配置请求
|
||||||
|
type UpdateBetaPolicySettingsRequest struct {
|
||||||
|
Rules []dto.BetaPolicyRule `json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBetaPolicySettings 更新 Beta 策略配置
|
||||||
|
// PUT /api/v1/admin/settings/beta-policy
|
||||||
|
func (h *SettingHandler) UpdateBetaPolicySettings(c *gin.Context) {
|
||||||
|
var req UpdateBetaPolicySettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rules := make([]service.BetaPolicyRule, len(req.Rules))
|
||||||
|
for i, r := range req.Rules {
|
||||||
|
rules[i] = service.BetaPolicyRule(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := &service.BetaPolicySettings{Rules: rules}
|
||||||
|
if err := h.settingService.SetBetaPolicySettings(c.Request.Context(), settings); err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch to return updated settings
|
||||||
|
updated, err := h.settingService.GetBetaPolicySettings(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outRules := make([]dto.BetaPolicyRule, len(updated.Rules))
|
||||||
|
for i, r := range updated.Rules {
|
||||||
|
outRules[i] = dto.BetaPolicyRule(r)
|
||||||
|
}
|
||||||
|
response.Success(c, dto.BetaPolicySettings{Rules: outRules})
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
|
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
|
||||||
type UpdateStreamTimeoutSettingsRequest struct {
|
type UpdateStreamTimeoutSettingsRequest struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
type snapshotCacheEntry struct {
|
type snapshotCacheEntry struct {
|
||||||
@@ -19,6 +21,12 @@ type snapshotCache struct {
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ttl time.Duration
|
ttl time.Duration
|
||||||
items map[string]snapshotCacheEntry
|
items map[string]snapshotCacheEntry
|
||||||
|
sf singleflight.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
type snapshotCacheLoadResult struct {
|
||||||
|
Entry snapshotCacheEntry
|
||||||
|
Hit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSnapshotCache(ttl time.Duration) *snapshotCache {
|
func newSnapshotCache(ttl time.Duration) *snapshotCache {
|
||||||
@@ -70,6 +78,41 @@ func (c *snapshotCache) Set(key string, payload any) snapshotCacheEntry {
|
|||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *snapshotCache) GetOrLoad(key string, load func() (any, error)) (snapshotCacheEntry, bool, error) {
|
||||||
|
if load == nil {
|
||||||
|
return snapshotCacheEntry{}, false, nil
|
||||||
|
}
|
||||||
|
if entry, ok := c.Get(key); ok {
|
||||||
|
return entry, true, nil
|
||||||
|
}
|
||||||
|
if c == nil || key == "" {
|
||||||
|
payload, err := load()
|
||||||
|
if err != nil {
|
||||||
|
return snapshotCacheEntry{}, false, err
|
||||||
|
}
|
||||||
|
return c.Set(key, payload), false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err, _ := c.sf.Do(key, func() (any, error) {
|
||||||
|
if entry, ok := c.Get(key); ok {
|
||||||
|
return snapshotCacheLoadResult{Entry: entry, Hit: true}, nil
|
||||||
|
}
|
||||||
|
payload, err := load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return snapshotCacheLoadResult{Entry: c.Set(key, payload), Hit: false}, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return snapshotCacheEntry{}, false, err
|
||||||
|
}
|
||||||
|
result, ok := value.(snapshotCacheLoadResult)
|
||||||
|
if !ok {
|
||||||
|
return snapshotCacheEntry{}, false, nil
|
||||||
|
}
|
||||||
|
return result.Entry, result.Hit, nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildETagFromAny(payload any) string {
|
func buildETagFromAny(payload any) string {
|
||||||
raw, err := json.Marshal(payload)
|
raw, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -95,6 +97,61 @@ func TestBuildETagFromAny_UnmarshalablePayload(t *testing.T) {
|
|||||||
require.Empty(t, etag)
|
require.Empty(t, etag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnapshotCache_GetOrLoad_MissThenHit(t *testing.T) {
|
||||||
|
c := newSnapshotCache(5 * time.Second)
|
||||||
|
var loads atomic.Int32
|
||||||
|
|
||||||
|
entry, hit, err := c.GetOrLoad("key1", func() (any, error) {
|
||||||
|
loads.Add(1)
|
||||||
|
return map[string]string{"hello": "world"}, nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, hit)
|
||||||
|
require.NotEmpty(t, entry.ETag)
|
||||||
|
require.Equal(t, int32(1), loads.Load())
|
||||||
|
|
||||||
|
entry2, hit, err := c.GetOrLoad("key1", func() (any, error) {
|
||||||
|
loads.Add(1)
|
||||||
|
return map[string]string{"unexpected": "value"}, nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, hit)
|
||||||
|
require.Equal(t, entry.ETag, entry2.ETag)
|
||||||
|
require.Equal(t, int32(1), loads.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnapshotCache_GetOrLoad_ConcurrentSingleflight(t *testing.T) {
|
||||||
|
c := newSnapshotCache(5 * time.Second)
|
||||||
|
var loads atomic.Int32
|
||||||
|
start := make(chan struct{})
|
||||||
|
const callers = 8
|
||||||
|
errCh := make(chan error, callers)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(callers)
|
||||||
|
for range callers {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
<-start
|
||||||
|
_, _, err := c.GetOrLoad("shared", func() (any, error) {
|
||||||
|
loads.Add(1)
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
return "value", nil
|
||||||
|
})
|
||||||
|
errCh <- err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
close(start)
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
for err := range errCh {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, int32(1), loads.Load())
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseBoolQueryWithDefault(t *testing.T) {
|
func TestParseBoolQueryWithDefault(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -77,12 +77,13 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
|
platform := c.Query("platform")
|
||||||
|
|
||||||
// Parse sorting parameters
|
// Parse sorting parameters
|
||||||
sortBy := c.DefaultQuery("sort_by", "created_at")
|
sortBy := c.DefaultQuery("sort_by", "created_at")
|
||||||
sortOrder := c.DefaultQuery("sort_order", "desc")
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
|
|
||||||
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status, sortBy, sortOrder)
|
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status, platform, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -216,6 +217,38 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetSubscriptionQuotaRequest represents the reset quota request
|
||||||
|
type ResetSubscriptionQuotaRequest struct {
|
||||||
|
Daily bool `json:"daily"`
|
||||||
|
Weekly bool `json:"weekly"`
|
||||||
|
Monthly bool `json:"monthly"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetQuota resets daily, weekly, and/or monthly usage for a subscription.
|
||||||
|
// POST /api/v1/admin/subscriptions/:id/reset-quota
|
||||||
|
func (h *SubscriptionHandler) ResetQuota(c *gin.Context) {
|
||||||
|
subscriptionID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid subscription ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req ResetSubscriptionQuotaRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !req.Daily && !req.Weekly && !req.Monthly {
|
||||||
|
response.BadRequest(c, "At least one of 'daily', 'weekly', or 'monthly' must be true")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sub, err := h.subscriptionService.AdminResetQuota(c.Request.Context(), subscriptionID, req.Daily, req.Weekly, req.Monthly)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, dto.UserSubscriptionFromServiceAdmin(sub))
|
||||||
|
}
|
||||||
|
|
||||||
// Revoke handles revoking a subscription
|
// Revoke handles revoking a subscription
|
||||||
// DELETE /api/v1/admin/subscriptions/:id
|
// DELETE /api/v1/admin/subscriptions/:id
|
||||||
func (h *SubscriptionHandler) Revoke(c *gin.Context) {
|
func (h *SubscriptionHandler) Revoke(c *gin.Context) {
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/model"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSFingerprintProfileHandler 处理 TLS 指纹模板的 HTTP 请求
|
||||||
|
type TLSFingerprintProfileHandler struct {
|
||||||
|
service *service.TLSFingerprintProfileService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSFingerprintProfileHandler 创建 TLS 指纹模板处理器
|
||||||
|
func NewTLSFingerprintProfileHandler(service *service.TLSFingerprintProfileService) *TLSFingerprintProfileHandler {
|
||||||
|
return &TLSFingerprintProfileHandler{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTLSFingerprintProfileRequest 创建模板请求
|
||||||
|
type CreateTLSFingerprintProfileRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
EnableGREASE *bool `json:"enable_grease"`
|
||||||
|
CipherSuites []uint16 `json:"cipher_suites"`
|
||||||
|
Curves []uint16 `json:"curves"`
|
||||||
|
PointFormats []uint16 `json:"point_formats"`
|
||||||
|
SignatureAlgorithms []uint16 `json:"signature_algorithms"`
|
||||||
|
ALPNProtocols []string `json:"alpn_protocols"`
|
||||||
|
SupportedVersions []uint16 `json:"supported_versions"`
|
||||||
|
KeyShareGroups []uint16 `json:"key_share_groups"`
|
||||||
|
PSKModes []uint16 `json:"psk_modes"`
|
||||||
|
Extensions []uint16 `json:"extensions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTLSFingerprintProfileRequest 更新模板请求(部分更新)
|
||||||
|
type UpdateTLSFingerprintProfileRequest struct {
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
EnableGREASE *bool `json:"enable_grease"`
|
||||||
|
CipherSuites []uint16 `json:"cipher_suites"`
|
||||||
|
Curves []uint16 `json:"curves"`
|
||||||
|
PointFormats []uint16 `json:"point_formats"`
|
||||||
|
SignatureAlgorithms []uint16 `json:"signature_algorithms"`
|
||||||
|
ALPNProtocols []string `json:"alpn_protocols"`
|
||||||
|
SupportedVersions []uint16 `json:"supported_versions"`
|
||||||
|
KeyShareGroups []uint16 `json:"key_share_groups"`
|
||||||
|
PSKModes []uint16 `json:"psk_modes"`
|
||||||
|
Extensions []uint16 `json:"extensions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取所有模板
|
||||||
|
// GET /api/v1/admin/tls-fingerprint-profiles
|
||||||
|
func (h *TLSFingerprintProfileHandler) List(c *gin.Context) {
|
||||||
|
profiles, err := h.service.List(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据 ID 获取模板
|
||||||
|
// GET /api/v1/admin/tls-fingerprint-profiles/:id
|
||||||
|
func (h *TLSFingerprintProfileHandler) GetByID(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid profile ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := h.service.GetByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if profile == nil {
|
||||||
|
response.NotFound(c, "Profile not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建模板
|
||||||
|
// POST /api/v1/admin/tls-fingerprint-profiles
|
||||||
|
func (h *TLSFingerprintProfileHandler) Create(c *gin.Context) {
|
||||||
|
var req CreateTLSFingerprintProfileRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := &model.TLSFingerprintProfile{
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
CipherSuites: req.CipherSuites,
|
||||||
|
Curves: req.Curves,
|
||||||
|
PointFormats: req.PointFormats,
|
||||||
|
SignatureAlgorithms: req.SignatureAlgorithms,
|
||||||
|
ALPNProtocols: req.ALPNProtocols,
|
||||||
|
SupportedVersions: req.SupportedVersions,
|
||||||
|
KeyShareGroups: req.KeyShareGroups,
|
||||||
|
PSKModes: req.PSKModes,
|
||||||
|
Extensions: req.Extensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.EnableGREASE != nil {
|
||||||
|
profile.EnableGREASE = *req.EnableGREASE
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := h.service.Create(c.Request.Context(), profile)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*model.ValidationError); ok {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, created)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新模板(支持部分更新)
|
||||||
|
// PUT /api/v1/admin/tls-fingerprint-profiles/:id
|
||||||
|
func (h *TLSFingerprintProfileHandler) Update(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid profile ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req UpdateTLSFingerprintProfileRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := h.service.GetByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
response.NotFound(c, "Profile not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部分更新
|
||||||
|
profile := &model.TLSFingerprintProfile{
|
||||||
|
ID: id,
|
||||||
|
Name: existing.Name,
|
||||||
|
Description: existing.Description,
|
||||||
|
EnableGREASE: existing.EnableGREASE,
|
||||||
|
CipherSuites: existing.CipherSuites,
|
||||||
|
Curves: existing.Curves,
|
||||||
|
PointFormats: existing.PointFormats,
|
||||||
|
SignatureAlgorithms: existing.SignatureAlgorithms,
|
||||||
|
ALPNProtocols: existing.ALPNProtocols,
|
||||||
|
SupportedVersions: existing.SupportedVersions,
|
||||||
|
KeyShareGroups: existing.KeyShareGroups,
|
||||||
|
PSKModes: existing.PSKModes,
|
||||||
|
Extensions: existing.Extensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
profile.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
profile.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.EnableGREASE != nil {
|
||||||
|
profile.EnableGREASE = *req.EnableGREASE
|
||||||
|
}
|
||||||
|
if req.CipherSuites != nil {
|
||||||
|
profile.CipherSuites = req.CipherSuites
|
||||||
|
}
|
||||||
|
if req.Curves != nil {
|
||||||
|
profile.Curves = req.Curves
|
||||||
|
}
|
||||||
|
if req.PointFormats != nil {
|
||||||
|
profile.PointFormats = req.PointFormats
|
||||||
|
}
|
||||||
|
if req.SignatureAlgorithms != nil {
|
||||||
|
profile.SignatureAlgorithms = req.SignatureAlgorithms
|
||||||
|
}
|
||||||
|
if req.ALPNProtocols != nil {
|
||||||
|
profile.ALPNProtocols = req.ALPNProtocols
|
||||||
|
}
|
||||||
|
if req.SupportedVersions != nil {
|
||||||
|
profile.SupportedVersions = req.SupportedVersions
|
||||||
|
}
|
||||||
|
if req.KeyShareGroups != nil {
|
||||||
|
profile.KeyShareGroups = req.KeyShareGroups
|
||||||
|
}
|
||||||
|
if req.PSKModes != nil {
|
||||||
|
profile.PSKModes = req.PSKModes
|
||||||
|
}
|
||||||
|
if req.Extensions != nil {
|
||||||
|
profile.Extensions = req.Extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := h.service.Update(c.Request.Context(), profile)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*model.ValidationError); ok {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除模板
|
||||||
|
// DELETE /api/v1/admin/tls-fingerprint-profiles/:id
|
||||||
|
func (h *TLSFingerprintProfileHandler) Delete(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid profile ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.service.Delete(c.Request.Context(), id); err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{"message": "Profile deleted successfully"})
|
||||||
|
}
|
||||||
@@ -110,6 +110,7 @@ func (h *UsageHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model := c.Query("model")
|
model := c.Query("model")
|
||||||
|
billingMode := strings.TrimSpace(c.Query("billing_mode"))
|
||||||
|
|
||||||
var requestType *int16
|
var requestType *int16
|
||||||
var stream *bool
|
var stream *bool
|
||||||
@@ -159,8 +160,8 @@ func (h *UsageHandler) List(c *gin.Context) {
|
|||||||
response.BadRequest(c, "Invalid end_date format, use YYYY-MM-DD")
|
response.BadRequest(c, "Invalid end_date format, use YYYY-MM-DD")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Set end time to end of day
|
// Use half-open range [start, end), move to next calendar day start (DST-safe).
|
||||||
t = t.Add(24*time.Hour - time.Nanosecond)
|
t = t.AddDate(0, 0, 1)
|
||||||
endTime = &t
|
endTime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +175,7 @@ func (h *UsageHandler) List(c *gin.Context) {
|
|||||||
RequestType: requestType,
|
RequestType: requestType,
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
BillingType: billingType,
|
BillingType: billingType,
|
||||||
|
BillingMode: billingMode,
|
||||||
StartTime: startTime,
|
StartTime: startTime,
|
||||||
EndTime: endTime,
|
EndTime: endTime,
|
||||||
ExactTotal: exactTotal,
|
ExactTotal: exactTotal,
|
||||||
@@ -234,6 +236,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model := c.Query("model")
|
model := c.Query("model")
|
||||||
|
billingMode := strings.TrimSpace(c.Query("billing_mode"))
|
||||||
|
|
||||||
var requestType *int16
|
var requestType *int16
|
||||||
var stream *bool
|
var stream *bool
|
||||||
@@ -285,7 +288,8 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
|||||||
response.BadRequest(c, "Invalid end_date format, use YYYY-MM-DD")
|
response.BadRequest(c, "Invalid end_date format, use YYYY-MM-DD")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
endTime = endTime.Add(24*time.Hour - time.Nanosecond)
|
// 与 SQL 条件 created_at < end 对齐,使用次日 00:00 作为上边界(DST-safe)。
|
||||||
|
endTime = endTime.AddDate(0, 0, 1)
|
||||||
} else {
|
} else {
|
||||||
period := c.DefaultQuery("period", "today")
|
period := c.DefaultQuery("period", "today")
|
||||||
switch period {
|
switch period {
|
||||||
@@ -311,6 +315,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
|||||||
RequestType: requestType,
|
RequestType: requestType,
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
BillingType: billingType,
|
BillingType: billingType,
|
||||||
|
BillingMode: billingMode,
|
||||||
StartTime: &startTime,
|
StartTime: &startTime,
|
||||||
EndTime: &endTime,
|
EndTime: &endTime,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type UpdateBalanceRequest struct {
|
|||||||
// - role: filter by user role
|
// - role: filter by user role
|
||||||
// - search: search in email, username
|
// - search: search in email, username
|
||||||
// - attr[{id}]: filter by custom attribute value, e.g. attr[1]=company
|
// - attr[{id}]: filter by custom attribute value, e.g. attr[1]=company
|
||||||
|
// - group_name: fuzzy filter by allowed group name
|
||||||
func (h *UserHandler) List(c *gin.Context) {
|
func (h *UserHandler) List(c *gin.Context) {
|
||||||
page, pageSize := response.ParsePagination(c)
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ func (h *UserHandler) List(c *gin.Context) {
|
|||||||
Status: c.Query("status"),
|
Status: c.Query("status"),
|
||||||
Role: c.Query("role"),
|
Role: c.Query("role"),
|
||||||
Search: search,
|
Search: search,
|
||||||
|
GroupName: strings.TrimSpace(c.Query("group_name")),
|
||||||
Attributes: parseAttributeFilters(c),
|
Attributes: parseAttributeFilters(c),
|
||||||
}
|
}
|
||||||
if raw, ok := c.GetQuery("include_subscriptions"); ok {
|
if raw, ok := c.GetQuery("include_subscriptions"); ok {
|
||||||
@@ -366,3 +368,35 @@ func (h *UserHandler) GetBalanceHistory(c *gin.Context) {
|
|||||||
"total_recharged": totalRecharged,
|
"total_recharged": totalRecharged,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplaceGroupRequest represents the request to replace a user's exclusive group
|
||||||
|
type ReplaceGroupRequest struct {
|
||||||
|
OldGroupID int64 `json:"old_group_id" binding:"required,gt=0"`
|
||||||
|
NewGroupID int64 `json:"new_group_id" binding:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceGroup handles replacing a user's exclusive group
|
||||||
|
// POST /api/v1/admin/users/:id/replace-group
|
||||||
|
func (h *UserHandler) ReplaceGroup(c *gin.Context) {
|
||||||
|
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req ReplaceGroupRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.adminService.ReplaceUserGroup(c.Request.Context(), userID, req.OldGroupID, req.NewGroupID)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, gin.H{
|
||||||
|
"migrated_keys": result.MigratedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -194,6 +194,12 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend mode: only admin can login
|
||||||
|
if h.settingSvc.IsBackendModeEnabled(c.Request.Context()) && !user.IsAdmin() {
|
||||||
|
response.Forbidden(c, "Backend mode is active. Only admin login is allowed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h.respondWithTokenPair(c, user)
|
h.respondWithTokenPair(c, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,16 +256,22 @@ func (h *AuthHandler) Login2FA(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the login session
|
// Get the user (before session deletion so we can check backend mode)
|
||||||
_ = h.totpService.DeleteLoginSession(c.Request.Context(), req.TempToken)
|
|
||||||
|
|
||||||
// Get the user
|
|
||||||
user, err := h.userService.GetByID(c.Request.Context(), session.UserID)
|
user, err := h.userService.GetByID(c.Request.Context(), session.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend mode: only admin can login (check BEFORE deleting session)
|
||||||
|
if h.settingSvc.IsBackendModeEnabled(c.Request.Context()) && !user.IsAdmin() {
|
||||||
|
response.Forbidden(c, "Backend mode is active. Only admin login is allowed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the login session (only after all checks pass)
|
||||||
|
_ = h.totpService.DeleteLoginSession(c.Request.Context(), req.TempToken)
|
||||||
|
|
||||||
h.respondWithTokenPair(c, user)
|
h.respondWithTokenPair(c, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,9 +459,9 @@ func (h *AuthHandler) ForgotPassword(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
frontendBaseURL := strings.TrimSpace(h.cfg.Server.FrontendURL)
|
frontendBaseURL := strings.TrimSpace(h.settingSvc.GetFrontendURL(c.Request.Context()))
|
||||||
if frontendBaseURL == "" {
|
if frontendBaseURL == "" {
|
||||||
slog.Error("server.frontend_url not configured; cannot build password reset link")
|
slog.Error("frontend_url not configured in settings or config; cannot build password reset link")
|
||||||
response.InternalError(c, "Password reset is not configured")
|
response.InternalError(c, "Password reset is not configured")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -522,16 +534,22 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPair, err := h.authService.RefreshTokenPair(c.Request.Context(), req.RefreshToken)
|
result, err := h.authService.RefreshTokenPair(c.Request.Context(), req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend mode: block non-admin token refresh
|
||||||
|
if h.settingSvc.IsBackendModeEnabled(c.Request.Context()) && result.UserRole != "admin" {
|
||||||
|
response.Forbidden(c, "Backend mode is active. Only admin login is allowed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
response.Success(c, RefreshTokenResponse{
|
response.Success(c, RefreshTokenResponse{
|
||||||
AccessToken: tokenPair.AccessToken,
|
AccessToken: result.AccessToken,
|
||||||
RefreshToken: tokenPair.RefreshToken,
|
RefreshToken: result.RefreshToken,
|
||||||
ExpiresIn: tokenPair.ExpiresIn,
|
ExpiresIn: result.ExpiresIn,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,8 +211,22 @@ func (h *AuthHandler) LinuxDoOAuthCallback(c *gin.Context) {
|
|||||||
email = linuxDoSyntheticEmail(subject)
|
email = linuxDoSyntheticEmail(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username)
|
// 传入空邀请码;如果需要邀请码,服务层返回 ErrOAuthInvitationRequired
|
||||||
|
tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, service.ErrOAuthInvitationRequired) {
|
||||||
|
pendingToken, tokenErr := h.authService.CreatePendingOAuthToken(email, username)
|
||||||
|
if tokenErr != nil {
|
||||||
|
redirectOAuthError(c, frontendCallback, "login_failed", "service_error", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fragment := url.Values{}
|
||||||
|
fragment.Set("error", "invitation_required")
|
||||||
|
fragment.Set("pending_oauth_token", pendingToken)
|
||||||
|
fragment.Set("redirect", redirectTo)
|
||||||
|
redirectWithFragment(c, frontendCallback, fragment)
|
||||||
|
return
|
||||||
|
}
|
||||||
// 避免把内部细节泄露给客户端;给前端保留结构化原因与提示信息即可。
|
// 避免把内部细节泄露给客户端;给前端保留结构化原因与提示信息即可。
|
||||||
redirectOAuthError(c, frontendCallback, "login_failed", infraerrors.Reason(err), infraerrors.Message(err))
|
redirectOAuthError(c, frontendCallback, "login_failed", infraerrors.Reason(err), infraerrors.Message(err))
|
||||||
return
|
return
|
||||||
@@ -227,6 +241,41 @@ func (h *AuthHandler) LinuxDoOAuthCallback(c *gin.Context) {
|
|||||||
redirectWithFragment(c, frontendCallback, fragment)
|
redirectWithFragment(c, frontendCallback, fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type completeLinuxDoOAuthRequest struct {
|
||||||
|
PendingOAuthToken string `json:"pending_oauth_token" binding:"required"`
|
||||||
|
InvitationCode string `json:"invitation_code" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompleteLinuxDoOAuthRegistration completes a pending OAuth registration by validating
|
||||||
|
// the invitation code and creating the user account.
|
||||||
|
// POST /api/v1/auth/oauth/linuxdo/complete-registration
|
||||||
|
func (h *AuthHandler) CompleteLinuxDoOAuthRegistration(c *gin.Context) {
|
||||||
|
var req completeLinuxDoOAuthRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "INVALID_REQUEST", "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email, username, err := h.authService.VerifyPendingOAuthToken(req.PendingOAuthToken)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "INVALID_TOKEN", "message": "invalid or expired registration token"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, req.InvitationCode)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"access_token": tokenPair.AccessToken,
|
||||||
|
"refresh_token": tokenPair.RefreshToken,
|
||||||
|
"expires_in": tokenPair.ExpiresIn,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) getLinuxDoOAuthConfig(ctx context.Context) (config.LinuxDoConnectConfig, error) {
|
func (h *AuthHandler) getLinuxDoOAuthConfig(ctx context.Context) (config.LinuxDoConnectConfig, error) {
|
||||||
if h != nil && h.settingSvc != nil {
|
if h != nil && h.settingSvc != nil {
|
||||||
return h.settingSvc.GetLinuxDoConnectOAuthConfig(ctx)
|
return h.settingSvc.GetLinuxDoConnectOAuthConfig(ctx)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type Announcement struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
NotifyMode string `json:"notify_mode"`
|
||||||
|
|
||||||
Targeting service.AnnouncementTargeting `json:"targeting"`
|
Targeting service.AnnouncementTargeting `json:"targeting"`
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ type UserAnnouncement struct {
|
|||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
NotifyMode string `json:"notify_mode"`
|
||||||
|
|
||||||
StartsAt *time.Time `json:"starts_at,omitempty"`
|
StartsAt *time.Time `json:"starts_at,omitempty"`
|
||||||
EndsAt *time.Time `json:"ends_at,omitempty"`
|
EndsAt *time.Time `json:"ends_at,omitempty"`
|
||||||
@@ -47,6 +49,7 @@ func AnnouncementFromService(a *service.Announcement) *Announcement {
|
|||||||
Title: a.Title,
|
Title: a.Title,
|
||||||
Content: a.Content,
|
Content: a.Content,
|
||||||
Status: a.Status,
|
Status: a.Status,
|
||||||
|
NotifyMode: a.NotifyMode,
|
||||||
Targeting: a.Targeting,
|
Targeting: a.Targeting,
|
||||||
StartsAt: a.StartsAt,
|
StartsAt: a.StartsAt,
|
||||||
EndsAt: a.EndsAt,
|
EndsAt: a.EndsAt,
|
||||||
@@ -65,6 +68,7 @@ func UserAnnouncementFromService(a *service.UserAnnouncement) *UserAnnouncement
|
|||||||
ID: a.Announcement.ID,
|
ID: a.Announcement.ID,
|
||||||
Title: a.Announcement.Title,
|
Title: a.Announcement.Title,
|
||||||
Content: a.Announcement.Content,
|
Content: a.Announcement.Content,
|
||||||
|
NotifyMode: a.Announcement.NotifyMode,
|
||||||
StartsAt: a.Announcement.StartsAt,
|
StartsAt: a.Announcement.StartsAt,
|
||||||
EndsAt: a.Announcement.EndsAt,
|
EndsAt: a.Announcement.EndsAt,
|
||||||
ReadAt: a.ReadAt,
|
ReadAt: a.ReadAt,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user