diff --git a/go.mod b/go.mod index 26b94b9..e59a1a9 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/coder/websocket v1.8.12 github.com/duke-git/lancet/v2 v2.3.3 github.com/faiface/beep v1.1.0 + github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.0 github.com/glebarez/sqlite v1.11.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -15,6 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.9 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkoukk/tiktoken-go v0.1.7 github.com/sashabaranov/go-openai v1.32.2 @@ -22,6 +24,8 @@ require ( golang.org/x/sync v0.8.0 google.golang.org/api v0.201.0 gopkg.in/vansante/go-ffprobe.v2 v2.2.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/postgres v1.5.11 gorm.io/gorm v1.25.12 ) @@ -34,6 +38,7 @@ require ( cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/iam v1.2.1 // indirect cloud.google.com/go/longrunning v0.6.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -42,7 +47,6 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect - github.com/gin-contrib/cors v1.7.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -50,6 +54,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect @@ -57,10 +62,15 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hajimehoshi/go-mp3 v0.3.4 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 3548eb6..f1bb6ab 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= -cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= cloud.google.com/go/ai v0.8.2 h1:LEaQwqBv+k2ybrcdTtCTc9OPZXoEdcQaGrfvDYS6Bnk= cloud.google.com/go/ai v0.8.2/go.mod h1:Wb3EUUGWwB6yHBaUf/+oxUq/6XbCaU1yh0GrwUS8lr4= cloud.google.com/go/aiplatform v1.68.0 h1:EPPqgHDJpBZKRvv+OsB3cr0jYz3EL2pZ+802rBPcG8U= @@ -13,7 +9,6 @@ cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8= cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/compute v1.28.1 h1:XwPcZjgMCnU2tkwY10VleUjSAfpTj9RDn+kGrbYsi8o= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= @@ -22,15 +17,14 @@ cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTS cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/vertexai v0.13.1 h1:E6I+eA6vNQxz7/rb0wdILdKg4hFmMNWZLp+dSy9DnEo= cloud.google.com/go/vertexai v0.13.1/go.mod h1:25DzKFzP9JByYxcNjJefu/px2dRjcRpCDSdULYL2avI= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d h1:3v1QFdgk450QH+7C+lw1k+olbjK4fKGsrEfnEG/HLkY= github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d/go.mod h1:2sp0vsMyh5sqmKl5N+ps/cSspqLkoXUlesSzsufIGRU= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -43,14 +37,13 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/duke-git/lancet/v2 v2.3.2 h1:Cv+uNkx5yGqDSvGc5Vu9eiiZobsPIf0Ng7NGy5hEdow= -github.com/duke-git/lancet/v2 v2.3.2/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc= github.com/duke-git/lancet/v2 v2.3.3 h1:OhqzNzkbJBS9ZlWLo/C7g+WSAOAAyNj7p9CAiEHurUc= github.com/duke-git/lancet/v2 v2.3.3/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -63,8 +56,6 @@ github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= @@ -75,8 +66,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= -github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -95,10 +84,11 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -143,7 +133,6 @@ github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDP github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= @@ -152,6 +141,14 @@ github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6Ee github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -163,13 +160,17 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -185,8 +186,6 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -199,20 +198,18 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4= -github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sashabaranov/go-openai v1.32.2 h1:8z9PfYaLPbRzmJIYpwcWu6z3XU8F+RwVMF1QRSeSF2M= github.com/sashabaranov/go-openai v1.32.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -227,29 +224,16 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -310,8 +294,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.200.0 h1:0ytfNWn101is6e9VBoct2wrGDjOi5vn7jw5KtaQgDrU= -google.golang.org/api v0.200.0/go.mod h1:Tc5u9kcbjO7A8SwGlYj4IiVifJU01UqXtEgDMYmBmV8= google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0= google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -321,12 +303,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20241007155032-5fefd90f89a9 h1:nFS3IivktIU5Mk6KQa+v6RKkHUpdQpphqGNLxqNnbEk= google.golang.org/genproto v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:tEzYTYZxbmVNOu0OAFH9HzdJtLn6h4Aj89zzlBCdHms= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -347,13 +325,19 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/vansante/go-ffprobe.v2 v2.2.0 h1:iuOqTsbfYuqIz4tAU9NWh22CmBGxlGHdgj4iqP+NUmY= gopkg.in/vansante/go-ffprobe.v2 v2.2.0/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -383,4 +367,3 @@ modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/store/db.go b/pkg/store/db.go new file mode 100644 index 0000000..862b90f --- /dev/null +++ b/pkg/store/db.go @@ -0,0 +1,80 @@ +package store + +import ( + "fmt" + "log" + "opencatd-open/team/model" + "os" + "strings" + + // "gocloud.dev/mysql" + // "gocloud.dev/postgres" + "github.com/glebarez/sqlite" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var IsPostgres bool + +// InitDB 初始化数据库连接 +func InitDB() (*gorm.DB, error) { + var db *gorm.DB + var err error + // 从环境变量获取DSN + dsn := os.Getenv("DSN") + + if dsn == "" { + log.Println("No DSN provided, using SQLite as default") + db, err = initSQLite() + } + + // 解析DSN来确定数据库类型 + if strings.HasPrefix(dsn, "postgres://") { + IsPostgres = true + db, err = initPostgres(dsn) + } else if strings.HasPrefix(dsn, "mysql://") { + db, err = initMySQL(dsn) + } + if err != nil { + return nil, err + } + if IsPostgres { + err = db.AutoMigrate(&model.User{}, &model.ApiKey_PG{}, &model.Token{}, &model.Session{}, &model.Usage{}, &model.DailyUsage{}) + if err != nil { + return nil, err + } + } + + return nil, fmt.Errorf("unsupported database type in DSN: %s", dsn) +} + +// initSQLite 初始化 SQLite 数据库 +func initSQLite() (*gorm.DB, error) { + db, err := gorm.Open(sqlite.Open("openteam.db"), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to SQLite: %v", err) + } + return db, nil +} + +// initPostgres 初始化 PostgreSQL 数据库 +func initPostgres(dsn string) (*gorm.DB, error) { + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to PostgreSQL: %v", err) + } + return db, nil +} + +// initMySQL 初始化 MySQL 数据库 +func initMySQL(dsn string) (*gorm.DB, error) { + // 移除 "mysql://" 前缀,因为 MySQL 驱动不需要这个前缀 + dsn = strings.TrimPrefix(dsn, "mysql://") + + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to MySQL: %v", err) + } + return db, nil +} diff --git a/router/router.go b/router/router.go index 9930b78..d8c0d62 100644 --- a/router/router.go +++ b/router/router.go @@ -13,51 +13,6 @@ import ( "github.com/gin-gonic/gin" ) -var ( - GPT3Dot5Turbo = "gpt-3.5-turbo" - GPT4 = "gpt-4" -) - -// type ChatCompletionMessage struct { -// Role string `json:"role"` -// Content string `json:"content"` -// Name string `json:"name,omitempty"` -// } - -// type ChatCompletionRequest struct { -// Model string `json:"model"` -// Messages []ChatCompletionMessage `json:"messages"` -// MaxTokens int `json:"max_tokens,omitempty"` -// Temperature float32 `json:"temperature,omitempty"` -// TopP float32 `json:"top_p,omitempty"` -// N int `json:"n,omitempty"` -// Stream bool `json:"stream,omitempty"` -// Stop []string `json:"stop,omitempty"` -// PresencePenalty float32 `json:"presence_penalty,omitempty"` -// FrequencyPenalty float32 `json:"frequency_penalty,omitempty"` -// LogitBias map[string]int `json:"logit_bias,omitempty"` -// User string `json:"user,omitempty"` -// } - -// type ChatCompletionChoice struct { -// Index int `json:"index"` -// Message ChatCompletionMessage `json:"message"` -// FinishReason string `json:"finish_reason"` -// } - -// type ChatCompletionResponse struct { -// ID string `json:"id"` -// Object string `json:"object"` -// Created int64 `json:"created"` -// Model string `json:"model"` -// Choices []ChatCompletionChoice `json:"choices"` -// Usage struct { -// PromptTokens int `json:"prompt_tokens"` -// CompletionTokens int `json:"completion_tokens"` -// TotalTokens int `json:"total_tokens"` -// } `json:"usage"` -// } - func HandleProxy(c *gin.Context) { var ( localuser bool diff --git a/store/keydb.go b/store/keydb.go index 02722f5..e5b0f81 100644 --- a/store/keydb.go +++ b/store/keydb.go @@ -36,11 +36,14 @@ type Key struct { ID uint `gorm:"primarykey" json:"id,omitempty"` Key string `gorm:"unique;not null" json:"key,omitempty"` Name string `gorm:"unique;not null" json:"name,omitempty"` + ModelAlias string `gorm:"column:model_alias"` UserId string `json:"-,omitempty"` ApiType string `gorm:"column:api_type"` EndPoint string `gorm:"column:endpoint"` ResourceNmae string `gorm:"column:resource_name"` DeploymentName string `gorm:"column:deployment_name"` + ModelPrefix string `gorm:"column:model_prefix"` + SupportModel string `gorm:"column:support_model"` ApiSecret string `gorm:"column:api_secret"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` diff --git a/store/usage.go b/store/usage.go index f5aa543..2e821f3 100644 --- a/store/usage.go +++ b/store/usage.go @@ -9,14 +9,15 @@ import ( ) type DailyUsage struct { - ID int `gorm:"column:id"` - UserID int `gorm:"column:user_id";primaryKey` + ID int64 `gorm:"column:id"` + UserID int64 `gorm:"column:user_id;primaryKey" json:"user_id"` Date time.Time `gorm:"column:date"` SKU string `gorm:"column:sku"` PromptUnits int `gorm:"column:prompt_units"` CompletionUnits int `gorm:"column:completion_units"` TotalUnit int `gorm:"column:total_unit"` Cost string `gorm:"column:cost"` + CreatedAt time.Time `json:"createdAt,omitempty"` } func (DailyUsage) TableName() string { @@ -39,13 +40,6 @@ func (Usage) TableName() string { return "usages" } -type Summary struct { - UserId int `gorm:"column:user_id"` - SumPromptUnits int `gorm:"column:sum_prompt_units"` - SumCompletionUnits int `gorm:"column:sum_completion_units"` - SumTotalUnit int `gorm:"column:sum_total_unit"` - SumCost float64 `gorm:"column:sum_cost"` -} type CalcUsage struct { UserID int `json:"userId,omitempty"` TotalUnit int `json:"totalUnit,omitempty"` diff --git a/store/userdb.go b/store/userdb.go index 8db3f05..1885288 100644 --- a/store/userdb.go +++ b/store/userdb.go @@ -2,16 +2,13 @@ package store import ( "time" - - "gorm.io/gorm" ) type User struct { - gorm.Model IsDelete bool `gorm:"default:false" json:"IsDelete"` - ID uint `gorm:"primarykey autoIncrement;" json:"id,omitempty"` + ID int64 `gorm:"primaryKey;autoIncrement" json:"id,omitempty"` Name string `gorm:"unique;not null" json:"name,omitempty"` - Token string `gorm:"unique;not null" json:"token,omitempty"` + Token string `gorm:"unique;not null;index" json:"token,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` // DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` diff --git a/team/consts/consts.go b/team/consts/consts.go new file mode 100644 index 0000000..d74ffd9 --- /dev/null +++ b/team/consts/consts.go @@ -0,0 +1,17 @@ +package consts + +const ( + RoleGuest = iota * 10 + RoleUser + RoleAdmin + RoleSuperAdmin +) + +const ( + StatusEnabled = iota + StatusDisabled + StatusExpired // 过期 + StatusExhausted // 耗尽 + StatusDeleted +) +const UnlimitedQuota = -1 diff --git a/team/dao/apikey.go b/team/dao/apikey.go new file mode 100644 index 0000000..fbf2d0a --- /dev/null +++ b/team/dao/apikey.go @@ -0,0 +1,145 @@ +package dao + +import ( + "errors" + "opencatd-open/team/model" + "time" + + "gorm.io/gorm" +) + +var _ ApiKeyRepository = (*ApiKeyDAO)(nil) + +type ApiKeyRepository interface { + Create(apiKey *model.ApiKey) error + GetByID(id int64) (*model.ApiKey, error) + GetByName(name string) (*model.ApiKey, error) + GetByApiKey(apiKeyValue string) (*model.ApiKey, error) + Update(apiKey *model.ApiKey) error + Delete(id int64) error + List(offset, limit int, status *int) ([]model.ApiKey, error) + Enable(id int64) error + Disable(id int64) error + BatchEnable(ids []int64) error + BatchDisable(ids []int64) error + BatchDelete(ids []int64) error + Count() (int64, error) +} + +type ApiKeyDAO struct { + db *gorm.DB +} + +func NewApiKeyDAO(db *gorm.DB) *ApiKeyDAO { + return &ApiKeyDAO{db: db} +} + +// CreateApiKey 创建ApiKey +func (dao *ApiKeyDAO) Create(apiKey *model.ApiKey) error { + if apiKey == nil { + return errors.New("apiKey is nil") + } + return dao.db.Create(apiKey).Error +} + +// GetApiKeyByID 根据ID获取ApiKey +func (dao *ApiKeyDAO) GetByID(id int64) (*model.ApiKey, error) { + var apiKey model.ApiKey + err := dao.db.First(&apiKey, id).Error + if err != nil { + return nil, err + } + return &apiKey, nil +} + +// GetApiKeyByName 根据名称获取ApiKey +func (dao *ApiKeyDAO) GetByName(name string) (*model.ApiKey, error) { + var apiKey model.ApiKey + err := dao.db.Where("name = ?", name).First(&apiKey).Error + if err != nil { + return nil, err + } + return &apiKey, nil +} + +// GetApiKeyByApiKey 根据ApiKey值获取ApiKey +func (dao *ApiKeyDAO) GetByApiKey(apiKeyValue string) (*model.ApiKey, error) { + var apiKey model.ApiKey + err := dao.db.Where("api_key = ?", apiKeyValue).First(&apiKey).Error + if err != nil { + return nil, err + } + return &apiKey, nil +} + +// UpdateApiKey 更新ApiKey信息 +func (dao *ApiKeyDAO) Update(apiKey *model.ApiKey) error { + if apiKey == nil { + return errors.New("apiKey is nil") + } + apiKey.UpdatedAt = time.Now() + return dao.db.Save(apiKey).Error +} + +// DeleteApiKey 删除ApiKey +func (dao *ApiKeyDAO) Delete(id int64) error { + return dao.db.Delete(&model.ApiKey{}, id).Error +} + +// ListApiKeys 获取ApiKey列表 +func (dao *ApiKeyDAO) List(offset, limit int, status *int) ([]model.ApiKey, error) { + var apiKeys []model.ApiKey + db := dao.db.Offset(offset).Limit(limit) + if status != nil { + db = db.Where("status = ?", *status) + } + err := db.Find(&apiKeys).Error + if err != nil { + return nil, err + } + return apiKeys, nil +} + +// EnableApiKey 启用ApiKey +func (dao *ApiKeyDAO) Enable(id int64) error { + return dao.db.Model(&model.ApiKey{}).Where("id = ?", id).Update("status", 0).Error +} + +// DisableApiKey 禁用ApiKey +func (dao *ApiKeyDAO) Disable(id int64) error { + return dao.db.Model(&model.ApiKey{}).Where("id = ?", id).Update("status", 1).Error +} + +// BatchEnableApiKeys 批量启用ApiKey +func (dao *ApiKeyDAO) BatchEnable(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Model(&model.ApiKey{}).Where("id IN ?", ids).Update("status", 0).Error +} + +// BatchDisableApiKeys 批量禁用ApiKey +func (dao *ApiKeyDAO) BatchDisable(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Model(&model.ApiKey{}).Where("id IN ?", ids).Update("status", 1).Error +} + +// BatchDeleteApiKey 批量删除ApiKey +func (dao *ApiKeyDAO) BatchDelete(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Delete(&model.ApiKey{}, ids).Error +} + +// CountApiKeys 获取ApiKey总数 +func (dao *ApiKeyDAO) Count() (int64, error) { + var count int64 + err := dao.db.Model(&model.ApiKey{}).Count(&count).Error + if err != nil { + return 0, err + } + return count, nil +} diff --git a/team/dao/token.go b/team/dao/token.go new file mode 100644 index 0000000..ec10335 --- /dev/null +++ b/team/dao/token.go @@ -0,0 +1,120 @@ +package dao + +import ( + "errors" + "opencatd-open/team/model" + + "gorm.io/gorm" +) + +// 确保 TokenDAO 实现了 TokenDAOInterface 接口 +var _ TokenDAOInterface = (*TokenDAO)(nil) + +type TokenDAOInterface interface { + Create(token *model.Token) error + GetByID(id int) (*model.Token, error) + GetByKey(key string) (*model.Token, error) + GetByUserID(userID int) ([]model.Token, error) + Update(token *model.Token) error + Delete(id int) error + List(offset, limit int) ([]model.Token, error) + Disable(id int) error + Enable(id int) error + BatchDisable(ids []int) error + BatchEnable(ids []int) error + BatchDelete(ids []int) error +} + +type TokenDAO struct { + db *gorm.DB +} + +func NewTokenDAO(db *gorm.DB) *TokenDAO { + return &TokenDAO{db: db} +} + +// CreateToken 创建 Token +func (dao *TokenDAO) Create(token *model.Token) error { + if token == nil { + return errors.New("token is nil") + } + return dao.db.Create(token).Error +} + +// GetTokenByID 根据 ID 获取 Token +func (dao *TokenDAO) GetByID(id int) (*model.Token, error) { + var token model.Token + err := dao.db.First(&token, id).Error + if err != nil { + return nil, err + } + return &token, nil +} + +// GetTokenByKey 根据 Key 获取 Token +func (dao *TokenDAO) GetByKey(key string) (*model.Token, error) { + var token model.Token + err := dao.db.Where("key = ?", key).First(&token).Error + if err != nil { + return nil, err + } + return &token, nil +} + +// GetTokensByUserID 根据 UserID 获取 Token 列表 +func (dao *TokenDAO) GetByUserID(userID int) ([]model.Token, error) { + var tokens []model.Token + err := dao.db.Where("user_id = ?", userID).Find(&tokens).Error + if err != nil { + return nil, err + } + return tokens, nil +} + +// UpdateToken 更新 Token 信息 +func (dao *TokenDAO) Update(token *model.Token) error { + if token == nil { + return errors.New("token is nil") + } + return dao.db.Save(token).Error +} + +// DeleteToken 删除 Token +func (dao *TokenDAO) Delete(id int) error { + return dao.db.Delete(&model.Token{}, id).Error +} + +// ListTokens 获取 Token 列表 +func (dao *TokenDAO) List(offset, limit int) ([]model.Token, error) { + var tokens []model.Token + err := dao.db.Offset(offset).Limit(limit).Find(&tokens).Error + if err != nil { + return nil, err + } + return tokens, nil +} + +// DisableToken 禁用 Token +func (dao *TokenDAO) Disable(id int) error { + return dao.db.Model(&model.Token{}).Where("id = ?", id).Update("status", false).Error +} + +// EnableToken 启用 Token +func (dao *TokenDAO) Enable(id int) error { + return dao.db.Model(&model.Token{}).Where("id = ?", id).Update("status", true).Error +} + +// BatchDisableTokens 批量禁用 Token +func (dao *TokenDAO) BatchDisable(ids []int) error { + return dao.db.Model(&model.Token{}).Where("id IN ?", ids).Update("status", false).Error +} + +// BatchEnableTokens 批量启用 Token +func (dao *TokenDAO) BatchEnable(ids []int) error { + return dao.db.Model(&model.Token{}).Where("id IN ?", ids).Update("status", true).Error +} + +// BatchDeleteTokens 批量删除 Token +func (dao *TokenDAO) BatchDelete(ids []int) error { + return dao.db.Where("id IN ?", ids).Delete(&model.Token{}).Error +} diff --git a/team/dao/user.go b/team/dao/user.go new file mode 100644 index 0000000..93d3373 --- /dev/null +++ b/team/dao/user.go @@ -0,0 +1,123 @@ +package dao + +import ( + "errors" + "opencatd-open/team/model" + "time" + + "gorm.io/gorm" +) + +// 确保 UserDAO 实现了 UserRepository 接口 +var _ UserRepository = (*UserDAO)(nil) + +// UserRepository 定义用户数据访问操作的接口 +type UserRepository interface { + Create(user *model.User) error + GetByID(id int64) (*model.User, error) + GetByUsername(username string) (*model.User, error) + Update(user *model.User) error + Delete(id int64) error + List(offset, limit int) ([]model.User, error) + Enable(id int64) error + Disable(id int64) error + BatchEnable(ids []int64) error + BatchDisable(ids []int64) error + BatchDelete(ids []int64) error +} + +type UserDAO struct { + db *gorm.DB +} + +func NewUserDAO(db *gorm.DB) *UserDAO { + return &UserDAO{db: db} +} + +// CreateUser 创建用户 +func (dao *UserDAO) Create(user *model.User) error { + if user == nil { + return errors.New("user is nil") + } + return dao.db.Create(user).Error +} + +// GetUserByID 根据ID获取用户 +func (dao *UserDAO) GetByID(id int64) (*model.User, error) { + var user model.User + err := dao.db.First(&user, id).Error + if err != nil { + return nil, err + } + return &user, nil +} + +// GetUserByUsername 根据用户名获取用户 +func (dao *UserDAO) GetByUsername(username string) (*model.User, error) { + var user model.User + err := dao.db.Where("user_name = ?", username).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +// UpdateUser 更新用户信息 +func (dao *UserDAO) Update(user *model.User) error { + if user == nil { + return errors.New("user is nil") + } + user.UpdatedAt = time.Now() + return dao.db.Save(user).Error +} + +// DeleteUser 删除用户 +func (dao *UserDAO) Delete(id int64) error { + return dao.db.Delete(&model.User{}, id).Error + // return dao.db.Model(&model.User{}).Where("id = ?", id).Update("status", 2).Error +} + +// ListUsers 获取用户列表 +func (dao *UserDAO) List(offset, limit int) ([]model.User, error) { + var users []model.User + err := dao.db.Offset(offset).Limit(limit).Find(&users).Error + if err != nil { + return nil, err + } + return users, nil +} + +// EnableUser 启用User +func (dao *UserDAO) Enable(id int64) error { + return dao.db.Model(&model.User{}).Where("id = ?", id).Update("status", 0).Error +} + +// DisableUser 禁用User +func (dao *UserDAO) Disable(id int64) error { + return dao.db.Model(&model.User{}).Where("id = ?", id).Update("status", 1).Error +} + +// BatchEnableUsers 批量启用User +func (dao *UserDAO) BatchEnable(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Model(&model.User{}).Where("id IN ?", ids).Update("status", 0).Error +} + +// BatchDisableUsers 批量禁用User +func (dao *UserDAO) BatchDisable(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Model(&model.User{}).Where("id IN ?", ids).Update("status", 1).Error +} + +// BatchDeleteUser 批量删除用户 +func (dao *UserDAO) BatchDelete(ids []int64) error { + if len(ids) == 0 { + return errors.New("ids is empty") + } + return dao.db.Where("id IN ?", ids).Delete(&model.User{}).Error + // return dao.db.Model(&model.User{}).Where("id IN ?", ids).Update("status", 2).Error +} diff --git a/team/key.go b/team/key.go new file mode 100644 index 0000000..802f10b --- /dev/null +++ b/team/key.go @@ -0,0 +1,182 @@ +package team + +import ( + "net/http" + "opencatd-open/pkg/azureopenai" + "opencatd-open/store" + "strings" + + "github.com/Sakurasan/to" + "github.com/gin-gonic/gin" +) + +type Key struct { + ID int `json:"id,omitempty"` + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + ApiType string `json:"api_type,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + UpdatedAt string `json:"updatedAt,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` +} + +func HandleKeys(c *gin.Context) { + keys, err := store.GetAllKeys() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "error": err.Error(), + }) + } + + c.JSON(http.StatusOK, keys) +} + +func HandleAddKey(c *gin.Context) { + var body Key + if err := c.BindJSON(&body); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + body.Name = strings.ToLower(strings.TrimSpace(body.Name)) + body.Key = strings.TrimSpace(body.Key) + if strings.HasPrefix(body.Name, "azure.") { + keynames := strings.Split(body.Name, ".") + if len(keynames) < 2 { + c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{ + "message": "Invalid Key Name", + }}) + return + } + k := &store.Key{ + ApiType: "azure", + Name: body.Name, + Key: body.Key, + ResourceNmae: keynames[1], + EndPoint: body.Endpoint, + } + if err := store.CreateKey(k); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } else if strings.HasPrefix(body.Name, "claude.") { + keynames := strings.Split(body.Name, ".") + if len(keynames) < 2 { + c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{ + "message": "Invalid Key Name", + }}) + return + } + if body.Endpoint == "" { + body.Endpoint = "https://api.anthropic.com" + } + k := &store.Key{ + // ApiType: "anthropic", + ApiType: "claude", + Name: body.Name, + Key: body.Key, + ResourceNmae: keynames[1], + EndPoint: body.Endpoint, + } + if err := store.CreateKey(k); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } else if strings.HasPrefix(body.Name, "google.") { + keynames := strings.Split(body.Name, ".") + if len(keynames) < 2 { + c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{ + "message": "Invalid Key Name", + }}) + return + } + + k := &store.Key{ + // ApiType: "anthropic", + ApiType: "google", + Name: body.Name, + Key: body.Key, + ResourceNmae: keynames[1], + EndPoint: body.Endpoint, + } + if err := store.CreateKey(k); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } else if strings.HasPrefix(body.Name, "github.") { + keynames := strings.Split(body.Name, ".") + if len(keynames) < 2 { + c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{ + "message": "Invalid Key Name", + }}) + return + } + + k := &store.Key{ + ApiType: "github", + Name: body.Name, + Key: body.Key, + ResourceNmae: keynames[1], + EndPoint: body.Endpoint, + } + if err := store.CreateKey(k); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } else { + if body.ApiType == "" { + if err := store.AddKey("openai", body.Key, body.Name); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } else { + k := &store.Key{ + ApiType: body.ApiType, + Name: body.Name, + Key: body.Key, + ResourceNmae: azureopenai.GetResourceName(body.Endpoint), + EndPoint: body.Endpoint, + } + if err := store.CreateKey(k); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + } + + } + + k, err := store.GetKeyrByName(body.Name) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{ + "message": err.Error(), + }}) + return + } + c.JSON(http.StatusOK, k) +} + +func HandleDelKey(c *gin.Context) { + id := to.Int(c.Param("id")) + if id < 1 { + c.JSON(http.StatusOK, gin.H{"error": "invalid key id"}) + return + } + if err := store.DeleteKey(uint(id)); err != nil { + c.JSON(http.StatusOK, gin.H{"error": "invalid key id"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "ok"}) +} diff --git a/team/me.go b/team/me.go new file mode 100644 index 0000000..cea2c43 --- /dev/null +++ b/team/me.go @@ -0,0 +1,89 @@ +package team + +import ( + "errors" + "net/http" + "opencatd-open/store" + "time" + + "github.com/Sakurasan/to" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func Handleinit(c *gin.Context) { + user, err := store.GetUserByID(1) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + u := store.User{Name: "root", Token: uuid.NewString()} + u.ID = 1 + if err := store.CreateUser(&u); err != nil { + c.JSON(http.StatusForbidden, gin.H{ + "error": err.Error(), + }) + return + } else { + rootToken = u.Token + + c.JSON(http.StatusOK, u) + return + } + } + c.JSON(http.StatusOK, gin.H{ + "error": err.Error(), + }) + return + } + if user.ID == 1 { + c.JSON(http.StatusForbidden, gin.H{ + "error": "super user already exists, use cli to reset password", + }) + } +} + +func HandleMe(c *gin.Context) { + token := c.GetHeader("Authorization") + u, err := store.GetUserByToken(token[7:]) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "error": err.Error(), + }) + } + + c.JSON(http.StatusOK, u) +} + +func HandleMeUsage(c *gin.Context) { + token := c.GetHeader("Authorization") + fromStr := c.Query("from") + toStr := c.Query("to") + getMonthStartAndEnd := func() (start, end string) { + loc, _ := time.LoadLocation("Local") + now := time.Now().In(loc) + + year, month, _ := now.Date() + + startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc) + endOfMonth := startOfMonth.AddDate(0, 1, 0) + + start = startOfMonth.Format("2006-01-02") + end = endOfMonth.Format("2006-01-02") + return + } + if fromStr == "" || toStr == "" { + fromStr, toStr = getMonthStartAndEnd() + } + user, err := store.GetUserByToken(token) + if err != nil { + c.AbortWithError(http.StatusForbidden, err) + return + } + usage, err := store.QueryUserUsage(to.String(user.ID), fromStr, toStr) + if err != nil { + c.AbortWithError(http.StatusForbidden, err) + return + } + + c.JSON(200, usage) +} diff --git a/team/middleware.go b/team/middleware.go new file mode 100644 index 0000000..7cbd49b --- /dev/null +++ b/team/middleware.go @@ -0,0 +1,69 @@ +package team + +import ( + "log" + "net/http" + "opencatd-open/store" + "strings" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +var ( + rootToken string +) + +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + if rootToken == "" { + u, err := store.GetUserByID(uint(1)) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + rootToken = u.Token + } + token := c.GetHeader("Authorization") + if token == "" || token[:7] != "Bearer " { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + if store.IsExistAuthCache(token[7:]) { + if strings.HasPrefix(c.Request.URL.Path, "/1/me") { + c.Next() + return + } + } + if token[7:] != rootToken { + u, err := store.GetUserByID(uint(1)) + if err != nil { + log.Println(err) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + if token[:7] != u.Token { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + rootToken = u.Token + store.LoadAuthCache() + } + // 可以在这里对 token 进行验证并检查权限 + + c.Next() + } +} + +func CORS() gin.HandlerFunc { + config := cors.DefaultConfig() + config.AllowAllOrigins = true + config.AllowCredentials = true + config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"} + config.AllowHeaders = []string{"*"} + return cors.New(config) +} diff --git a/team/model/Token.go b/team/model/Token.go new file mode 100644 index 0000000..7423a12 --- /dev/null +++ b/team/model/Token.go @@ -0,0 +1,19 @@ +package model + +// 用户的token +type Token struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement"` + UserId int64 `gorm:"column:user_id;not null;index:idx_token_user_id"` + Name string `gorm:"column:name;index:idx_token_name"` + Key string `gorm:"column:key;type:char(48);uniqueIndex:idx_token_key"` + Status bool `gorm:"column:status;default:true"` // enabled 0, disabled 1 + Quota int64 `gorm:"column:quota;type:bigint;default:0"` // -1 means unlimited + UnlimitedQuota bool `gorm:"column:unlimited_quota;default:true"` + UsedQuota int64 `gorm:"column:used_quota;type:bigint;default:0"` + CreatedTime int64 `gorm:"column:created_time;type:bigint"` + ExpiredTime int64 `gorm:"column:expired_time;type:bigint;default:-1"` // -1 means never expired +} + +func (Token) TableName() string { + return "token" +} diff --git a/team/model/apikey.go b/team/model/apikey.go new file mode 100644 index 0000000..503d1f6 --- /dev/null +++ b/team/model/apikey.go @@ -0,0 +1,49 @@ +package model + +import ( + "time" + + "github.com/lib/pq" +) + +type ApiKey_PG struct { + ID int64 `gorm:"column:id;primaryKey;autoIncrement"` + Name string `gorm:"column:name;not null;unique;index:idx_apikey_name"` + ApiType string `gorm:"column:apitype;not null;unique;index:idx_apikey_apitype"` + ApiKey string `gorm:"column:apikey;not null;unique;index:idx_apikey_apikey"` + Status int `gorm:"type:int;default:0"` // enabled 0, disabled 1 + Endpoint string `gorm:"column:endpoint"` + ResourceNmae string `gorm:"column:resource_name"` + DeploymentName string `gorm:"column:deployment_name"` + ApiSecret string `gorm:"column:api_secret"` + ModelPrefix string `gorm:"column:model_prefix"` + ModelAlias string `gorm:"column:model_alias"` + SupportModels pq.StringArray `gorm:"type:text[]"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty"` +} + +func (ApiKey_PG) TableName() string { + return "apikeys" +} + +type ApiKey struct { + ID int64 `gorm:"column:id;primaryKey;autoIncrement"` + Name string `gorm:"column:name;not null;unique;index:idx_apikey_name"` + ApiType string `gorm:"column:apitype;not null;unique;index:idx_apikey_apitype"` + ApiKey string `gorm:"column:apikey;not null;unique;index:idx_apikey_apikey"` + Status int `json:"status" gorm:"type:int;default:0"` // enabled 0, disabled 1 + Endpoint string `gorm:"column:endpoint"` + ResourceNmae string `gorm:"column:resource_name"` + DeploymentName string `gorm:"column:deployment_name"` + ApiSecret string `gorm:"column:api_secret"` + ModelPrefix string `gorm:"column:model_prefix"` + ModelAlias string `gorm:"column:model_alias"` + SupportModels []string `gorm:"type:json"` + CreatedAt time.Time `json:"created_at,omitempty" gorm:"autoUpdateTime"` + UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"autoCreateTime"` +} + +func (ApiKey) TableName() string { + return "apikeys" +} diff --git a/team/model/usage.go b/team/model/usage.go new file mode 100644 index 0000000..dbd5258 --- /dev/null +++ b/team/model/usage.go @@ -0,0 +1,72 @@ +package model + +import ( + "net/http" + "opencatd-open/store" + "time" + + "github.com/gin-gonic/gin" +) + +type DailyUsage struct { + ID int64 `gorm:"column:id;primaryKey;autoIncrement"` + UserID int64 `gorm:"column:user_id;index:idx_user_id"` + TokenId int64 `gorm:"column:token_id;index:idx_token_id"` + Capability string `gorm:"column:capability;index:idx_capability;comment:模型能力"` + Date time.Time `gorm:"column:date;autoCreateTime;index:idx_date"` + Model string `gorm:"column:model"` + Stream bool `gorm:"column:stream"` + PromptTokens int `gorm:"column:prompt_tokens"` + CompletionTokens int `gorm:"column:completion_tokens"` + TotalTokens int `gorm:"column:total_tokens"` + Cost string `gorm:"column:cost"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` +} + +func (DailyUsage) TableName() string { + return "daily_usages" +} + +type Usage struct { + ID int `gorm:"column:id"` + UserID int `gorm:"column:user_id"` + SKU string `gorm:"column:sku"` + PromptUnits int `gorm:"column:prompt_units"` + CompletionUnits int `gorm:"column:completion_units"` + TotalUnit int `gorm:"column:total_unit"` + Cost string `gorm:"column:cost"` + Date time.Time `gorm:"column:date"` +} + +func (Usage) TableName() string { + return "usages" +} + +func HandleUsage(c *gin.Context) { + fromStr := c.Query("from") + toStr := c.Query("to") + getMonthStartAndEnd := func() (start, end string) { + loc, _ := time.LoadLocation("Local") + now := time.Now().In(loc) + + year, month, _ := now.Date() + + startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc) + endOfMonth := startOfMonth.AddDate(0, 1, 0) + + start = startOfMonth.Format("2006-01-02") + end = endOfMonth.Format("2006-01-02") + return + } + if fromStr == "" || toStr == "" { + fromStr, toStr = getMonthStartAndEnd() + } + + usage, err := store.QueryUsage(fromStr, toStr) + if err != nil { + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + + c.JSON(200, usage) +} diff --git a/team/model/user.go b/team/model/user.go new file mode 100644 index 0000000..170b3b5 --- /dev/null +++ b/team/model/user.go @@ -0,0 +1,113 @@ +package model + +import ( + "net/http" + "opencatd-open/store" + "time" + + "github.com/Sakurasan/to" + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type User struct { + ID int64 `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"unique;index" validate:"max=12"` + UserName string `json:"username" gorm:"unique;index" validate:"max=12"` + Password string `json:"password" gorm:"not null;" validate:"min=8,max=20"` + Role int `json:"role" gorm:"type:int;default:1"` // default user + Status int `json:"status" gorm:"type:int;default:0"` // enabled 0, disabled 1 deleted 2 + Nickname string `json:"nickname" gorm:"type:varchar(50)"` + AvatarURL string `json:"avatar_url" gorm:"type:varchar(255)"` + Email string `json:"email" gorm:"type:varchar(255);unique;index"` + Quota int64 `json:"quota" gorm:"bigint;default:-1"` // default unlimited + Token string `json:"token,omitempty"` + Timezone string `json:"timezone" gorm:"type:varchar(50)"` + Language string `json:"language" gorm:"type:varchar(50)"` + + CreatedAt time.Time `json:"created_at,omitempty" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"autoUpdateTime"` +} + +func HandleUsers(c *gin.Context) { + users, err := store.GetAllUsers() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "error": err.Error(), + }) + } + + c.JSON(http.StatusOK, users) +} + +func HandleAddUser(c *gin.Context) { + var body User + if err := c.BindJSON(&body); err != nil { + c.JSON(http.StatusOK, gin.H{"error": err.Error()}) + return + } + if len(body.Name) == 0 { + c.JSON(http.StatusOK, gin.H{"error": "invalid user name"}) + return + } + + if err := store.AddUser(body.Name, uuid.NewString()); err != nil { + c.JSON(http.StatusOK, gin.H{"error": err.Error()}) + return + } + u, err := store.GetUserByName(body.Name) + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, u) +} + +func HandleDelUser(c *gin.Context) { + id := to.Int(c.Param("id")) + if id <= 1 { + c.JSON(http.StatusOK, gin.H{"error": "invalid user id"}) + return + } + if err := store.DeleteUser(uint(id)); err != nil { + c.JSON(http.StatusOK, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "ok"}) +} + +func HandleResetUserToken(c *gin.Context) { + id := to.Int(c.Param("id")) + newtoken := c.Query("token") + if newtoken == "" { + newtoken = uuid.NewString() + } + + if err := store.UpdateUser(uint(id), newtoken); err != nil { + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + u, err := store.GetUserByID(uint(id)) + if err != nil { + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, u) +} + +type Session struct { + ID int64 `json:"id" gorm:"primaryKey;autoIncrement"` + UserID int64 `json:"user_id" gorm:"index:idx_user_id"` + Token string `json:"token" gorm:"type:varchar(64);uniqueIndex"` + DeviceType string `json:"device_type" gorm:"type:varchar(100);default:''"` + DeviceName string `json:"device_name" gorm:"type:varchar(100);default:''"` + LastActiveAt time.Time `json:"last_active_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP"` + LogoutAt time.Time `json:"logout_at" gorm:"type:timestamp;null"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;not null;default:CURRENT_TIMESTAMP;update:CURRENT_TIMESTAMP"` +} + +func (Session) TableName() string { + return "sessions" +}