diff --git a/.vscode/settings.json b/.vscode/settings.json index 7efd943..21b4579 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,8 @@ "gomail", "gonic", "hackintosh", + "huma", + "humagin", "Infof", "jmoiron", "joho", diff --git a/Dockerfile b/Dockerfile index 6664834..002854a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ FROM golang as builder COPY . /app WORKDIR /app -# RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o saturday . RUN go env -w CGO_ENABLED=0 &&\ go build -v -o saturday . @@ -12,6 +11,7 @@ RUN apk add --no-cache tzdata &&\ WORKDIR /app COPY --from=builder /app/saturday /app +COPY --from=builder /app/migrations /app/migrations ENV Port=80 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..b6491e2 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,14 @@ +version: "3" +services: + server: + image: ghcr.io/nbtca/saturday + ports: + - 80:80 + volumes: + - ./logs/:/app/logs/ + environment: + DB_URL: root:password@(host.docker.internal:3306)/saturday_dev?parseTime=True + PORT: 80 + extra_hosts: + - "host.docker.internal:host-gateway" + diff --git a/go.mod b/go.mod index c1ff778..34cc2fd 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,22 @@ module github.com/nbtca/saturday -go 1.17 +go 1.22 + +toolchain go1.23.4 require ( github.com/Masterminds/squirrel v1.5.4 github.com/MicahParks/keyfunc v1.9.0 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/danielgtaylor/huma/v2 v2.28.0 github.com/gin-contrib/cors v1.6.0 - github.com/gin-gonic/gin v1.9.1 - github.com/go-playground/validator/v10 v10.19.0 + github.com/gin-gonic/gin v1.10.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/go-sql-driver/mysql v1.7.1 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/jmoiron/sqlx v1.3.5 github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.9 github.com/nsqio/go-nsq v1.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/qustavo/sqlhooks/v2 v2.1.0 @@ -21,15 +25,22 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect - github.com/bytedance/sonic v1.11.2 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/bytedance/sonic v1.12.3 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/text v0.1.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.7.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/arch v0.11.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect @@ -50,8 +61,9 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-migrate/migrate/v4 v4.17.1 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -65,18 +77,18 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.14 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index bc1b305..98beaa5 100644 --- a/go.sum +++ b/go.sum @@ -1,112 +1,100 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= -github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +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/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/danielgtaylor/huma/v2 v2.28.0 h1:W+hIT52MigO73edJNJWXU896uC99xSBWpKoE2PRyybM= +github.com/danielgtaylor/huma/v2 v2.28.0/go.mod h1:67KO0zmYEkR+LVUs8uqrcvf44G1wXiMIu94LV/cH2Ek= 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/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= +github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +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/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg= github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= 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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +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/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 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.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.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.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -116,41 +104,31 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 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 v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -158,48 +136,32 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE= github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/qustavo/sqlhooks/v2 v2.1.0 h1:54yBemHnGHp/7xgT+pxwmIlMSDNYKx5JW5dfRAiCZi0= github.com/qustavo/sqlhooks/v2 v2.1.0/go.mod h1:aMREyKo7fOKTwiLuWPsaHRXEmtqG4yREztO0idF83AU= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -209,19 +171,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -231,166 +186,80 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 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/middleware/auth.go b/middleware/auth.go index 6f382fd..79899fe 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -2,7 +2,10 @@ package middleware import ( "net/http" + "slices" + "strings" + "github.com/nbtca/saturday/service" "github.com/nbtca/saturday/util" "github.com/gin-gonic/gin" @@ -15,27 +18,88 @@ const ( Admin Role = "admin" ) -func Auth(role ...Role) func(c *gin.Context) { +type AuthContextUser struct { + UserInfo service.FetchUserInfoResponse + Role []string +} + +func Auth(acceptableRoles ...Role) func(c *gin.Context) { return func(c *gin.Context) { - tokenString := c.GetHeader("Authorization") - if tokenString == "" { + token := c.GetHeader("Authorization") + if token == "" { + c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). + SetMessage("not authorized"). + Build()) + return + } + // handel legacy jwt token + // this is currently used by wechat mini app + if len(strings.Split(token, ".")) > 1 { + tokenParsed, claims, err := util.ParseToken(token) + if err != nil || !tokenParsed.Valid { + c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). + SetMessage("not authorized"). + Build()) + return + } + for _, roleObj := range acceptableRoles { + if string(roleObj) == claims.Role { + c.Set("id", claims.Who) + c.Set("member", claims.Member) + c.Set("role", claims.Role) + return + } + } c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). SetMessage("not authorized"). Build()) return } - token, claims, err := util.ParseToken(tokenString) - if err != nil || !token.Valid { + + // strip bearer + token, err := util.GetTokenString(token) + if err != nil { + c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). + SetMessage("invalid token type"). + Build()) + return + } + userinfo, err := service.LogtoServiceApp.FetchUserInfo(token) + if err != nil { c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). SetMessage("not authorized"). Build()) return } - for _, roleObj := range role { - if string(roleObj) == claims.Role { - c.Set("id", claims.Who) - c.Set("member", claims.Member) - c.Set("role", claims.Role) + // TODO this is used for backward compatibility, will only use userRoles in the future + var role string + userRoles := []string{"client"} + if slices.Contains(userinfo.Roles, "Repair Admin") { + userRoles = append(userRoles, "admin") + role = "admin" + } + if slices.Contains(userinfo.Roles, "Repair Member") { + userRoles = append(userRoles, "member") + if role == "" { + role = "member" + } + } + for _, r := range acceptableRoles { + if slices.Contains(userRoles, string(r)) { + member, err := service.MemberServiceApp.GetMemberByLogtoId(userinfo.Sub) + if err != nil { + c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized). + SetMessage("not authorized"). + Build()) + } + user := AuthContextUser{ + Role: userRoles, + UserInfo: userinfo, + } + c.Set("id", member.MemberId) + c.Set("member", member) + c.Set("role", role) + c.Set("user", user) return } } diff --git a/migrations/000001_init.down.sql b/migrations/000001_init.down.sql new file mode 100644 index 0000000..be5d4cc --- /dev/null +++ b/migrations/000001_init.down.sql @@ -0,0 +1,20 @@ +DROP VIEW IF EXISTS public.event_log_view; +DROP VIEW IF EXISTS public.event_view; +DROP VIEW IF EXISTS public.member_view; + +DROP TABLE IF EXISTS public.setting; +DROP TABLE IF EXISTS public.member_role_relation; +DROP TABLE IF EXISTS public.role; +DROP TABLE IF EXISTS public.member; +DROP TABLE IF EXISTS public.event_event_status_relation; +DROP TABLE IF EXISTS public.event_event_action_relation; +DROP TABLE IF EXISTS public.event_log; +DROP TABLE IF EXISTS public.event_action; +DROP TABLE IF EXISTS public.event_status; +DROP TABLE IF EXISTS public.event; +DROP TABLE IF EXISTS public.client; + +DROP SEQUENCE IF EXISTS public.client_client_id_seq; +DROP SEQUENCE IF EXISTS public.event_event_id_seq; +DROP SEQUENCE IF EXISTS public.event_event_action_relation_event_log_id_seq; +DROP SEQUENCE IF EXISTS public.event_log_event_log_id_seq; \ No newline at end of file diff --git a/migrations/000001_init.up.sql b/migrations/000001_init.up.sql new file mode 100644 index 0000000..88c4b86 --- /dev/null +++ b/migrations/000001_init.up.sql @@ -0,0 +1,605 @@ +-- Active: 1716098742535@@127.0.0.1@5432@weekend +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 16.3 (Debian 16.3-1.pgdg120+1) +-- Dumped by pg_dump version 16.3 (Debian 16.3-1.pgdg120+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: client; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.client ( + client_id bigint NOT NULL, + openid character(28) DEFAULT ''::bpchar, + gmt_create timestamp without time zone NOT NULL, + gmt_modified timestamp without time zone NOT NULL +); + + +ALTER TABLE public.client OWNER TO postgres; + +-- +-- Name: COLUMN client.openid; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.client.openid IS '微信'; + + +-- +-- Name: client_client_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.client ALTER COLUMN client_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.client_client_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: event; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event ( + client_id bigint NOT NULL, + event_id bigint NOT NULL, + model character varying(40) DEFAULT ''::character varying, + phone character varying(11) DEFAULT ''::character varying NOT NULL, + qq character varying(20) DEFAULT ''::character varying, + contact_preference character varying(20) DEFAULT 'qq'::character varying NOT NULL, + problem character varying(500) DEFAULT ''::character varying, + member_id character(10) DEFAULT ''::bpchar, + closed_by character(10) DEFAULT ''::bpchar, + gmt_create timestamp without time zone NOT NULL, + gmt_modified timestamp without time zone NOT NULL +); + + +ALTER TABLE public.event OWNER TO postgres; + + +COMMENT ON COLUMN public.event.model IS '型号'; +COMMENT ON COLUMN public.event.contact_preference IS '联系偏好'; +COMMENT ON COLUMN public.event.problem IS '事件(用户)描述'; +COMMENT ON COLUMN public.event.member_id IS '最后由谁维修'; +COMMENT ON COLUMN public.event.closed_by IS '由谁关闭'; + + +-- +-- Name: event_action; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event_action ( + event_action_id smallint NOT NULL, + action character varying(30) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE public.event_action OWNER TO postgres; + +-- +-- Name: event_event_action_relation; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event_event_action_relation ( + event_log_id bigint NOT NULL, + event_action_id smallint NOT NULL +); + + +ALTER TABLE public.event_event_action_relation OWNER TO postgres; + +-- +-- Name: event_event_action_relation_event_log_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.event_event_action_relation ALTER COLUMN event_log_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.event_event_action_relation_event_log_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: event_event_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.event ALTER COLUMN event_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.event_event_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: event_event_status_relation; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event_event_status_relation ( + event_id bigint NOT NULL, + event_status_id smallint NOT NULL +); + + +ALTER TABLE public.event_event_status_relation OWNER TO postgres; + +-- +-- Name: event_log; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event_log ( + event_log_id bigint NOT NULL, + event_id bigint NOT NULL, + description character varying(255) DEFAULT ''::character varying, + member_id character(10) DEFAULT ''::bpchar, + gmt_create timestamp without time zone NOT NULL +); + + +ALTER TABLE public.event_log OWNER TO postgres; + +-- +-- Name: event_log_event_log_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.event_log ALTER COLUMN event_log_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.event_log_event_log_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: event_log_view; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public.event_log_view AS + SELECT event_log.event_log_id, + event_log.event_id, + event_log.description, + event_log.member_id, + event_log.gmt_create, + event_action.action + FROM ((public.event_log + LEFT JOIN public.event_event_action_relation ON ((event_log.event_log_id = event_event_action_relation.event_log_id))) + LEFT JOIN public.event_action ON ((event_event_action_relation.event_action_id = event_action.event_action_id))); + + +ALTER VIEW public.event_log_view OWNER TO postgres; + +-- +-- Name: event_status; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.event_status ( + event_status_id smallint NOT NULL, + status character varying(255) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE public.event_status OWNER TO postgres; + +-- +-- Name: event_view; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public.event_view AS + SELECT event.event_id, + event.client_id, + event.model, + event.phone, + event.qq, + event.contact_preference, + event.problem, + event.member_id, + event.closed_by, + event.gmt_create, + event.gmt_modified, + COALESCE(event_status.status, ''::character varying) AS status + FROM ((public.event + LEFT JOIN public.event_event_status_relation ON ((event.event_id = event_event_status_relation.event_id))) + LEFT JOIN public.event_status ON ((event_event_status_relation.event_status_id = event_status.event_status_id))); + + +ALTER VIEW public.event_view OWNER TO postgres; + +-- +-- Name: member; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.member ( + member_id character(10) NOT NULL, + alias character varying(50) DEFAULT ''::character varying, + password character varying(50) DEFAULT ''::character varying, + name character varying(20) DEFAULT ''::character varying, + section character varying(20) DEFAULT ''::character varying, + profile character varying(1000) DEFAULT ''::character varying, + phone character varying(11) DEFAULT ''::character varying, + qq character varying(20) DEFAULT ''::character varying, + avatar character varying(255) DEFAULT ''::character varying, + created_by character(10) DEFAULT ''::bpchar, + gmt_create timestamp without time zone NOT NULL, + gmt_modified timestamp without time zone NOT NULL, + logto_id character varying(50) DEFAULT ''::character varying +); + + +ALTER TABLE public.member OWNER TO postgres; + +-- +-- Name: COLUMN member.alias; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.member.alias IS '昵称'; + + +-- +-- Name: COLUMN member.section; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.member.section IS '班级(计算机196)'; + + +-- +-- Name: COLUMN member.profile; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.member.profile IS '个人简介'; + + +-- +-- Name: COLUMN member.avatar; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.member.avatar IS '头像地址'; + + +-- +-- Name: COLUMN member.created_by; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON COLUMN public.member.created_by IS '由谁添加'; + + +-- +-- Name: member_role_relation; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.member_role_relation ( + member_id character varying(10) NOT NULL, + role_id smallint NOT NULL +); + + +ALTER TABLE public.member_role_relation OWNER TO postgres; + +-- +-- Name: role; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.role ( + role_id smallint NOT NULL, + role character varying(255) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE public.role OWNER TO postgres; + +-- +-- Name: member_view; Type: VIEW; Schema: public; Owner: postgres +-- + +CREATE VIEW public.member_view AS + SELECT member.member_id, + member.alias, + member.password, + member.name, + member.section, + member.profile, + member.phone, + member.qq, + member.avatar, + member.created_by, + member.gmt_create, + member.gmt_modified, + COALESCE(role.role, ''::character varying) AS role, + member.logto_id + FROM ((public.member + LEFT JOIN public.member_role_relation ON ((member.member_id = (member_role_relation.member_id)::bpchar))) + LEFT JOIN public.role ON ((member_role_relation.role_id = role.role_id))); + + +ALTER VIEW public.member_view OWNER TO postgres; + +-- +-- Name: setting; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE IF NOT EXISTS public.setting ( + setting character varying(10000) DEFAULT ''::character varying +); + + +ALTER TABLE public.setting OWNER TO postgres; + +-- +-- Data for Name: client; Type: TABLE DATA; Schema: public; Owner: postgres +-- + + +-- +-- Data for Name: event_action; Type: TABLE DATA; Schema: public; Owner: postgres +-- +INSERT INTO public.event_action (event_action_id, action) VALUES +(1, 'create'), +(2, 'accept'), +(3, 'cancel'), +(4, 'commit'), +(5, 'alterCommit'), +(6, 'drop'), +(7, 'close'), +(8, 'reject'), +(9, 'update'); + +-- +-- Data for Name: event_status; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +INSERT INTO public.event_status (event_status_id, status) VALUES +(1, 'open'), +(2, 'accepted'), +(3, 'cancelled'), +(4, 'committed'), +(5, 'closed'); + + +-- +-- Data for Name: role; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +INSERT INTO public.role (role_id,role) VALUES +(0,'member_inactive'), +(1,'admin_inactive'), +(2,'member'), +(4,'admin'); + +-- +-- Name: client client_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT client_pkey PRIMARY KEY (client_id); + + +-- +-- Name: event_action event_action_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_action + ADD CONSTRAINT event_action_pkey PRIMARY KEY (event_action_id); + + +-- +-- Name: event_event_action_relation event_event_action_relation_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_action_relation + ADD CONSTRAINT event_event_action_relation_pkey PRIMARY KEY (event_log_id); + + +-- +-- Name: event_event_status_relation event_event_status_relation_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_status_relation + ADD CONSTRAINT event_event_status_relation_pkey PRIMARY KEY (event_id); + + +-- +-- Name: event_log event_log_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_log + ADD CONSTRAINT event_log_pkey PRIMARY KEY (event_log_id); + + +-- +-- Name: event event_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event + ADD CONSTRAINT event_pkey PRIMARY KEY (event_id); + + +-- +-- Name: event_status event_status_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_status + ADD CONSTRAINT event_status_pkey PRIMARY KEY (event_status_id); + + +-- +-- Name: member member_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.member + ADD CONSTRAINT member_pkey PRIMARY KEY (member_id); + + +-- +-- Name: member_role_relation member_role_relation_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.member_role_relation + ADD CONSTRAINT member_role_relation_pkey PRIMARY KEY (member_id); + + +-- +-- Name: role role_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.role + ADD CONSTRAINT role_pkey PRIMARY KEY (role_id); + + +-- +-- Name: event_client_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_client_id_idx ON public.event USING btree (client_id); + + +-- +-- Name: event_closed_by_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_closed_by_idx ON public.event USING btree (closed_by); + + +-- +-- Name: event_event_action_relation_event_action_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_event_action_relation_event_action_id_idx ON public.event_event_action_relation USING btree (event_action_id); + + +-- +-- Name: event_event_status_relation_event_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_event_status_relation_event_id_idx ON public.event_event_status_relation USING btree (event_id); + + +-- +-- Name: event_event_status_relation_event_status_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_event_status_relation_event_status_id_idx ON public.event_event_status_relation USING btree (event_status_id); + + +-- +-- Name: event_log_event_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_log_event_id_idx ON public.event_log USING btree (event_id); + + +-- +-- Name: event_log_member_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_log_member_id_idx ON public.event_log USING btree (member_id); + + +-- +-- Name: event_member_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX event_member_id_idx ON public.event USING btree (member_id); + + +-- +-- Name: member_role_relation_role_id_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX member_role_relation_role_id_idx ON public.member_role_relation USING btree (role_id); + + +-- +-- Name: event event_client_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event + ADD CONSTRAINT event_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.client(client_id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: event_event_action_relation event_event_action_relation_event_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_action_relation + ADD CONSTRAINT event_event_action_relation_event_action_id_fkey FOREIGN KEY (event_action_id) REFERENCES public.event_action(event_action_id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: event_event_action_relation event_event_action_relation_event_log_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_action_relation + ADD CONSTRAINT event_event_action_relation_event_log_id_fkey FOREIGN KEY (event_log_id) REFERENCES public.event_log(event_log_id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: event_event_status_relation event_event_status_relation_event_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_status_relation + ADD CONSTRAINT event_event_status_relation_event_id_fkey FOREIGN KEY (event_id) REFERENCES public.event(event_id); + + +-- +-- Name: event_event_status_relation event_event_status_relation_event_status_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_event_status_relation + ADD CONSTRAINT event_event_status_relation_event_status_id_fkey FOREIGN KEY (event_status_id) REFERENCES public.event_status(event_status_id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: event_log event_log_event_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.event_log + ADD CONSTRAINT event_log_event_id_fkey FOREIGN KEY (event_id) REFERENCES public.event(event_id); + + +-- +-- Name: member_role_relation member_role_relation_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.member_role_relation + ADD CONSTRAINT member_role_relation_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(member_id); + + +-- +-- Name: member_role_relation member_role_relation_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.member_role_relation + ADD CONSTRAINT member_role_relation_role_id_fkey FOREIGN KEY (role_id) REFERENCES public.role(role_id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/migrations/000002_add_logto_id_to_client.down.sql b/migrations/000002_add_logto_id_to_client.down.sql new file mode 100644 index 0000000..801ef6d --- /dev/null +++ b/migrations/000002_add_logto_id_to_client.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.client +DROP COLUMN logto_id; \ No newline at end of file diff --git a/migrations/000002_add_logto_id_to_client.up.sql b/migrations/000002_add_logto_id_to_client.up.sql new file mode 100644 index 0000000..7e1e3d7 --- /dev/null +++ b/migrations/000002_add_logto_id_to_client.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.client +ADD COLUMN logto_id character varying(50) DEFAULT ''::character varying; diff --git a/model/client.go b/model/client.go index 0d31586..99afaf8 100644 --- a/model/client.go +++ b/model/client.go @@ -3,6 +3,7 @@ package model type Client struct { ClientId int64 `json:"clientId" db:"client_id"` OpenId string `json:"openid"` + LogtoId string `json:"logtoId" db:"logto_id"` GmtCreate string `json:"gmtCreate" db:"gmt_create"` GmtModified string `json:"gmtModified" db:"gmt_modified"` } diff --git a/model/dto/pagination.go b/model/dto/pagination.go index b447e37..54145c8 100644 --- a/model/dto/pagination.go +++ b/model/dto/pagination.go @@ -4,3 +4,8 @@ type Page struct { Offset uint64 `json:"-" form:"offset" binding:"min=0"` Limit uint64 `json:"-" form:"limit" binding:"min=0"` } + +type PageRequest struct { + Offset uint64 `query:"offset" default:"0" example:"0" minimum:"0" doc:"Offset"` + Limit uint64 `query:"limit" default:"50" example:"50" minimum:"0" doc:"Limit"` +} diff --git a/repo/client.go b/repo/client.go index 6f0a58f..11c351b 100644 --- a/repo/client.go +++ b/repo/client.go @@ -11,7 +11,7 @@ import ( ) func GetClientByOpenId(openId string) (model.Client, error) { - statement, args, _ := squirrel.Select("*").From("client").Where(squirrel.Eq{"openid": openId}).ToSql() + statement, args, _ := sq.Select("*").From("client").Where(squirrel.Eq{"openid": openId}).ToSql() client := model.Client{} if err := db.Get(&client, statement, args...); err != nil { if err == sql.ErrNoRows { @@ -22,18 +22,29 @@ func GetClientByOpenId(openId string) (model.Client, error) { return client, nil } +func GetClientByLogtoId(logtoId string) (model.Client, error) { + statement, args, _ := sq.Select("*").From("client").Where(squirrel.Eq{"logto_id": logtoId}).ToSql() + client := model.Client{} + if err := db.Get(&client, statement, args...); err != nil { + if err == sql.ErrNoRows { + return model.Client{}, nil + } + return model.Client{}, nil + } + return client, nil +} + + func CreateClient(client *model.Client) error { client.GmtCreate = util.GetDate() client.GmtModified = util.GetDate() - sql, args, _ := squirrel.Insert("client").Columns("openid", "gmt_create", "gmt_modified"). - Values(client.OpenId, time.Now(), time.Now()).ToSql() - res, err := db.Exec(sql, args...) - if err != nil { - return err - } - client.ClientId, err = res.LastInsertId() + sql, args, _ := sq.Insert("client").Columns("openid", "logto_id", "gmt_create", "gmt_modified"). + Values(client.OpenId, client.LogtoId, time.Now(), time.Now()).ToSql() + var id int64 + err := db.QueryRow(sql+"RETURNING client_id", args...).Scan(&id) if err != nil { return err } + client.ClientId = id return nil } diff --git a/repo/db.go b/repo/db.go index dc5fa93..0c26348 100644 --- a/repo/db.go +++ b/repo/db.go @@ -6,14 +6,23 @@ import ( "os" "time" - "github.com/go-sql-driver/mysql" - "github.com/nbtca/saturday/util" + "github.com/Masterminds/squirrel" + pq "github.com/lib/pq" "github.com/qustavo/sqlhooks/v2" + + "github.com/nbtca/saturday/util" "github.com/sirupsen/logrus" "github.com/jmoiron/sqlx" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + _ "github.com/golang-migrate/migrate/v4/source/file" ) +// sq is a squirrel.StatementBuilderType with Correct PlaceholderFormat +var sq squirrel.StatementBuilderType + var db *sqlx.DB // Hooks satisfies the sqlhooks.Hooks interface @@ -31,21 +40,41 @@ func (h *Hooks) After(ctx context.Context, query string, args ...interface{}) (c "query": query, "args": args, "elapsed": time.Since(begin), + "id": ctx.Value("uuid"), }).Debug("SQL executed") return ctx, nil } func InitDB() { var err error - sql.Register("mysqlWithHooks", sqlhooks.Wrap(&mysql.MySQLDriver{}, &Hooks{})) - db, err = sqlx.Connect("mysqlWithHooks", os.Getenv("DB_URL")) + sql.Register("pqHooked", sqlhooks.Wrap(&pq.Driver{}, &Hooks{})) + sqlx.BindDriver("pqHooked", sqlx.DOLLAR) + db, err = sqlx.Connect("pqHooked", os.Getenv("DB_URL")) + if err != nil { + util.Logger.Fatal(err) + } + util.Logger.Debug("Connected to database") + driver, err := postgres.WithInstance(db.DB, &postgres.Config{}) if err != nil { util.Logger.Fatal(err) } + + m, err := migrate.NewWithDatabaseInstance( + "file://migrations", + "postgres", driver) + if err != nil { + util.Logger.Fatal(err) + } + + m.Up() // or m.Step(2) if you want to explicitly set the number of migrations to run + db.SetMaxOpenConns(1000) // The default is 0 (unlimited) db.SetMaxIdleConns(10) // defaultMaxIdleConns = 2 db.SetConnMaxLifetime(time.Minute * 5) // 0, connections are reused forever. + + sq = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) + util.Logger.Trace("Initialized database") } func SetDB(dbx *sqlx.DB) { diff --git a/repo/event.go b/repo/event.go index 09a6a68..c1099eb 100644 --- a/repo/event.go +++ b/repo/event.go @@ -24,14 +24,14 @@ func getEventStatement() squirrel.SelectBuilder { // when column prefix is set, table must set with correspond alias(first letter of alias) // for example: when column prefix is "member", the table should be aliased to "m". - return squirrel.Select(fields...). + return sq.Select(fields...). From("event_view as e"). LeftJoin("member_view as m USING (member_id)"). LeftJoin("member_view as a ON (e.closed_by=a.member_id)") } func getLogStatement() squirrel.SelectBuilder { - return squirrel.Select(EventLogFields...).From("event_log_view") + return sq.Select(EventLogFields...).From("event_log_view") } /* @@ -120,7 +120,7 @@ func GetClientEvents(f EventFilter, clientId string) ([]model.Event, error) { } func UpdateEvent(event *model.Event, eventLog *model.EventLog) error { - sql, args, _ := squirrel.Update("event"). + sql, args, _ := sq.Update("event"). Set("model", event.Model). Set("phone", event.Phone). Set("qq", event.QQ). @@ -153,11 +153,11 @@ func UpdateEvent(event *model.Event, eventLog *model.EventLog) error { func CreateEvent(event *model.Event) error { event.GmtCreate = util.GetDate() event.GmtModified = util.GetDate() - createEventSql, args, _ := squirrel.Insert("event").Columns( - "event_id", "client_id", "model", "phone", "qq", + createEventSql, args, _ := sq.Insert("event").Columns( + "client_id", "model", "phone", "qq", "contact_preference", "problem", "member_id", "closed_by", "gmt_create", "gmt_modified").Values( - event.EventId, event.ClientId, event.Model, event.Phone, event.QQ, + event.ClientId, event.Model, event.Phone, event.QQ, event.ContactPreference, event.Problem, event.MemberId, event.ClosedBy, event.GmtCreate, event.GmtModified).ToSql() conn, err := db.Beginx() @@ -165,11 +165,13 @@ func CreateEvent(event *model.Event) error { return err } defer util.RollbackOnErr(err, conn) - res, err := conn.Exec(createEventSql, args...) + var id int64 + err = conn.QueryRow(createEventSql+"RETURNING event_id", args...).Scan(&id) + // res, err := conn.Exec(createEventSql, args...) if err != nil { return err } - event.EventId, _ = res.LastInsertId() + event.EventId = id if err = conn.Commit(); err != nil { return err @@ -178,14 +180,14 @@ func CreateEvent(event *model.Event) error { } func CreateEventLog(eventLog *model.EventLog, conn *sqlx.Tx) error { - sql, args, _ := squirrel.Insert("event_log").Columns("event_id", "description", "member_id", "gmt_create"). + sql, args, _ := sq.Insert("event_log").Columns("event_id", "description", "member_id", "gmt_create"). Values(eventLog.EventId, eventLog.Description, eventLog.MemberId, util.GetDate()).ToSql() - res, err := conn.Exec(sql, args...) + var eventLogId int64 + err := db.QueryRow(sql+"RETURNING event_log_id", args...).Scan(&eventLogId) if err != nil { return err } - eventLogId, _ := res.LastInsertId() - eventLog.EventLogId = int64(eventLogId) + eventLog.EventLogId = eventLogId err = SetEventAction(eventLogId, eventLog.Action, conn) if err != nil { return err @@ -195,7 +197,8 @@ func CreateEventLog(eventLog *model.EventLog, conn *sqlx.Tx) error { func ExistEventAction(action string) (bool, error) { var count int - err := db.Get(&count, "SELECT count(*) as count FROM event_action where action = ?", action) + sql, args, _ := sq.Select("count(*) as count").From("event_action").Where(squirrel.Eq{"action": action}).ToSql() + err := db.Get(&count, sql, args...) if err != nil { return false, err } @@ -210,7 +213,13 @@ func SetEventAction(eventLogId int64, action string, conn *sqlx.Tx) error { SELECT event_action_id FROM event_action WHERE action=?)) ON DUPLICATE KEY UPDATE event_action_id=( SELECT event_action_id FROM event_action WHERE action= ? )` - _, err := conn.Exec(sql, eventLogId, action, action) + if db.DriverName() == "pqHooked" { + sql = `INSERT INTO event_event_action_relation VALUES ($1,( + SELECT event_action_id FROM event_action WHERE action=$2)) + ON CONFLICT (event_log_id) DO UPDATE SET event_action_id=( + SELECT event_action_id FROM event_action WHERE action=$3 )` + } + _, err := conn.Exec(db.Rebind(sql), eventLogId, action, action) return err } @@ -230,12 +239,20 @@ func SetEventStatus(eventId int64, status string, conn *sqlx.Tx) (sql.Result, er sql := `INSERT INTO event_event_status_relation (event_id, event_status_id) VALUES (?, (Select event_status_id from event_status where status = ?)) ON DUPLICATE KEY UPDATE event_status_id=(SELECT event_status_id FROM event_status WHERE status=?)` - return conn.Exec(sql, eventId, status, status) + + if db.DriverName() == "pqHooked" { + sql = `INSERT INTO event_event_status_relation (event_id, event_status_id) + VALUES ($1, (Select event_status_id from event_status where status = $2)) + ON CONFLICT (event_id) DO UPDATE SET event_status_id=(SELECT event_status_id FROM event_status WHERE status=$3)` + } + + return conn.Exec(db.Rebind(sql), eventId, status, status) } func GetEventClientId(eventId int64) (int64, error) { var clientId int64 - err := db.Get(&clientId, "SELECT client_id FROM event WHERE event_id = ?", eventId) + sql, args, _ := sq.Select("client_id").From("event").Where(squirrel.Eq{"event_id": eventId}).ToSql() + err := db.Get(&clientId, sql, args...) if err != nil { return 0, err } diff --git a/repo/member.go b/repo/member.go index c8fdb25..2ea80a7 100644 --- a/repo/member.go +++ b/repo/member.go @@ -14,12 +14,13 @@ var memberFields = []string{"member_id", "alias", "password", "name", "section", "phone", "qq", "avatar", "created_by", "gmt_create", "gmt_modified"} func getMemberStatement() squirrel.SelectBuilder { - return squirrel.Select("*").From("member_view") + return sq.Select("*").From("member_view") } func ExistMember(id string) (bool, error) { var count int - err := db.Get(&count, "SELECT count(*) as count FROM member where member_id = ?", id) + sql, args, _ := sq.Select("count(*) as count").From("member").Where(squirrel.Eq{"member_id": id}).ToSql() + err := db.Get(&count, sql, args...) if err != nil { return false, err } @@ -43,13 +44,26 @@ func GetMemberById(id string) (model.Member, error) { func GetMemberIdByLogtoId(logtoId string) (sql.NullString, error) { var memberId sql.NullString - err := db.Get(&memberId, "SELECT member_id FROM member where logto_id = ?", logtoId) + s, args, _ := sq.Select("member_id").From("member").Where(squirrel.Eq{"logto_id": logtoId}).ToSql() + err := db.Get(&memberId, s, args...) if err != nil { return sql.NullString{}, err } return memberId, nil } +func GetMemberByLogtoId(logtoId string) (model.Member, error) { + statement, args, _ := getMemberStatement().Where(squirrel.Eq{"logto_id": logtoId}).ToSql() + member := model.Member{} + if err := db.Get(&member, statement, args...); err != nil { + if err == sql.ErrNoRows { + return model.Member{}, nil + } + return model.Member{}, err + } + return member, nil +} + func GetMembers(offset uint64, limit uint64) ([]model.Member, error) { sql, args, _ := getMemberStatement().Offset(offset).Limit(limit).ToSql() members := []model.Member{} @@ -62,7 +76,7 @@ func GetMembers(offset uint64, limit uint64) ([]model.Member, error) { func CreateMember(member *model.Member) error { member.GmtCreate = util.GetDate() member.GmtModified = util.GetDate() - sqlMember, argsMember, _ := squirrel.Insert("member").Columns( + sqlMember, argsMember, _ := sq.Insert("member").Columns( "member_id", "logto_id", "alias", "name", "section", "profile", "avatar", "phone", "qq", "created_by", "gmt_create", "gmt_modified").Values( member.MemberId, member.LogtoId, member.Alias, member.Name, member.Section, @@ -86,7 +100,7 @@ func CreateMember(member *model.Member) error { } func UpdateMember(member model.Member) error { - sql, args, _ := squirrel.Update("member"). + sql, args, _ := sq.Update("member"). Set("logto_id", member.LogtoId). Set("alias", member.Alias). Set("name", member.Name). diff --git a/repo/role.go b/repo/role.go index 3a9a9a9..436049b 100644 --- a/repo/role.go +++ b/repo/role.go @@ -1,15 +1,13 @@ package repo import ( - "database/sql" - _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) func ExistRole(role string) (bool, error) { var count int - err := db.Get(&count, "SELECT count(*) as count FROM role where role = ?", role) + err := db.Get(&count, db.Rebind("SELECT count(*) as count FROM role where role = ?"), role) if err != nil { return false, err } @@ -19,19 +17,15 @@ func ExistRole(role string) (bool, error) { return true, nil } -func GetRoleId(role string) (sql.NullInt64, error) { - var id sql.NullInt64 - err := db.Get(&id, "SELECT role_id FROM role where role = ?", role) - if err != nil { - return sql.NullInt64{}, err - } - return id, nil -} - func SetMemberRole(memberId string, role string, conn *sqlx.Tx) error { sql := `INSERT INTO member_role_relation (member_id, role_id) VALUES (?, (Select role_id from role where role = ?)) ON DUPLICATE KEY UPDATE role_id=(Select role_id from role where role = ?)` - _, err := conn.Exec(sql, memberId, role, role) + if db.DriverName() == "pqHooked" { + sql = `INSERT INTO member_role_relation (member_id, role_id) + VALUES ($1, (Select role_id from role where role = $2)) + ON CONFLICT (member_id) DO UPDATE SET role_id=(Select role_id from role where role = $3)` + } + _, err := conn.Exec(db.Rebind(sql), memberId, role, role) return err } diff --git a/router/client.go b/router/client.go index 2fcac3e..13d0b46 100644 --- a/router/client.go +++ b/router/client.go @@ -1,44 +1,82 @@ package router import ( + "context" + + "github.com/danielgtaylor/huma/v2" + "github.com/gin-gonic/gin" + "github.com/nbtca/saturday/middleware" "github.com/nbtca/saturday/model" "github.com/nbtca/saturday/model/dto" "github.com/nbtca/saturday/service" "github.com/nbtca/saturday/util" - - "github.com/gin-gonic/gin" ) type ClientRouter struct{} -func (ClientRouter) CreateTokenViaWeChat(c *gin.Context) { - wxLoginRequest := &dto.WxLoginRequest{} - if err := util.BindAll(c, wxLoginRequest); util.CheckError(c, err) { - return +func (ClientRouter) CreateTokenViaWeChat(c context.Context, input *struct { + Body struct { + Code string `json:"code"` } - openid, err := util.CodeToSession(wxLoginRequest.Code) - if util.CheckError(c, err) { - return +}) (*util.CommonResponse[dto.ClientTokenResponse], error) { + openid, err := util.CodeToSession(input.Body.Code) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } client, err := service.ClientServiceApp.GetClientByOpenId(openid) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } if client == (model.Client{}) { client, err = service.ClientServiceApp.CreateClientByOpenId(openid) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) + } + } + token, err := service.ClientServiceApp.CreateClientToken(client) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) + } + return util.MakeCommonResponse(dto.ClientTokenResponse{ + Token: token, + Client: client, + }), nil +} + +func (ClientRouter) CreateTokenViaLogto(c *gin.Context) { + user := c.Value("user").(middleware.AuthContextUser) + response := dto.ClientTokenResponse{} + logtoId := user.UserInfo.Sub + if logtoId == "" { + c.Error(huma.Error422UnprocessableEntity("user not found")) + // return nil, huma.Error422UnprocessableEntity("user not found") + } + client, err := service.ClientServiceApp.GetClientByLogtoId(logtoId) + if err != nil { + c.Error(huma.Error422UnprocessableEntity(err.Error())) + // return nil, huma.Error422UnprocessableEntity(err.Error()) + } + if client == (model.Client{}) { + client, err = service.ClientServiceApp.CreateClientByLogtoId(logtoId) + if err != nil { + // return nil, huma.Error422UnprocessableEntity(err.Error()) + c.Error(huma.Error422UnprocessableEntity(err.Error())) } } - token, err := service.ClientServiceApp.CreateTokenViaWechat(client) - if util.CheckError(c, err) { - return + token, err := service.ClientServiceApp.CreateClientToken(client) + if err != nil { + // return nil, huma.Error422UnprocessableEntity(err.Error()) + c.Error(huma.Error422UnprocessableEntity(err.Error())) } - res := dto.ClientTokenResponse{ + response = dto.ClientTokenResponse{ Token: token, Client: client, } - c.JSON(200, res) + c.JSON(200, response) + // return util.MakeCommonResponse(dto.ClientTokenResponse{ + // Token: token, + // Client: client, + // }), nil } var ClientRouterApp = ClientRouter{} diff --git a/router/event.go b/router/event.go index e8f3d47..4823a63 100644 --- a/router/event.go +++ b/router/event.go @@ -1,9 +1,11 @@ package router import ( + "context" "net/http" "strconv" + "github.com/danielgtaylor/huma/v2" "github.com/nbtca/saturday/model" "github.com/nbtca/saturday/model/dto" "github.com/nbtca/saturday/repo" @@ -13,39 +15,35 @@ import ( "github.com/gin-gonic/gin" ) -type EventRouter struct{} - -func (EventRouter) GetPublicEventById(c *gin.Context) { - eventId := &dto.EventID{} - if err := util.BindAll(c, eventId); util.CheckError(c, err) { - return - } - event, err := service.EventServiceApp.GetPublicEventById(eventId.EventID) - if util.CheckError(c, err) { - return - } - c.JSON(200, event) +type EventRouter struct { + huma huma.API } -func (EventRouter) GetPublicEventByPage(c *gin.Context) { - offset, limit, err := util.GetPaginationQuery(c) // TODO use validator +func (EventRouter) GetPublicEventById(c context.Context, input *struct { + EventID int64 `path:"EventId"` +}) (*util.CommonResponse[model.PublicEvent], error) { + event, err := service.EventServiceApp.GetPublicEventById(input.EventID) if err != nil { - c.Error(err) - return + return nil, huma.Error422UnprocessableEntity(err.Error()) } - status := c.DefaultQuery("status", "") - order := c.DefaultQuery("order", "ASC") + return util.MakeCommonResponse(event), nil +} + +func (EventRouter) GetPublicEventByPage(c context.Context, input *struct { + dto.PageRequest + Status string `query:"status"` + Order string `query:"order" default:"ASC"` +}) (*util.CommonResponse[[]model.PublicEvent], error) { events, err := service.EventServiceApp.GetPublicEvents(repo.EventFilter{ - Offset: offset, - Limit: limit, - Status: status, - Order: order, + Offset: input.Offset, + Limit: input.Limit, + Status: input.Status, + Order: input.Order, }) if err != nil { - c.Error(err) - return + return nil, huma.Error422UnprocessableEntity(err.Error()) } - c.JSON(200, events) + return util.MakeCommonResponse(events), nil } func (EventRouter) GetEventById(c *gin.Context) { diff --git a/router/main.go b/router/main.go index f23ac08..4585c0a 100644 --- a/router/main.go +++ b/router/main.go @@ -1,15 +1,24 @@ package router import ( + "context" + "net/http" "regexp" "time" + "github.com/danielgtaylor/huma/v2" + "github.com/danielgtaylor/huma/v2/adapters/humagin" "github.com/gin-contrib/cors" "github.com/nbtca/saturday/middleware" + "github.com/nbtca/saturday/util" "github.com/gin-gonic/gin" ) +type PingResponse struct { + Pong string `json:"message" example:"ping" doc:"Ping message"` +} + func SetupRouter() *gin.Engine { Router := gin.Default() @@ -17,7 +26,7 @@ func SetupRouter() *gin.Engine { Router.Use(middleware.Logger) Router.Use(gin.Recovery()) Router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"https://repair.nbtca.space"}, + AllowOrigins: []string{"https://repair.nbtca.space", "http://localhost:5173"}, AllowMethods: []string{"PUT", "PATCH", "GET", "POST", "DELETE"}, AllowHeaders: []string{"Origin"}, ExposeHeaders: []string{"Content-Length"}, @@ -29,27 +38,95 @@ func SetupRouter() *gin.Engine { MaxAge: 12 * time.Hour, })) - Router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + api := humagin.New(Router, huma.DefaultConfig("Saturday API", "1.0.0")) + + huma.Register(api, huma.Operation{ + OperationID: "ping", + Method: http.MethodGet, + Path: "/ping", + Summary: "Ping", + Tags: []string{"Common", "Public"}, + }, func(ctx context.Context, input *struct{}) (*util.CommonResponse[PingResponse], error) { + resp := PingResponse{ + Pong: "Hello", + } + return util.MakeCommonResponse(resp), nil }) - PublicGroup := Router.Group("/") - { - PublicGroup.GET("members/:MemberId", MemberRouterApp.GetPublicMemberById) - PublicGroup.GET("members", MemberRouterApp.GetPublicMemberByPage) - PublicGroup.POST("members/:MemberId/token", MemberRouterApp.CreateToken) - PublicGroup.PATCH("members/:MemberId/logto_id", MemberRouterApp.BindMemberLogtoId) - PublicGroup.GET("member/token/logto", MemberRouterApp.CreateTokenViaLogtoToken) - - PublicGroup.POST("clients/token/wechat", ClientRouterApp.CreateTokenViaWeChat) - - PublicGroup.GET("events/:EventId", EventRouterApp.GetPublicEventById) - PublicGroup.GET("events", EventRouterApp.GetPublicEventByPage) - - PublicGroup.GET("setting", SettingRouterApp.GetMiniAppSetting) - } + huma.Register(api, huma.Operation{ + OperationID: "get-public-member", + Method: http.MethodGet, + Path: "/members/{MemberId}", + Summary: "Get a public member by id", + Tags: []string{"Member", "Public"}, + }, MemberRouterApp.GetPublicMemberById) + + huma.Register(api, huma.Operation{ + OperationID: "get-public-member-by-page", + Method: http.MethodGet, + Path: "/members", + Summary: "Get a public member by page", + Tags: []string{"Member", "Public"}, + }, MemberRouterApp.GetPublicMemberByPage) + + huma.Register(api, huma.Operation{ + OperationID: "create-token", + Method: http.MethodPost, + Path: "/members/{MemberId}/token", + Summary: "Create token", + Tags: []string{"Member", "Public"}, + }, MemberRouterApp.CreateToken) + + huma.Register(api, huma.Operation{ + OperationID: "create-token-via-logto-token", + Method: http.MethodGet, + Path: "/member/token/logto", + Summary: "Create token via logto token", + Tags: []string{"Member", "Public"}, + }, MemberRouterApp.CreateTokenViaLogtoToken) + + huma.Register(api, huma.Operation{ + OperationID: "bind-member-logto-id", + Method: http.MethodPatch, + Path: "/members/{MemberId}/logto_id", + Summary: "Bind member logto id", + Tags: []string{"Member", "Public"}, + }, MemberRouterApp.BindMemberLogtoId) + + huma.Register(api, huma.Operation{ + OperationID: "create-token-via-wechat", + Method: http.MethodPost, + Path: "/clients/token/wechat", + Summary: "Create token via wechat", + Tags: []string{"Client", "Public"}, + }, ClientRouterApp.CreateTokenViaWeChat) + + Router.POST("/clients/token/logto", middleware.Auth("client"), ClientRouterApp.CreateTokenViaLogto) + + huma.Register(api, huma.Operation{ + OperationID: "get-public-event-by-id", + Method: http.MethodGet, + Path: "/events/{EventId}", + Summary: "Get a public event by id", + Tags: []string{"Event", "Public"}, + }, EventRouterApp.GetPublicEventById) + + huma.Register(api, huma.Operation{ + OperationID: "get-public-event-by-page", + Method: http.MethodGet, + Path: "/events", + Summary: "Get a public event by page", + Tags: []string{"Event", "Public"}, + }, EventRouterApp.GetPublicEventByPage) + + huma.Register(api, huma.Operation{ + OperationID: "create-member-with-logto", + Method: http.MethodPost, + Path: "/members/:MemberId/logto", + Summary: "Create member with logto", + Tags: []string{"Member", "Private"}, + }, MemberRouterApp.CreateWithLogto) - Router.POST("/members/:MemberId/logto", MemberRouterApp.CreateWithLogto) Router.PATCH("member/activate", middleware.Auth("member_inactive", "admin_inactive"), MemberRouterApp.Activate) @@ -85,7 +162,7 @@ func SetupRouter() *gin.Engine { AdminGroup.POST("/members/:MemberId", MemberRouterApp.Create) // TODO change this path AdminGroup.GET("/members/full", MemberRouterApp.GetMemberByPage) - AdminGroup.PATCH("/members/:MemberId", MemberRouterApp.UpdateBasic) + // AdminGroup.PATCH("/members/:MemberId", MemberRouterApp.UpdateBasic) AdminGroup.Use(middleware.EventActionPreProcess) AdminGroup.DELETE("/events/:EventId/commit", EventRouterApp.RejectCommit) diff --git a/router/member.go b/router/member.go index c3fc30a..5e78044 100644 --- a/router/member.go +++ b/router/member.go @@ -1,10 +1,12 @@ package router import ( + "context" "log" "net/http" "os" + "github.com/danielgtaylor/huma/v2" "github.com/nbtca/saturday/model" "github.com/nbtca/saturday/model/dto" "github.com/nbtca/saturday/repo" @@ -16,30 +18,24 @@ import ( type MemberRouter struct{} -func (MemberRouter) GetPublicMemberById(c *gin.Context) { - memberId := &dto.MemberId{} - if err := util.BindAll(c, memberId); util.CheckError(c, err) { - return - } - member, err := service.MemberServiceApp.GetPublicMemberById(memberId.MemberId) - if util.CheckError(c, err) { - return +func (MemberRouter) GetPublicMemberById(ctx context.Context, input *struct { + MemberId string `path:"MemberId" maxLength:"10" example:"2333333333" doc:"Name to greet"` +}) (*util.CommonResponse[model.PublicMember], error) { + member, err := service.MemberServiceApp.GetPublicMemberById(input.MemberId) + if err != nil { + return nil, huma.NewError(http.StatusUnprocessableEntity, err.Error()) } - c.JSON(200, member) + return util.MakeCommonResponse(member), nil } -func (MemberRouter) GetPublicMemberByPage(c *gin.Context) { - offset, limit, err := util.GetPaginationQuery(c) - if err != nil { - c.Error(err) - return - } - members, err := service.MemberServiceApp.GetPublicMembers(offset, limit) +func (MemberRouter) GetPublicMemberByPage(ctx context.Context, input *struct { + dto.PageRequest +}) (*util.CommonResponse[[]model.PublicMember], error) { + members, err := service.MemberServiceApp.GetPublicMembers(input.Offset, input.Limit) if err != nil { - c.Error(err) - return + return nil, huma.NewError(http.StatusUnprocessableEntity, err.Error()) } - c.JSON(200, members) + return util.MakeCommonResponse(members), nil } func (MemberRouter) GetMemberByPage(c *gin.Context) { @@ -65,76 +61,64 @@ func (MemberRouter) GetMemberById(c *gin.Context) { c.JSON(200, member) } -func (MemberRouter) CreateToken(c *gin.Context) { - req := &dto.CreateMemberTokenRequest{} - if err := util.BindAll(c, req); util.CheckError(c, err) { - return +func (MemberRouter) CreateToken(ctx context.Context, input *struct { + MemberId string `path:"MemberId" maxLength:"10" example:"2333333333" doc:"Member Id"` + Body struct { + Password string `json:"password"` } - member, err := service.MemberServiceApp.GetMemberById(req.MemberId) - if util.CheckError(c, err) { - return +}) (*util.CommonResponse[dto.CreateMemberTokenResponse], error) { + member, err := service.MemberServiceApp.GetMemberById(input.MemberId) + if err != nil { + return nil, huma.NewError(http.StatusUnprocessableEntity, err.Error()) } - if member.Password != req.Password { - c.AbortWithStatusJSON(util. - MakeServiceError(http.StatusUnprocessableEntity). - SetMessage("Validation Failed"). - AddDetailError("member", "password", "invalid password"). - Build()) - return + if member.Password != input.Body.Password { + return nil, huma.NewError(http.StatusUnprocessableEntity, "Invalid password") } token, err := service.MemberServiceApp.CreateToken(member) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.NewError(http.StatusUnprocessableEntity, err.Error()) } - res := dto.CreateMemberTokenResponse{ + return util.MakeCommonResponse(dto.CreateMemberTokenResponse{ Member: member, Token: token, - } - c.JSON(200, res) + }), nil } -func (MemberRouter) CreateTokenViaLogtoToken(c *gin.Context) { +func (MemberRouter) CreateTokenViaLogtoToken(c context.Context, input *struct { + Authorization string `header:"Authorization"` +}) (*util.CommonResponse[dto.CreateMemberTokenResponse], error) { service.LogtoServiceApp = service.MakeLogtoService(os.Getenv("LOGTO_ENDPOINT")) res, err := service.LogtoServiceApp.FetchLogtoToken(service.DefaultLogtoResource, "all") - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } + accessToken := res["access_token"].(string) - auth := c.GetHeader("Authorization") - user, err := service.LogtoServiceApp.FetchUserByToken(auth, accessToken) - if util.CheckError(c, err) { - return + user, err := service.LogtoServiceApp.FetchUserByToken(input.Authorization, accessToken) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - invalidTokenError := util. - MakeServiceError(http.StatusUnprocessableEntity). - AddDetailError("member", "logto token", "invalid token") if user["id"] == nil { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: id missing").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: id missing") } logto_id, ok := user["id"].(string) if !ok { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: failed at getting id").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: failed at getting id") } memberId, err := repo.GetMemberIdByLogtoId(logto_id) if err != nil || !memberId.Valid { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: member not found").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: member not found") } member, err := service.MemberServiceApp.GetMemberById(memberId.String) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } logto_roles, err := service.LogtoServiceApp.FetchUserRole(logto_id, accessToken) if err != nil { - c.AbortWithStatusJSON(util. - MakeServiceError(http.StatusUnprocessableEntity). - SetMessage("error at FetchLogtoToken" + err.Error()). - Build()) + return nil, huma.Error422UnprocessableEntity(err.Error()) } mappedRole := service.MemberServiceApp.MapLogtoUserRole(logto_roles) @@ -142,16 +126,13 @@ func (MemberRouter) CreateTokenViaLogtoToken(c *gin.Context) { member.Role = mappedRole err = service.MemberServiceApp.UpdateMember(member) if err != nil { - c.AbortWithStatusJSON(util. - MakeServiceError(http.StatusUnprocessableEntity). - SetMessage("error at syncing member role" + err.Error()). - Build()) + return nil, huma.Error422UnprocessableEntity("error at syncing member role" + err.Error()) } } t, err := service.MemberServiceApp.CreateToken(member) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } patchLogtoUserRequest := dto.PatchLogtoUserRequest{} @@ -169,11 +150,10 @@ func (MemberRouter) CreateTokenViaLogtoToken(c *gin.Context) { if err != nil { log.Println(err) } - response := dto.CreateMemberTokenResponse{ + return util.MakeCommonResponse(dto.CreateMemberTokenResponse{ Member: member, Token: t, - } - c.JSON(200, response) + }), nil } func (MemberRouter) Create(c *gin.Context) { @@ -200,54 +180,56 @@ func (MemberRouter) Create(c *gin.Context) { c.JSON(200, member) } -func (MemberRouter) CreateWithLogto(c *gin.Context) { - req := &dto.CreateMemberWithLogtoRequest{} - if err := util.BindAll(c, req); util.CheckError(c, err) { - return - } - +func (MemberRouter) CreateWithLogto(c context.Context, input *struct { + MemberId string `path:"MemberId" maxLength:"10" example:"2333333333" doc:"Member Id"` + LogtoId string `json:"logtoId" doc:"Logto Id"` + Name string `json:"name" minLength:"2" maxLength:"4" doc:"Name"` + Section string `json:"section"` + Alias string `json:"alias" maxLength:"20"` + Avatar string `json:"avatar" maxLength:"255"` + Profile string `json:"profile" maxLength:"1000"` + Phone string `json:"phone"` + QQ string `json:"qq" minLength:"5" maxLength:"20"` + Auth string `header:"Authorization"` +}) (*util.CommonResponse[model.Member], error) { res, err := service.LogtoServiceApp.FetchLogtoToken(service.DefaultLogtoResource, "all") - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } accessToken := res["access_token"].(string) service.LogtoServiceApp = service.MakeLogtoService(os.Getenv("LOGTO_ENDPOINT")) - auth := c.GetHeader("Authorization") - user, err := service.LogtoServiceApp.FetchUserByToken(auth, accessToken) - if util.CheckError(c, err) { - return + user, err := service.LogtoServiceApp.FetchUserByToken(input.Auth, accessToken) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - invalidTokenError := util. - MakeServiceError(http.StatusUnprocessableEntity). - AddDetailError("member", "logto token", "invalid token") if user["id"] == nil { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: id missing").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: id missing") } logtoId, ok := user["id"].(string) if !ok { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: failed at getting id").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: failed at getting id") } + member := &model.Member{ - MemberId: req.MemberId, - Alias: req.Alias, - Name: req.Name, - Section: req.Section, - Avatar: req.Avatar, - Profile: req.Profile, - QQ: req.QQ, - Phone: req.Phone, + MemberId: input.MemberId, + Alias: input.Alias, + Name: input.Name, + Section: input.Section, + Avatar: input.Avatar, + Profile: input.Profile, + QQ: input.QQ, + Phone: input.Phone, Role: "member", - CreatedBy: req.MemberId, + CreatedBy: input.MemberId, LogtoId: logtoId, } - err = service.MemberServiceApp.CreateMember(member) - if util.CheckError(c, err) { - return + if err = service.MemberServiceApp.CreateMember(member); err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - c.JSON(200, member) + + return util.MakeCommonResponse(*member), nil + } func (MemberRouter) CreateMany(c *gin.Context) { @@ -320,64 +302,50 @@ func (MemberRouter) Update(c *gin.Context) { c.JSON(200, member) } -func (MemberRouter) BindMemberLogtoId(c *gin.Context) { - req := &dto.CreateMemberTokenRequest{} - if err := util.BindAll(c, req); util.CheckError(c, err) { - return +func (MemberRouter) BindMemberLogtoId(c context.Context, input *struct { + MemberId string `path:"MemberId" maxLength:"10" example:"2333333333" doc:"Member Id"` + Authorization string `header:"Authorization"` + Body struct { + Password string `json:"password"` } - member, err := service.MemberServiceApp.GetMemberById(req.MemberId) - if util.CheckError(c, err) { - return +}) (*util.CommonResponse[model.Member], error) { + member, err := service.MemberServiceApp.GetMemberById(input.MemberId) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - if member.Password != req.Password { - c.AbortWithStatusJSON(util. - MakeServiceError(http.StatusUnprocessableEntity). - SetMessage("Validation Failed"). - AddDetailError("member", "password", "invalid password"). - Build()) - return + if member.Password != input.Body.Password { + return nil, huma.NewError(http.StatusUnprocessableEntity, "Invalid password") } service.LogtoServiceApp = service.MakeLogtoService(os.Getenv("LOGTO_ENDPOINT")) res, err := service.LogtoServiceApp.FetchLogtoToken(service.DefaultLogtoResource, "all") - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } accessToken := res["access_token"].(string) - auth := c.GetHeader("Authorization") - user, err := service.LogtoServiceApp.FetchUserByToken(auth, accessToken) - if util.CheckError(c, err) { - return + user, err := service.LogtoServiceApp.FetchUserByToken(input.Authorization, accessToken) + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - invalidTokenError := util. - MakeServiceError(http.StatusUnprocessableEntity). - AddDetailError("member", "logto token", "invalid token") if user["id"] == nil { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: id missing").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: id missing") } logtoId, ok := user["id"].(string) if !ok { - c.AbortWithStatusJSON(invalidTokenError.SetMessage("Invalid token: failed at getting id").Build()) - return + return nil, huma.Error422UnprocessableEntity("Invalid token: failed at getting id") } if member.LogtoId != "" { - c.AbortWithStatusJSON(util. - MakeServiceError(http.StatusUnprocessableEntity). - SetMessage("Validation Failed"). - AddDetailError("member", "logtoId", "already bound"). - Build()) - return + return nil, huma.Error422UnprocessableEntity("Validation Failed: member logtoId already bound") } member.LogtoId = logtoId err = service.MemberServiceApp.UpdateMember(member) - if util.CheckError(c, err) { - return + if err != nil { + return nil, huma.Error422UnprocessableEntity(err.Error()) } - c.JSON(200, member) + return util.MakeCommonResponse(member), nil } func (MemberRouter) UpdateBasic(c *gin.Context) { diff --git a/service/client.go b/service/client.go index a0d5c6d..5a1512c 100644 --- a/service/client.go +++ b/service/client.go @@ -11,7 +11,7 @@ import ( type ClientService struct { } -func (service ClientService) CreateTokenViaWechat(client model.Client) (string, error) { +func (service ClientService) CreateClientToken(client model.Client) (string, error) { res, err := util.CreateToken(util.Payload{Who: fmt.Sprint(client.ClientId), Role: "client"}) return res, err } @@ -24,6 +24,14 @@ func (service ClientService) GetClientByOpenId(openId string) (model.Client, err return client, nil } +func (service ClientService) GetClientByLogtoId(logtoId string) (model.Client, error) { + client, err := repo.GetClientByLogtoId(logtoId) + if err != nil { + return model.Client{}, err + } + return client, nil +} + func (service ClientService) CreateClientByOpenId(openId string) (model.Client, error) { client := model.Client{OpenId: openId} err := repo.CreateClient(&client) @@ -33,4 +41,13 @@ func (service ClientService) CreateClientByOpenId(openId string) (model.Client, return client, nil } +func (service ClientService) CreateClientByLogtoId(logtoId string) (model.Client, error) { + client := model.Client{LogtoId: logtoId} + err := repo.CreateClient(&client) + if err != nil { + return model.Client{}, err + } + return client, nil +} + var ClientServiceApp = ClientService{} diff --git a/service/logto.go b/service/logto.go index 87652a7..277eb31 100644 --- a/service/logto.go +++ b/service/logto.go @@ -197,4 +197,49 @@ func (l LogtoService) FetchUserRole(userId string, accessToken string) (FetchUse return body, nil } +type FetchUserInfoResponse struct { + Sub string `json:"sub"` + Name string `json:"name"` + Picture string `json:"picture"` + UpdatedAt int64 `json:"updated_at"` + Username string `json:"username"` + CreatedAt int64 `json:"created_at"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + CustomData map[string]interface{} `json:"custom_data"` + Roles []string `json:"roles"` +} + +func (l LogtoService) FetchUserInfo(accessToken string) (FetchUserInfoResponse, error) { + userInfoEndpointURL, err := url.JoinPath(os.Getenv("LOGTO_ENDPOINT"), "/oidc/me") + if err != nil { + return FetchUserInfoResponse{}, err + } + req, _ := http.NewRequest("GET", userInfoEndpointURL, nil) + + req.Header.Add("Authorization", "Bearer "+accessToken) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return FetchUserInfoResponse{}, err + } + defer res.Body.Close() + rawBody, err := io.ReadAll(res.Body) + if err != nil { + return FetchUserInfoResponse{}, err + } + + if res.Status != "200 OK" { + return FetchUserInfoResponse{}, fmt.Errorf(string(rawBody)) + } + + var body FetchUserInfoResponse + if err := json.Unmarshal(rawBody, &body); err != nil { + return FetchUserInfoResponse{}, err + } + + return body, nil + +} + var LogtoServiceApp LogtoService diff --git a/service/member.go b/service/member.go index c2b5b5b..f487e39 100644 --- a/service/member.go +++ b/service/member.go @@ -24,6 +24,20 @@ func (service *MemberService) GetMemberById(id string) (model.Member, error) { } } +func (service *MemberService) GetMemberByLogtoId(logtoId string) (model.Member, error) { + member, err := repo.GetMemberByLogtoId(logtoId) + if err != nil { + return model.Member{}, err + } + if member == (model.Member{}) { + error := util.MakeServiceError(http.StatusUnprocessableEntity).SetMessage("Validation Failed"). + AddDetailError("member", "logtoId", "invalid logtoId") + return member, error + } else { + return member, nil + } +} + func (service *MemberService) GetPublicMemberById(id string) (model.PublicMember, error) { member, err := service.GetMemberById(id) if err != nil { diff --git a/util/dbutil.go b/util/dbutil.go index fc33087..3b91725 100644 --- a/util/dbutil.go +++ b/util/dbutil.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "strconv" "strings" "time" @@ -48,7 +49,7 @@ func SetColumnPrefix(prefix string, column string) string { func Prefixer(prefix string, columns []string) []string { ans := make([]string, len(columns)) for i, v := range columns { - ans[i] = fmt.Sprint(string(prefix[0]), ".", v, " as '", SetColumnPrefix(prefix, v), "'") + ans[i] = fmt.Sprint(string(prefix[0]), ".", v, " as", strconv.Quote(SetColumnPrefix(prefix, v))) } return ans } diff --git a/util/request.go b/util/request.go index afadba3..65b845f 100644 --- a/util/request.go +++ b/util/request.go @@ -49,3 +49,11 @@ func GetIdentity(c *gin.Context) model.Identity { Role: role, } } + +type CommonResponse[T any] struct { + Body T +} + +func MakeCommonResponse[T any](body T) *CommonResponse[T] { + return &CommonResponse[T]{Body: body} +}